Skip to content

Commit 7310e72

Browse files
authored
Merge pull request #16 from topcoder-platform/PM-921_qa-fixes
PM-921 - implement missing methods & checks for winnings & admin winn…
2 parents 321b3fd + 3f9dafa commit 7310e72

File tree

10 files changed

+346
-74
lines changed

10 files changed

+346
-74
lines changed

src/api/admin-winning/adminWinning.service.ts

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
PaymentStatus,
1919
AuditPayoutDto,
2020
} from 'src/dto/adminWinning.dto';
21+
import { TaxFormRepository } from '../repository/taxForm.repo';
22+
import { PaymentMethodRepository } from '../repository/paymentMethod.repo';
2123

2224
const ONE_DAY = 24 * 60 * 60 * 1000;
2325

@@ -30,7 +32,11 @@ export class AdminWinningService {
3032
* Constructs the admin winning service with the given dependencies.
3133
* @param prisma the prisma service.
3234
*/
33-
constructor(private readonly prisma: PrismaService) {}
35+
constructor(
36+
private readonly prisma: PrismaService,
37+
private readonly taxFormRepo: TaxFormRepository,
38+
private readonly paymentMethodRepo: PaymentMethodRepository,
39+
) {}
3440

3541
/**
3642
* Search winnings with parameters
@@ -101,7 +107,7 @@ export class AdminWinningService {
101107
details: item.payment?.map((paymentItem) => ({
102108
id: paymentItem.payment_id,
103109
netAmount: Number(paymentItem.net_amount),
104-
grossAmount: Number(paymentItem.gross_amount),
110+
grossAmount: (paymentItem.gross_amount),
105111
totalAmount: Number(paymentItem.total_amount),
106112
installmentNumber: paymentItem.installment_number,
107113
datePaid: paymentItem.date_paid ?? undefined,
@@ -276,6 +282,21 @@ export class AdminWinningService {
276282
return orderBy;
277283
}
278284

285+
private getPaymentsByWinningsId(winningsId: string, paymentId?: string) {
286+
return this.prisma.payment.findMany({
287+
where: {
288+
winnings_id: {
289+
equals: winningsId,
290+
},
291+
payment_id: paymentId
292+
? {
293+
equals: paymentId,
294+
}
295+
: undefined,
296+
},
297+
});
298+
}
299+
279300
/**
280301
* Update winnings with parameters
281302
* @param body the request body
@@ -291,18 +312,10 @@ export class AdminWinningService {
291312
let needsReconciliation = false;
292313
const winningsId = body.winningsId;
293314
try {
294-
const payments = await this.prisma.payment.findMany({
295-
where: {
296-
winnings_id: {
297-
equals: winningsId,
298-
},
299-
payment_id: body.paymentId
300-
? {
301-
equals: body.paymentId,
302-
}
303-
: undefined,
304-
},
305-
});
315+
const payments = await this.getPaymentsByWinningsId(
316+
winningsId,
317+
body.paymentId,
318+
);
306319

307320
if (payments.length === 0) {
308321
throw new NotFoundException('failed to get current payments');
@@ -316,6 +329,13 @@ export class AdminWinningService {
316329
const transactions: PrismaPromise<any>[] = [];
317330
const now = new Date().getTime();
318331
payments.forEach((payment) => {
332+
if (
333+
payment.payment_status &&
334+
payment.payment_status === PaymentStatus.CANCELLED
335+
) {
336+
throw new BadRequestException('cannot update cancelled winnings');
337+
}
338+
319339
let version = payment.version ?? 1;
320340
// Update Payment Status if requested
321341
if (body.paymentStatus) {
@@ -356,7 +376,10 @@ export class AdminWinningService {
356376
break;
357377
}
358378

359-
if (errMessage) {
379+
if (
380+
errMessage &&
381+
payment.payment_status === PaymentStatus.PROCESSING
382+
) {
360383
throw new BadRequestException(errMessage);
361384
}
362385

@@ -371,7 +394,10 @@ export class AdminWinningService {
371394
),
372395
);
373396
version += 1;
374-
needsReconciliation = true;
397+
398+
if (body.paymentStatus === PaymentStatus.OWED) {
399+
needsReconciliation = true;
400+
}
375401

376402
if (payment.installment_number === 1) {
377403
transactions.push(
@@ -388,11 +414,6 @@ export class AdminWinningService {
388414
// Update Release Date if requested
389415
if (body.releaseDate) {
390416
const newReleaseDate = new Date(body.releaseDate);
391-
if (newReleaseDate.getTime() < now) {
392-
throw new BadRequestException(
393-
'new release date cannot be in the past',
394-
);
395-
}
396417

397418
transactions.push(
398419
this.updateReleaseDate(
@@ -417,10 +438,11 @@ export class AdminWinningService {
417438
}
418439
}
419440

420-
// Update Release Date if requested
441+
// Update payment amount if requested
421442
if (
422443
body.paymentAmount !== undefined &&
423444
(payment.payment_status === PaymentStatus.OWED ||
445+
payment.payment_status === PaymentStatus.ON_HOLD ||
424446
payment.payment_status === PaymentStatus.ON_HOLD ||
425447
payment.payment_status === PaymentStatus.ON_HOLD_ADMIN)
426448
) {
@@ -661,9 +683,47 @@ export class AdminWinningService {
661683
});
662684
}
663685

664-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
665-
private async reconcileWinningsStatusOnUserDetailsUpdate(userId: string) {
666-
// not implement, because it's about detail payment
686+
/**
687+
* Update payment for user from one status to another
688+
*
689+
* @param userId user id
690+
* @param fromStatus from status
691+
* @param toStatus to status
692+
* @param tx transaction
693+
*/
694+
updateWinningsStatus(userId, fromStatus, toStatus) {
695+
return this.prisma.$executeRaw`
696+
UPDATE payment
697+
SET payment_status = ${toStatus}::payment_status,
698+
updated_at = now(),
699+
updated_by = 'system',
700+
version = version + 1
701+
FROM winnings
702+
WHERE payment.winnings_id = winnings.winning_id
703+
AND winnings.winner_id = ${userId}
704+
AND payment.payment_status = ${fromStatus}::payment_status AND version = version
705+
`;
706+
}
707+
708+
/**
709+
* Reconcile winning if user data updated
710+
*
711+
* @param userId user id
712+
*/
713+
async reconcileWinningsStatusOnUserDetailsUpdate(userId) {
714+
const hasTaxForm = await this.taxFormRepo.hasActiveTaxForm(userId);
715+
const hasPaymentMethod =
716+
await this.paymentMethodRepo.hasVerifiedPaymentMethod(userId);
717+
let fromStatus, toStatus;
718+
if (hasTaxForm && hasPaymentMethod) {
719+
fromStatus = PaymentStatus.ON_HOLD;
720+
toStatus = PaymentStatus.OWED;
721+
} else {
722+
fromStatus = PaymentStatus.OWED;
723+
toStatus = PaymentStatus.ON_HOLD;
724+
}
725+
726+
await this.updateWinningsStatus(userId, fromStatus, toStatus);
667727
}
668728

669729
/**

src/api/api.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { APP_GUARD } from '@nestjs/core';
1212
import { TokenValidatorMiddleware } from 'src/core/auth/middleware/tokenValidator.middleware';
1313
import { AuthGuard, RolesGuard } from 'src/core/auth/guards';
1414
import { TopcoderModule } from 'src/shared/topcoder/topcoder.module';
15+
import { OriginRepository } from './repository/origin.repo';
16+
import { TaxFormRepository } from './repository/taxForm.repo';
17+
import { PaymentMethodRepository } from './repository/paymentMethod.repo';
1518

1619
@Module({
1720
imports: [GlobalProvidersModule, TopcoderModule],
@@ -34,6 +37,9 @@ import { TopcoderModule } from 'src/shared/topcoder/topcoder.module';
3437
AdminWinningService,
3538
WinningService,
3639
WalletService,
40+
OriginRepository,
41+
TaxFormRepository,
42+
PaymentMethodRepository,
3743
],
3844
})
3945
export class ApiModule implements NestModule {

src/api/repository/origin.repo.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { PrismaService } from 'src/shared/global/prisma.service';
3+
4+
@Injectable()
5+
export class OriginRepository {
6+
constructor(private readonly prisma: PrismaService) {}
7+
8+
/**
9+
* Get origin id by name
10+
*
11+
* @param name origin name
12+
* @param tx transaction
13+
*/
14+
async getOriginIdByName(name: string, tx?): Promise<number | null> {
15+
const db = tx || this.prisma;
16+
const originData = await db.origin.findFirst({
17+
where: { origin_name: name },
18+
});
19+
if (!originData) {
20+
return null;
21+
}
22+
return originData.origin_id as number;
23+
}
24+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Injectable } from '@nestjs/common';
2+
import {
3+
PaymentMethodQueryResult,
4+
UserPaymentMethodStatus,
5+
} from 'src/dto/paymentMethod.dto';
6+
import { PrismaService } from 'src/shared/global/prisma.service';
7+
8+
@Injectable()
9+
export class PaymentMethodRepository {
10+
constructor(private readonly prisma: PrismaService) {}
11+
12+
/**
13+
* Check user has verified payment method
14+
*
15+
* @param userId user id
16+
* @param tx transaction
17+
*/
18+
async hasVerifiedPaymentMethod(userId: string, tx?): Promise<boolean> {
19+
const methods = await this.findPaymentMethodByUserId(userId, tx);
20+
for (const method of methods) {
21+
if (
22+
method.status ===
23+
UserPaymentMethodStatus.UserPaymentMethodStatusConnected.toString()
24+
) {
25+
return true;
26+
}
27+
}
28+
return false;
29+
}
30+
31+
/**
32+
* Get user payment methods
33+
*
34+
* @param userId user id
35+
* @param tx transaction
36+
* @returns payment methods
37+
*/
38+
private async findPaymentMethodByUserId(userId: string, tx?) {
39+
const query = `
40+
SELECT pm.payment_method_id, pm.payment_method_type, pm.name, pm.description, upm.status, upm.id
41+
FROM payment_method pm
42+
JOIN user_payment_methods upm ON pm.payment_method_id = upm.payment_method_id
43+
WHERE upm.user_id = '${userId}'
44+
`;
45+
const db = tx || this.prisma;
46+
const ret = await db.$queryRawUnsafe(query);
47+
return (ret || []) as PaymentMethodQueryResult[];
48+
}
49+
}

