|
1 | 1 | 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' |
8 | 2 | import { getConferenceForCurrentDomain } from '@/lib/conference/sanity' |
9 | 3 | 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' |
13 | 5 | 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' |
21 | 6 | import { unstable_noStore as noStore } from 'next/cache' |
22 | 7 |
|
23 | 8 | export async function GET(request: NextRequest) { |
@@ -45,6 +30,7 @@ export async function GET(request: NextRequest) { |
45 | 30 | const { conference, error: conferenceError } = |
46 | 31 | await getConferenceForCurrentDomain({ |
47 | 32 | sponsors: true, |
| 33 | + organizers: true, |
48 | 34 | featuredSpeakers: false, |
49 | 35 | featuredTalks: false, |
50 | 36 | }) |
@@ -74,156 +60,96 @@ export async function GET(request: NextRequest) { |
74 | 60 | ) |
75 | 61 | } |
76 | 62 |
|
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) |
146 | 64 |
|
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) |
154 | 67 | } |
155 | 68 |
|
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) |
186 | 73 |
|
187 | 74 | await sendWeeklyUpdateToSlack({ |
188 | 75 | 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, |
201 | 125 | }) |
202 | 126 |
|
203 | 127 | return NextResponse.json({ |
204 | 128 | success: true, |
205 | 129 | 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 |
214 | 138 | ? { |
215 | 139 | 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, |
222 | 146 | } |
223 | 147 | : 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, |
227 | 153 | }, |
228 | 154 | }) |
229 | 155 | } catch (error) { |
|
0 commit comments