Skip to content

Commit

Permalink
Merge branch 'dev' into bal-3173
Browse files Browse the repository at this point in the history
  • Loading branch information
tomer-shvadron authored Jan 21, 2025
2 parents b22d99e + 5f128bb commit 60f0be9
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { TNoteableType } from '@/domains/notes/types';

export const useCreateNoteMutation = ({
onSuccess,
disableToast = false,
}: {
onSuccess?: <TData>(data: TData) => void;
disableToast: boolean;
}) => {
const queryClient = useQueryClient();

Expand Down Expand Up @@ -41,7 +43,9 @@ export const useCreateNoteMutation = ({
onSuccess: data => {
void queryClient.invalidateQueries();

toast.success(t(`toast:note_created.success`));
if (!disableToast) {
toast.success(t(`toast:note_created.success`));
}

onSuccess?.(data);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ export const MerchantMonitoringBusinessReport: FunctionComponent = () => {
</FormControl>
<FormMessage />
<SelectContent>
{deboardingReasonOptions?.map(({ label, value }, index) => {
{deboardingReasonOptions?.map((option, index) => {
return (
<SelectItem key={index} value={value}>
{label}
<SelectItem key={index} value={option}>
{option}
</SelectItem>
);
})}
Expand Down Expand Up @@ -223,15 +223,12 @@ export const MerchantMonitoringBusinessReport: FunctionComponent = () => {
throw new Error('Merchant ID is missing');
}

turnOngoingMonitoringOn(
{ merchantId: businessReport.merchantId },
{
onSuccess: () => {
setIsDeboardModalOpen(false);
setIsDropdownOpen(false);
},
turnOngoingMonitoringOn(businessReport.merchantId, {
onSuccess: () => {
setIsDeboardModalOpen(false);
setIsDropdownOpen(false);
},
);
});
}}
variant={'ghost'}
className="justify-start"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { handleZodError } from '@/common/utils/handle-zod-error/handle-zod-error
export type TurnOngoingMonitoringBody = z.infer<typeof TurnOngoingMonitoringBodySchema>;
export const TurnOngoingMonitoringBodySchema = z.object({
state: z.string(),
reason: z.string().optional(),
userReason: z.string().optional(),
});

export type TurnOngoingMonitoringResponse = z.infer<typeof TurnOngoingMonitoringResponseSchema>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ParsedBooleanSchema, useReportTabs } from '@ballerine/ui';
import { t } from 'i18next';
import { capitalize } from 'lodash-es';
import { useCallback, useMemo } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
Expand All @@ -15,6 +16,7 @@ import {
MERCHANT_REPORT_TYPES_MAP,
} from '@/domains/business-reports/constants';
import { useBusinessReportByIdQuery } from '@/domains/business-reports/hooks/queries/useBusinessReportByIdQuery/useBusinessReportByIdQuery';
import { useCreateNoteMutation } from '@/domains/notes/hooks/mutations/useCreateNoteMutation/useCreateNoteMutation';
import { useNotesByNoteable } from '@/domains/notes/hooks/queries/useNotesByNoteable/useNotesByNoteable';
import { useToggleMonitoringMutation } from '@/pages/MerchantMonitoringBusinessReport/hooks/useToggleMonitoringMutation/useToggleMonitoringMutation';
import { isObject } from '@ballerine/common';
Expand Down Expand Up @@ -56,11 +58,11 @@ const statusToBadgeData = {
} as const;

const deboardingReasonOptions = [
{ value: 'fraud', label: 'Fraudulent Activity Detected' },
{ value: 'regulations', label: 'Non-Compliance with Regulations' },
{ value: 'disputes', label: 'Excessive Chargebacks or Disputes' },
{ value: 'expired', label: 'Business Relationship Ended' },
{ value: 'other', label: 'Other' },
'Fraudulent Activity Detected',
'Non-Compliance with Regulations',
'Excessive Chargebacks or Disputes',
'Business Relationship Ended',
'Other',
] as const;

export const useMerchantMonitoringBusinessReportLogic = () => {
Expand Down Expand Up @@ -92,12 +94,21 @@ export const useMerchantMonitoringBusinessReportLogic = () => {
throw new Error('Merchant ID is missing');
}

return turnOffMonitoringMutation.mutate({ merchantId: businessReport.merchantId, body: data });
return turnOffMonitoringMutation.mutate(businessReport.merchantId);
};

const { mutateAsync: mutateCreateNote } = useCreateNoteMutation({ disableToast: true });
const turnOnMonitoringMutation = useToggleMonitoringMutation({
state: 'on',
onSuccess: () => {
void mutateCreateNote({
content: 'Monitoring turned on',
entityId: businessReport?.merchantId ?? '',
entityType: 'Business',
noteableId: businessReport?.id ?? '',
noteableType: 'Report',
parentNoteId: null,
});
toast.success(t(`toast:business_monitoring_on.success`));
},
onError: error => {
Expand All @@ -112,6 +123,22 @@ export const useMerchantMonitoringBusinessReportLogic = () => {
const turnOffMonitoringMutation = useToggleMonitoringMutation({
state: 'off',
onSuccess: () => {
const { reason, userReason } = form.getValues();
const content = [
'Monitoring turned off',
reason ? `with reason: ${capitalize(reason)}` : null,
userReason ? `(${userReason})` : '',
]
.filter(Boolean)
.join(' ');
void mutateCreateNote({
content,
entityId: businessReport?.merchantId ?? '',
entityType: 'Business',
noteableId: businessReport?.id ?? '',
noteableType: 'Report',
parentNoteId: null,
});
setIsDeboardModalOpen(false);
setIsDropdownOpen(false);
form.reset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';

import { HttpError } from '@/common/errors/http-error';
import {
turnOngoingMonitoring,
TurnOngoingMonitoringBody,
} from '@/pages/MerchantMonitoringBusinessReport/fetchers';
import { turnOngoingMonitoring } from '@/pages/MerchantMonitoringBusinessReport/fetchers';

export const useToggleMonitoringMutation = ({
state,
Expand All @@ -19,10 +16,8 @@ export const useToggleMonitoringMutation = ({
const queryClient = useQueryClient();

return useMutation({
mutationFn: async (data: {
merchantId: string;
body?: Omit<TurnOngoingMonitoringBody, 'state'>;
}) => turnOngoingMonitoring({ merchantId: data.merchantId, body: { ...data.body, state } }),
mutationFn: async (merchantId: string) =>
turnOngoingMonitoring({ merchantId, body: { state } }),
onSuccess: data => {
void queryClient.invalidateQueries();

Expand Down
18 changes: 18 additions & 0 deletions services/workflows-service/src/alert/alert.service.intg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ const createFutureDate = (daysToAdd: number) => {
return futureDate;
};

const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;

describe('AlertService', () => {
let prismaService: PrismaService;
let alertService: AlertService;
Expand Down Expand Up @@ -2398,6 +2400,22 @@ describe('AlertService', () => {
expect(alerts[0]?.alertDefinitionId).toEqual(alertDefinition.id);
expect(alerts[0]?.counterpartyId).toEqual(null);
expect(alerts[0]?.counterpartyBeneficiaryId).toEqual(counterparty.id);
expect(alerts[0]?.executionDetails).toMatchObject({
filters: {
counterpartyBeneficiaryId: counterparty.id,
paymentMethod: {
in: ['credit_card'],
},
projectId: project.id,
transactionDate: {
gte: expect.stringMatching(isoPattern),
},
transactionDirection: 'inbound',
},
subject: {
counterpartyBeneficiaryId: counterparty.id,
},
});
});

it(`Shouldnt create alert for non credit card transaction`, async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ export class BusinessControllerExternal {
featureConfig: {
[FEATURE_LIST.ONGOING_MERCHANT_REPORT]: {
enabled: isEnabled,
reason: data.reason ?? null,
userReason: data.userReason ?? null,
disabledAt: isEnabled ? null : new Date().getTime(),
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsIn, IsOptional, IsString } from 'class-validator';
import { IsIn, IsString } from 'class-validator';

export class BusinessMonitoringPatchDto {
@ApiProperty({ type: String, required: true })
@IsString()
@IsIn(['on', 'off'])
state!: 'on' | 'off';

@ApiProperty({ type: String, required: false })
@IsOptional()
@IsString()
reason?: string;

@ApiProperty({ type: String, required: false })
@IsOptional()
@IsString()
userReason?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export class DataAnalyticsService {
);
}

if (paymentMethods.length) {
if (!isEmpty(paymentMethods)) {
const methodCondition = excludePaymentMethods ? `NOT IN` : `IN`;

conditions.push(
Expand Down Expand Up @@ -487,7 +487,7 @@ export class DataAnalyticsService {
Prisma.sql`"tr"."transactionDate" <= NOW()`,
];

if (Array.isArray(paymentMethods.length)) {
if (!isEmpty(paymentMethods)) {
conditions.push(Prisma.sql`"paymentMethod" IN (${Prisma.join([...paymentMethods])})`);
}

Expand Down Expand Up @@ -813,7 +813,7 @@ AND a.activeDaysTransactions > ((a.lastTransactionsCount - a.activeDaysTransacti
conditions.push(Prisma.sql`"transactionDirection"::text = ${direction}`);
}

if (paymentMethods.length) {
if (!isEmpty(paymentMethods)) {
const methodCondition = excludePaymentMethods ? `NOT IN` : `IN`;

conditions.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
TransactionsAgainstDynamicRulesType,
} from './types';
import type { AlertService } from '@/alert/alert.service';
import { isEmpty } from 'lodash';

@Injectable()
export class DataInvestigationService {
Expand Down Expand Up @@ -143,7 +144,7 @@ export class DataInvestigationService {
amountBetween,
direction,
transactionType,
paymentMethods = [],
paymentMethods,
excludePaymentMethods = false,
projectId,
amountThreshold,
Expand All @@ -167,18 +168,22 @@ export class DataInvestigationService {
}
: {}),
...(direction ? { transactionDirection: direction } : {}),
...(transactionType
...(!isEmpty(transactionType)
? {
transactionType: {
in: transactionType as TransactionRecordType[],
},
}
: {}),
paymentMethod: {
...(excludePaymentMethods
? { notIn: paymentMethods as PaymentMethod[] }
: { in: paymentMethods as PaymentMethod[] }),
},
...(!isEmpty(paymentMethods)
? {
paymentMethod: {
...(excludePaymentMethods
? { notIn: paymentMethods as PaymentMethod[] }
: { in: paymentMethods as PaymentMethod[] }),
},
}
: {}),
} as const satisfies Prisma.TransactionRecordWhereInput;
}

Expand All @@ -204,7 +209,7 @@ export class DataInvestigationService {
}

investigateCustomersTransactionType(options: TCustomersTransactionTypeOptions) {
const { projectId, transactionType = [], paymentMethods = [] } = options;
const { projectId, transactionType, paymentMethods } = options;

return {
projectId,
Expand All @@ -215,9 +220,13 @@ export class DataInvestigationService {
},
}
: {}),
transactionType: {
in: transactionType as TransactionRecordType[],
},
...(!isEmpty(transactionType)
? {
transactionType: {
in: transactionType as TransactionRecordType[],
},
}
: {}),
} as const satisfies Prisma.TransactionRecordWhereInput;
}

Expand Down Expand Up @@ -269,32 +278,36 @@ export class DataInvestigationService {
paymentMethods,
excludePaymentMethods,

transactionType = [],
transactionType,
} = options;

return {
projectId,
...(amountThreshold
...(direction ? { transactionDirection: direction } : {}),
...(!isEmpty(transactionType)
? {
transactionBaseAmount: {
gte: amountThreshold,
transactionType: {
in: transactionType as TransactionRecordType[],
},
}
: {}),
...(direction ? { transactionDirection: direction } : {}),
...(transactionType

...(!isEmpty(paymentMethods)
? {
transactionType: {
in: transactionType as TransactionRecordType[],
paymentMethod: {
...(excludePaymentMethods
? { notIn: paymentMethods as PaymentMethod[] }
: { in: paymentMethods as PaymentMethod[] }),
},
}
: {}),
...(ruleType === 'amount' && amountThreshold
? {
transactionBaseAmount: {
gte: amountThreshold,
},
}
: {}),
paymentMethod: {
...(excludePaymentMethods
? { notIn: paymentMethods as PaymentMethod[] }
: { in: paymentMethods as PaymentMethod[] }),
},
...(ruleType === 'amount' ? { transactionBaseAmount: amountThreshold } : {}),
} as const satisfies Prisma.TransactionRecordWhereInput;
}

Expand Down

0 comments on commit 60f0be9

Please sign in to comment.