src/api/repository/taxForm.repo.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { TaxFormStatus } from 'src/dto/adminWinning.dto';
3+
import { TaxFormQueryResult } from 'src/dto/taxForm.dto';
4+
import { PrismaService } from 'src/shared/global/prisma.service';
5+
6+
@Injectable()
7+
export class TaxFormRepository {
8+
constructor(private readonly prisma: PrismaService) {}
9+
10+
/**
11+
* Check user has tax form or not
12+
*
13+
* @param userId user id
14+
* @returns true if user has active tax form
15+
*/
16+
async hasActiveTaxForm(userId: string): Promise<boolean> {
17+
const ret = await this.findTaxFormByUserId(userId);
18+
for (const r of ret) {
19+
if (r.status_id === TaxFormStatus.Active.toString()) {
20+
return true;
21+
}
22+
}
23+
return false;
24+
}
25+
26+
/**
27+
* Find tax forms by user id
28+
*
29+
* @param userId user id
30+
* @param tx transaction
31+
* @returns tax forms
32+
*/
33+
async findTaxFormByUserId(
34+
userId: string,
35+
tx?,
36+
): Promise<TaxFormQueryResult[]> {
37+
const query = `
38+
SELECT u.id, u.user_id, t.tax_form_id, t.name, t.text, t.description, u.date_filed, u.withholding_amount, u.withholding_percentage, u.status_id::text, u.use_percentage
39+
FROM user_tax_form_associations AS u
40+
JOIN tax_forms AS t ON u.tax_form_id = t.tax_form_id
41+
WHERE u.user_id = '${userId}'
42+
`;
43+
const db = tx || this.prisma;
44+
45+
const ret = await db.$queryRawUnsafe(query);
46+
return (ret || []) as TaxFormQueryResult[];
47+
}
48+
}

src/api/winning/winning.controller.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,10 @@ export class WinningController {
8585
@Body() body: WinningRequestDto,
8686
): Promise<ResponseDto<SearchWinningResult>> {
8787
const result = await this.adminWinningService.searchWinnings(body);
88-
if (result.error) {
89-
result.status = ResponseStatusType.ERROR;
90-
}
9188

92-
result.status = ResponseStatusType.SUCCESS;
89+
result.status = result.error
90+
? ResponseStatusType.ERROR
91+
: ResponseStatusType.SUCCESS;
9392

9493
return result;
9594
}

0 commit comments

Comments
 (0)