Skip to content

Commit d3be36f

Browse files
committed
refactor: implement conference status summary logic and restructure status router
1 parent 88c3489 commit d3be36f

5 files changed

Lines changed: 375 additions & 151 deletions

File tree

src/app/api/cron/weekly-update/route.ts

Lines changed: 77 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,8 @@
11
import { NextRequest, NextResponse } from 'next/server'
2-
import { fetchEventTickets } from '@/lib/tickets/api'
3-
import { TicketSalesProcessor } from '@/lib/tickets/processor'
4-
import type {
5-
ProcessTicketSalesInput,
6-
TicketAnalysisResult,
7-
} from '@/lib/tickets/types'
82
import { getConferenceForCurrentDomain } from '@/lib/conference/sanity'
93
import { isConferenceOver } from '@/lib/conference/state'
10-
import { getOrganizerCount, getSpeakers } from '@/lib/speaker/sanity'
11-
import { Status } from '@/lib/proposal/types'
12-
import { getProposals } from '@/lib/proposal/server'
4+
import { buildConferenceStatusSummary } from '@/lib/status/summary'
135
import { sendWeeklyUpdateToSlack } from '@/lib/slack/weeklyUpdate'
14-
import type { ProposalSummaryData } from '@/lib/slack/weeklyUpdate'
15-
import {
16-
aggregateSponsorPipeline,
17-
type SponsorPipelineData,
18-
} from '@/lib/sponsor-crm/pipeline'
19-
import { listSponsorsForConference } from '@/lib/sponsor-crm/sanity'
20-
import { calculateTicketStatistics } from '@/lib/tickets/utils'
216
import { unstable_noStore as noStore } from 'next/cache'
227

