@@ -8,7 +8,6 @@ import { QfRound } from '../entities/qfRound';
88import { ChainType } from '../types/network' ;
99import { ORGANIZATION_LABELS } from '../entities/organization' ;
1010import { AppDataSource } from '../orm' ;
11- import { getPowerRound } from './powerRoundRepository' ;
1211
1312export const exportClusterMatchingDonationsFormat = async (
1413 qfRoundId : number ,
@@ -584,45 +583,94 @@ export const getRecentDonations = async (take: number): Promise<Donation[]> => {
584583 . getMany ( ) ;
585584} ;
586585
587- export const getSumOfGivbackEligibleDonationsForSpecificRound = async ( params : {
588- powerRound ?: number ;
589- } ) : Promise < number > => {
590- // This function calculates the total USD value of all donations that are eligible for Givback in a specific PowerRound
591-
592- try {
593- // If no powerRound is passed, get the latest one from the PowerRound table
594- const powerRound = params . powerRound || ( await getPowerRound ( ) ) ?. round ;
595- if ( ! powerRound ) {
596- throw new Error ( 'No powerRound found in the database.' ) ;
586+ export const getSumOfGivbackEligibleDonationsForSpecificRound =
587+ async ( _params : { powerRound ?: number } ) : Promise < number > => {
588+ // This function calculates the total USD value of all donations eligible for GIVback
589+ // Uses the operational window (16:00 UTC offset) to match givbacks service
590+ // Groups recurring donations and applies minimum thresholds
591+
592+ try {
593+ // Calculate date range for current month with 16:00 UTC offset (matches givbacks service)
594+ const now = new Date ( ) ;
595+ const year = now . getUTCFullYear ( ) ;
596+ const month = now . getUTCMonth ( ) ;
597+
598+ // Start: 1st of current month at 16:00 UTC
599+ const startDate = new Date ( Date . UTC ( year , month , 1 , 16 , 0 , 0 ) ) ;
600+ // End: Last day of current month at 15:59:59 UTC (or 1st of next month at 15:59:59)
601+ const endDate = new Date ( Date . UTC ( year , month + 1 , 0 , 15 , 59 , 59 ) ) ;
602+
603+ const startDateStr = startDate
604+ . toISOString ( )
605+ . replace ( 'T' , ' ' )
606+ . replace ( 'Z' , '' ) ;
607+ const endDateStr = endDate
608+ . toISOString ( )
609+ . replace ( 'T' , ' ' )
610+ . replace ( 'Z' , '' ) ;
611+
612+ // Minimum threshold: $4 for most projects, $0.05 for community project
613+ const minEligibleValueUsd = 4 ;
614+ const communityMinValueUsd = 0.05 ;
615+ const communityProjectSlug = 'the-giveth-community-of-makers' ;
616+
617+ // Execute query with:
618+ // 1. Date range (operational window)
619+ // 2. Group recurring donations by parent ID
620+ // 3. Apply minimum threshold on grouped totals
621+ // 4. Purple list exclusion
622+ const result = await AppDataSource . getDataSource ( ) . query (
623+ `
624+ WITH grouped_donations AS (
625+ SELECT
626+ COALESCE("recurringDonationId"::text, "id"::text) AS group_id,
627+ SUM("valueUsd") AS total_usd,
628+ SUM("valueUsd" * "givbackFactor") AS total_usd_with_factor,
629+ MAX("fromWalletAddress") AS from_address,
630+ MAX("projectId") AS project_id
631+ FROM "donation"
632+ WHERE "status" = 'verified'
633+ AND "isProjectGivbackEligible" = true
634+ AND "createdAt" > $1
635+ AND "createdAt" < $2
636+ GROUP BY COALESCE("recurringDonationId"::text, "id"::text)
637+ )
638+ SELECT COALESCE(SUM(gd.total_usd_with_factor), 0) AS "totalUsdWithGivbackFactor"
639+ FROM grouped_donations gd
640+ JOIN "project" p ON p.id = gd.project_id
641+ WHERE
642+ (
643+ (p.slug = $3 AND gd.total_usd > $4)
644+ OR (p.slug != $3 AND gd.total_usd >= $5)
645+ )
646+ AND NOT EXISTS (
647+ SELECT 1
648+ FROM "project_address" pa
649+ INNER JOIN "project" proj ON proj.id = pa."projectId"
650+ WHERE LOWER(pa.address) = LOWER(gd.from_address)
651+ AND pa."isRecipient" = true
652+ AND proj.verified = true
653+ AND proj."statusId" = 5
654+ );
655+ ` ,
656+ [
657+ startDateStr ,
658+ endDateStr ,
659+ communityProjectSlug ,
660+ communityMinValueUsd ,
661+ minEligibleValueUsd ,
662+ ] ,
663+ ) ;
664+
665+ return result ?. [ 0 ] ?. totalUsdWithGivbackFactor ?? 0 ;
666+ } catch ( e ) {
667+ logger . error (
668+ 'getSumOfGivbackEligibleDonationsForSpecificRound() error' ,
669+ e ,
670+ ) ;
671+ return 0 ;
597672 }
598-
599- // Execute the main raw SQL query with the powerRound
600- const result = await AppDataSource . getDataSource ( ) . query (
601- `
602- SELECT
603- SUM("donation"."valueUsd" * "donation"."givbackFactor") AS "totalUsdWithGivbackFactor"
604- FROM "donation"
605- WHERE "donation"."status" = 'verified'
606- AND "donation"."isProjectGivbackEligible" = true
607- AND "donation"."powerRound" = $1
608- AND NOT EXISTS (
609- SELECT 1
610- FROM "project_address" "pa"
611- INNER JOIN "project" "p" ON "p"."id" = "pa"."projectId"
612- WHERE "pa"."address" = "donation"."fromWalletAddress"
613- AND "pa"."isRecipient" = true
614- AND "p"."verified" = true
615- );
616- ` ,
617- [ powerRound ] ,
618- ) ;
619-
620- return result ?. [ 0 ] ?. totalUsdWithGivbackFactor ?? 0 ;
621- } catch ( e ) {
622- logger . error ( 'getSumOfGivbackEligibleDonationsForSpecificRound() error' , e ) ;
623- return 0 ;
624- }
625- } ;
673+ } ;
626674
627675export const getPendingDonationsIds = ( ) : Promise < { id : number } [ ] > => {
628676 const date = moment ( )
0 commit comments