@@ -27,6 +27,12 @@ type CommissionGroupIdQueryRow = {
2727 count : bigint ;
2828} ;
2929
30+ type CommissionPartnerTagQueryRow = {
31+ partnerTagId : string ;
32+ earnings : bigint ;
33+ count : bigint ;
34+ } ;
35+
3036const excludedStatuses = [
3137 CommissionStatus . duplicate ,
3238 CommissionStatus . fraud ,
@@ -41,6 +47,7 @@ function commissionSqlConditions({
4147 partnerFilter,
4248 typeFilter,
4349 groupIdParam,
50+ partnerTagIdParam,
4451} : {
4552 programId : string ;
4653 startDate : Date ;
@@ -49,6 +56,7 @@ function commissionSqlConditions({
4956 partnerFilter : ReturnType < typeof parseFilterValue > ;
5057 typeFilter : ReturnType < typeof parseFilterValue > | null ;
5158 groupIdParam : string | undefined ;
59+ partnerTagIdParam : string | undefined ;
5260} ) : Prisma . Sql [ ] {
5361 const conditions : Prisma . Sql [ ] = [
5462 Prisma . sql `c.programId = ${ programId } ` ,
@@ -94,6 +102,30 @@ function commissionSqlConditions({
94102 }
95103 }
96104
105+ if ( partnerTagIdParam ) {
106+ const partnerTagFilter = parseFilterValue ( partnerTagIdParam ) ;
107+ if ( partnerTagFilter ) {
108+ const list = Prisma . join (
109+ partnerTagFilter . values . map ( ( v ) => Prisma . sql `${ v } ` ) ,
110+ ) ;
111+ if ( partnerTagFilter . sqlOperator === "NOT IN" ) {
112+ conditions . push ( Prisma . sql `NOT EXISTS (
113+ SELECT 1 FROM ProgramPartnerTag ppt
114+ WHERE ppt.programId = c.programId
115+ AND ppt.partnerId = c.partnerId
116+ AND ppt.partnerTagId IN (${ list } )
117+ )` ) ;
118+ } else {
119+ conditions . push ( Prisma . sql `EXISTS (
120+ SELECT 1 FROM ProgramPartnerTag ppt
121+ WHERE ppt.programId = c.programId
122+ AND ppt.partnerId = c.partnerId
123+ AND ppt.partnerTagId IN (${ list } )
124+ )` ) ;
125+ }
126+ }
127+ }
128+
97129 return conditions ;
98130}
99131
@@ -123,6 +155,10 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
123155 return byGroupId ( { programId, parsed, startDate, endDate } ) ;
124156 }
125157
158+ if ( groupBy === "partnerTagId" ) {
159+ return byPartnerTagIdId ( { programId, parsed, startDate, endDate } ) ;
160+ }
161+
126162 if ( groupBy === "partnerId" ) {
127163 return byPartnerId ( { programId, parsed, startDate, endDate } ) ;
128164 }
@@ -141,9 +177,10 @@ async function byType({
141177 startDate : Date ;
142178 endDate : Date ;
143179} ) {
144- const { status, partnerId, groupId, type } = parsed ;
180+ const { status, partnerId, groupId, partnerTagId , type } = parsed ;
145181 const partnerFilter = parseFilterValue ( partnerId ) ;
146182 const groupFilter = parseFilterValue ( groupId ) ;
183+ const partnerTagFilter = parseFilterValue ( partnerTagId ) ;
147184
148185 const rawTypeFilter = parseFilterValue ( type ) ;
149186 const validCommissionTypes = new Set ( Object . values ( CommissionType ) ) ;
@@ -167,6 +204,21 @@ async function byType({
167204 ? { ...rawTypeFilter , values : validTypeValues }
168205 : null ;
169206
207+ const programEnrollmentFilter = {
208+ ...( groupFilter && {
209+ groupId :
210+ groupFilter . sqlOperator === "NOT IN"
211+ ? { notIn : groupFilter . values }
212+ : { in : groupFilter . values } ,
213+ } ) ,
214+ ...( partnerTagFilter && {
215+ programPartnerTags :
216+ partnerTagFilter . sqlOperator === "NOT IN"
217+ ? { none : { partnerTagId : { in : partnerTagFilter . values } } }
218+ : { some : { partnerTagId : { in : partnerTagFilter . values } } } ,
219+ } ) ,
220+ } ;
221+
170222 const baseWhere : Prisma . CommissionWhereInput = {
171223 programId,
172224 createdAt : { gte : startDate , lt : endDate } ,
@@ -183,13 +235,8 @@ async function byType({
183235 ? { notIn : typeFilter . values }
184236 : { in : typeFilter . values } ,
185237 } ) ,
186- ...( groupFilter && {
187- programEnrollment : {
188- groupId :
189- groupFilter . sqlOperator === "NOT IN"
190- ? { notIn : groupFilter . values }
191- : { in : groupFilter . values } ,
192- } ,
238+ ...( Object . keys ( programEnrollmentFilter ) . length > 0 && {
239+ programEnrollment : programEnrollmentFilter ,
193240 } ) ,
194241 } ;
195242
@@ -224,7 +271,7 @@ async function byGroupId({
224271 startDate : Date ;
225272 endDate : Date ;
226273} ) {
227- const { status, partnerId, groupId, type } = parsed ;
274+ const { status, partnerId, groupId, partnerTagId , type } = parsed ;
228275 const partnerFilter = parseFilterValue ( partnerId ) ;
229276
230277 const rawTypeFilter = parseFilterValue ( type ) ;
@@ -257,6 +304,7 @@ async function byGroupId({
257304 partnerFilter,
258305 typeFilter,
259306 groupIdParam : groupId ,
307+ partnerTagIdParam : partnerTagId ,
260308 } ) ;
261309
262310 const whereClause = Prisma . join ( conditions , " AND " ) ;
@@ -308,6 +356,112 @@ async function byGroupId({
308356 return NextResponse . json ( commissionAnalyticsSchema . groupId . parse ( result ) ) ;
309357}
310358
359+ async function byPartnerTagIdId ( {
360+ programId,
361+ parsed,
362+ startDate,
363+ endDate,
364+ } : {
365+ programId : string ;
366+ parsed : CommissionAnalyticsQuery ;
367+ startDate : Date ;
368+ endDate : Date ;
369+ } ) {
370+ const { status, partnerId, groupId, partnerTagId, type } = parsed ;
371+ const partnerFilter = parseFilterValue ( partnerId ) ;
372+
373+ const rawTypeFilter = parseFilterValue ( type ) ;
374+ const validCommissionTypes = new Set ( Object . values ( CommissionType ) ) ;
375+
376+ const validTypeValues = rawTypeFilter
377+ ? ( rawTypeFilter . values . filter ( ( v ) =>
378+ validCommissionTypes . has ( v as CommissionType ) ,
379+ ) as CommissionType [ ] )
380+ : [ ] ;
381+
382+ if (
383+ rawTypeFilter ?. sqlOperator === "IN" &&
384+ rawTypeFilter . values . length > 0 &&
385+ validTypeValues . length === 0
386+ ) {
387+ return NextResponse . json ( commissionAnalyticsSchema . partnerTagId . parse ( [ ] ) ) ;
388+ }
389+
390+ const typeFilter =
391+ rawTypeFilter && validTypeValues . length > 0
392+ ? { ...rawTypeFilter , values : validTypeValues }
393+ : null ;
394+
395+ const partnerTagFilter = parseFilterValue ( partnerTagId ) ;
396+
397+ const conditions = commissionSqlConditions ( {
398+ programId,
399+ startDate,
400+ endDate,
401+ status,
402+ partnerFilter,
403+ typeFilter,
404+ groupIdParam : groupId ,
405+ partnerTagIdParam : undefined ,
406+ } ) ;
407+
408+ if ( partnerTagFilter ) {
409+ const list = Prisma . join (
410+ partnerTagFilter . values . map ( ( v ) => Prisma . sql `${ v } ` ) ,
411+ ) ;
412+ if ( partnerTagFilter . sqlOperator === "IN" ) {
413+ conditions . push ( Prisma . sql `ppt.partnerTagId IN (${ list } )` ) ;
414+ } else {
415+ conditions . push ( Prisma . sql `NOT EXISTS (
416+ SELECT 1 FROM ProgramPartnerTag ppt_excl
417+ WHERE ppt_excl.programId = c.programId
418+ AND ppt_excl.partnerId = c.partnerId
419+ AND ppt_excl.partnerTagId IN (${ list } )
420+ )` ) ;
421+ }
422+ }
423+
424+ const whereClause = Prisma . join ( conditions , " AND " ) ;
425+
426+ const rows = await prisma . $queryRaw < CommissionPartnerTagQueryRow [ ] > (
427+ Prisma . sql `
428+ SELECT
429+ ppt.partnerTagId AS partnerTagId,
430+ SUM(c.earnings) AS earnings,
431+ COUNT(c.id) AS count
432+ FROM Commission c
433+ JOIN ProgramPartnerTag ppt
434+ ON ppt.programId = c.programId
435+ AND ppt.partnerId = c.partnerId
436+ WHERE ${ whereClause }
437+ GROUP BY ppt.partnerTagId
438+ ORDER BY earnings DESC` ,
439+ ) ;
440+
441+ const partnerTagIds = rows . map ( ( r ) => r . partnerTagId ) ;
442+
443+ const partnerTags =
444+ partnerTagIds . length > 0
445+ ? await prisma . partnerTag . findMany ( {
446+ where : { id : { in : partnerTagIds } } ,
447+ select : { id : true , name : true } ,
448+ } )
449+ : [ ] ;
450+
451+ const partnerTagById = new Map ( partnerTags . map ( ( t ) => [ t . id , t ] ) ) ;
452+
453+ const result = rows . map ( ( row ) => ( {
454+ key : row . partnerTagId ,
455+ label : partnerTagById . get ( row . partnerTagId ) ?. name ?? row . partnerTagId ,
456+ earnings : Number ( row . earnings ) ,
457+ count : Number ( row . count ) ,
458+ } ) ) ;
459+
460+ return NextResponse . json (
461+ commissionAnalyticsSchema . partnerTagId . parse ( result ) ,
462+ ) ;
463+ }
464+
311465async function byPartnerId ( {
312466 programId,
313467 parsed,
@@ -319,9 +473,10 @@ async function byPartnerId({
319473 startDate : Date ;
320474 endDate : Date ;
321475} ) {
322- const { status, partnerId, groupId, type } = parsed ;
476+ const { status, partnerId, groupId, partnerTagId , type } = parsed ;
323477 const partnerFilter = parseFilterValue ( partnerId ) ;
324478 const groupFilter = parseFilterValue ( groupId ) ;
479+ const partnerTagFilter = parseFilterValue ( partnerTagId ) ;
325480
326481 const rawTypeFilter = parseFilterValue ( type ) ;
327482 const validCommissionTypes = new Set ( Object . values ( CommissionType ) ) ;
@@ -345,6 +500,21 @@ async function byPartnerId({
345500 ? { ...rawTypeFilter , values : validTypeValues }
346501 : null ;
347502
503+ const programEnrollmentFilter = {
504+ ...( groupFilter && {
505+ groupId :
506+ groupFilter . sqlOperator === "NOT IN"
507+ ? { notIn : groupFilter . values }
508+ : { in : groupFilter . values } ,
509+ } ) ,
510+ ...( partnerTagFilter && {
511+ programPartnerTags :
512+ partnerTagFilter . sqlOperator === "NOT IN"
513+ ? { none : { partnerTagId : { in : partnerTagFilter . values } } }
514+ : { some : { partnerTagId : { in : partnerTagFilter . values } } } ,
515+ } ) ,
516+ } ;
517+
348518 const grouped = await prisma . commission . groupBy ( {
349519 by : [ "partnerId" ] ,
350520 where : {
@@ -363,13 +533,8 @@ async function byPartnerId({
363533 ? { notIn : typeFilter . values }
364534 : { in : typeFilter . values } ,
365535 } ) ,
366- ...( groupFilter && {
367- programEnrollment : {
368- groupId :
369- groupFilter . sqlOperator === "NOT IN"
370- ? { notIn : groupFilter . values }
371- : { in : groupFilter . values } ,
372- } ,
536+ ...( Object . keys ( programEnrollmentFilter ) . length > 0 && {
537+ programEnrollment : programEnrollmentFilter ,
373538 } ) ,
374539 } ,
375540 _sum : { earnings : true } ,
@@ -418,8 +583,17 @@ async function byTimeseries({
418583 programId : string ;
419584 parsed : z . infer < typeof commissionAnalyticsQuerySchema > ;
420585} ) {
421- const { start, end, interval, timezone, status, partnerId, groupId, type } =
422- parsed ;
586+ const {
587+ start,
588+ end,
589+ interval,
590+ timezone,
591+ status,
592+ partnerId,
593+ groupId,
594+ partnerTagId,
595+ type,
596+ } = parsed ;
423597
424598 const { startDate, endDate, granularity } = getStartEndDates ( {
425599 interval,
@@ -449,6 +623,7 @@ async function byTimeseries({
449623 partnerFilter,
450624 typeFilter,
451625 groupIdParam : groupId ,
626+ partnerTagIdParam : partnerTagId ,
452627 } ) ;
453628
454629 const whereClause = Prisma . join ( conditions , " AND " ) ;
0 commit comments