238
export async function GET(request: NextRequest) {
@@ -45,6 +30,7 @@ export async function GET(request: NextRequest) {
4530
const { conference, error: conferenceError } =
4631
await getConferenceForCurrentDomain({
4732
sponsors: true,
33+
organizers: true,
4834
featuredSpeakers: false,
4935
featuredTalks: false,
5036
})
@@ -74,156 +60,96 @@ export async function GET(request: NextRequest) {
7460
)
7561
}
7662

77-
if (!conference.checkinCustomerId || !conference.checkinEventId) {
78-
console.error('Conference missing checkin configuration')
79-
return NextResponse.json(
80-
{ error: 'Conference not configured for ticket sales tracking' },
81-
{ status: 400 },
82-
)
83-
}
84-
85-
const allTickets = await fetchEventTickets(
86-
conference.checkinCustomerId,
87-
conference.checkinEventId,
88-
)
89-
90-
const paidTickets = allTickets.filter((t) => parseFloat(t.sum) > 0)
91-
const freeTickets = allTickets.filter((t) => parseFloat(t.sum) === 0)
92-
93-
const { count: organizerCount, err: organizerErr } =
94-
await getOrganizerCount()
95-
if (organizerErr) {
96-
console.log('Failed to fetch organizer count:', organizerErr)
97-
}
98-
99-
const { speakers, err: speakersErr } = await getSpeakers(
100-
conference._id,
101-
[Status.confirmed],
102-
false,
103-
)
104-
if (speakersErr) {
105-
console.log('Failed to fetch speakers:', speakersErr)
106-
}
107-
108-
let analysis: TicketAnalysisResult | null = null
109-
110-
const targetConfig = conference.ticketTargets
111-
if (
112-
targetConfig &&
113-
targetConfig.enabled &&
114-
conference.ticketCapacity &&
115-
targetConfig.salesStartDate &&
116-
targetConfig.targetCurve &&
117-
paidTickets.length > 0
118-
) {
119-
try {
120-
const input: ProcessTicketSalesInput = {
121-
tickets: paidTickets.map((t) => ({
122-
order_id: t.order_id,
123-
order_date: t.order_date,
124-
category: t.category,
125-
sum: t.sum,
126-
})),
127-
config: targetConfig,
128-
capacity: conference.ticketCapacity,
129-
conference,
130-
conferenceDate:
131-
conference.startDate ||
132-
conference.programDate ||
133-
new Date().toISOString(),
134-
speakerCount: speakers.length,
135-
}
136-
137-
const processor = new TicketSalesProcessor(input)
138-
analysis = processor.process()
139-
} catch (error) {
140-
console.log(
141-
'Target analysis calculation failed:',
142-
(error as Error).message,
143-
)
144-
}
145-
}
63+
const summary = await buildConferenceStatusSummary(conference)
14664

147-
const basicStats = calculateTicketStatistics(paidTickets)
148-
const statistics = analysis?.statistics || {
149-
...basicStats,
150-
categoryBreakdown: {},
151-
sponsorTickets: 0,
152-
speakerTickets: 0,
153-
totalCapacityUsed: paidTickets.length,
65+
for (const err of summary.errors) {
66+
console.log(`${err.section} fetch failed:`, err.message)
15467
}
15568

156-
let proposalSummary: ProposalSummaryData | null = null
157-
try {
158-
const { proposals } = await getProposals({
159-
conferenceId: conference._id,
160-
returnAll: true,
161-
})
162-
proposalSummary = {
163-
submitted: proposals.filter((p) => p.status === Status.submitted)
164-
.length,
165-
accepted: proposals.filter((p) => p.status === Status.accepted).length,
166-
confirmed: proposals.filter((p) => p.status === Status.confirmed)
167-
.length,
168-
rejected: proposals.filter((p) => p.status === Status.rejected).length,
169-
withdrawn: proposals.filter((p) => p.status === Status.withdrawn)
170-
.length,
171-
total: proposals.length,
172-
}
173-
} catch (error) {
174-
console.log('Proposal summary fetch failed:', (error as Error).message)
175-
}
176-
177-
let sponsorPipeline: SponsorPipelineData | null = null
178-
const { sponsors: crmSponsors, error: sponsorsError } =
179-
await listSponsorsForConference(conference._id)
180-
181-
if (sponsorsError) {
182-
console.log('Sponsor pipeline data fetch failed:', sponsorsError.message)
183-
} else if (crmSponsors && crmSponsors.length > 0) {
184-
sponsorPipeline = aggregateSponsorPipeline(crmSponsors)
185-
}
69+
const complimentary =
70+
(summary.tickets?.speakerTickets ?? 0) +
71+
(summary.tickets?.organizerTickets ?? 0) +
72+
(summary.tickets?.sponsorTickets ?? 0)
18673

18774
await sendWeeklyUpdateToSlack({
18875
conference,
189-
ticketsByCategory: statistics.categoryBreakdown,
190-
paidTickets: statistics.totalPaidTickets,
191-
sponsorTickets: statistics.sponsorTickets,
192-
speakerTickets: statistics.speakerTickets,
193-
organizerTickets: organizerCount,
194-
freeTicketsClaimed: freeTickets.length,
195-
totalTickets: statistics.totalCapacityUsed,
196-
totalRevenue: statistics.totalRevenue,
197-
targetAnalysis: analysis,
198-
sponsorPipeline,
199-
proposalSummary,
200-
lastUpdated: new Date().toISOString(),
76+
ticketsByCategory: summary.tickets?.categoryBreakdown ?? {},
77+
paidTickets: summary.tickets?.paidTickets ?? 0,
78+
sponsorTickets: summary.tickets?.sponsorTickets ?? 0,
79+
speakerTickets: summary.tickets?.speakerTickets ?? 0,
80+
organizerTickets: summary.tickets?.organizerTickets ?? 0,
81+
freeTicketsClaimed: summary.tickets?.freeTicketsClaimed ?? 0,
82+
totalTickets: summary.tickets?.totalTickets ?? 0,
83+
totalRevenue: summary.tickets?.totalRevenue ?? 0,
84+
targetAnalysis: summary.targetProgress
85+
? {
86+
progression: [],
87+
capacity: summary.targetProgress.capacity,
88+
performance: {
89+
currentPercentage: summary.targetProgress.currentPercentage,
90+
targetPercentage: summary.targetProgress.targetPercentage,
91+
variance: summary.targetProgress.variance,
92+
isOnTrack: summary.targetProgress.isOnTrack,
93+
nextMilestone: summary.targetProgress.nextMilestone
94+
? {
95+
date: '',
96+
label: summary.targetProgress.nextMilestone.label,
97+
daysAway: summary.targetProgress.nextMilestone.daysAway,
98+
}
99+
: null,
100+
},
101+
statistics: {
102+
totalPaidTickets: summary.tickets?.paidTickets ?? 0,
103+
totalRevenue: summary.tickets?.totalRevenue ?? 0,
104+
totalOrders: 0,
105+
averageTicketPrice: 0,
106+
categoryBreakdown: summary.tickets?.categoryBreakdown ?? {},
107+
sponsorTickets: summary.tickets?.sponsorTickets ?? 0,
108+
speakerTickets: summary.tickets?.speakerTickets ?? 0,
109+
totalCapacityUsed: summary.tickets?.totalTickets ?? 0,
110+
},
111+
}
112+
: null,
113+
sponsorPipeline: summary.sponsors,
114+
proposalSummary: summary.proposals
115+
? {
116+
submitted: summary.proposals.submitted,
117+
accepted: summary.proposals.accepted,
118+
confirmed: summary.proposals.confirmed,
119+
rejected: summary.proposals.rejected,
120+
withdrawn: summary.proposals.withdrawn,
121+
total: summary.proposals.total,
122+
}
123+
: null,
124+
lastUpdated: summary.lastUpdated,
201125
})
202126

203127
return NextResponse.json({
204128
success: true,
205129
data: {
206-
conference: conference.title,
207-
paidTickets: statistics.totalPaidTickets,
208-
sponsorTickets: statistics.sponsorTickets,
209-
speakerTickets: statistics.speakerTickets,
210-
totalTickets: statistics.totalCapacityUsed,
211-
totalRevenue: statistics.totalRevenue,
212-
categories: statistics.categoryBreakdown,
213-
targetAnalysis: analysis
130+
conference: summary.conferenceTitle,
131+
paidTickets: summary.tickets?.paidTickets ?? 0,
132+
sponsorTickets: summary.tickets?.sponsorTickets ?? 0,
133+
speakerTickets: summary.tickets?.speakerTickets ?? 0,
134+
totalTickets: summary.tickets?.totalTickets ?? 0,
135+
totalRevenue: summary.tickets?.totalRevenue ?? 0,
136+
categories: summary.tickets?.categoryBreakdown ?? {},
137+
targetAnalysis: summary.targetProgress
214138
? {
215139
enabled: true,
216-
capacity: analysis.capacity,
217-
currentTargetPercentage: analysis.performance.targetPercentage,
218-
actualPercentage: analysis.performance.currentPercentage,
219-
variance: analysis.performance.variance,
220-
isOnTrack: analysis.performance.isOnTrack,
221-
nextMilestone: analysis.performance.nextMilestone,
140+
capacity: summary.targetProgress.capacity,
141+
currentTargetPercentage: summary.targetProgress.targetPercentage,
142+
actualPercentage: summary.targetProgress.currentPercentage,
143+
variance: summary.targetProgress.variance,
144+
isOnTrack: summary.targetProgress.isOnTrack,
145+
nextMilestone: summary.targetProgress.nextMilestone,
222146
}
223147
: null,
224-
sponsorPipeline,
225-
proposalSummary,
226-
lastUpdated: new Date().toISOString(),
148+
sponsorPipeline: summary.sponsors,
149+
proposalSummary: summary.proposals,
150+
complimentary,
151+
lastUpdated: summary.lastUpdated,
152+
errors: summary.errors,
227153
},
228154
})
229155
} catch (error) {

0 commit comments

Comments
 (0)