Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add balance check to post and put voucher #3773

Merged
merged 6 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "iSunFA",
"version": "0.8.5+240",
"version": "0.8.5+241",
"private": false,
"scripts": {
"dev": "next dev",
Expand Down
3 changes: 3 additions & 0 deletions src/constants/status_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum ErrorCode {
UNSUPPORTED_FILE_TYPE = '400ISF0006',
INVALID_TIMESTAMP = '400ISF0007',
INVALID_TIMEZONE_FORMAT = '400ISF0008',
UNBALANCED_DEBIT_CREDIT = '400ISF0009',
UNAUTHORIZED_ACCESS = '401ISF0000',
FORBIDDEN = '403ISF0000',
RESOURCE_NOT_FOUND = '404ISF0000',
Expand Down Expand Up @@ -79,6 +80,7 @@ enum ErrorMessage {
UNSUPPORTED_FILE_TYPE = 'Unsupported file type',
INVALID_TIMESTAMP = 'Invalid timestamp',
INVALID_TIMEZONE_FORMAT = 'Invalid timezone format, should be +HHMM or -HHMM',
UNBALANCED_DEBIT_CREDIT = 'Debit Amount Must Equal Credit Amount',
UNAUTHORIZED_ACCESS = 'Unauthorized access',
RESOURCE_NOT_FOUND = 'Resource not found',
FORBIDDEN = 'Forbidden',
Expand Down Expand Up @@ -141,6 +143,7 @@ export const STATUS_CODE = {
[STATUS_MESSAGE.UNSUPPORTED_FILE_TYPE]: ErrorCode.UNSUPPORTED_FILE_TYPE,
[STATUS_MESSAGE.INVALID_TIMESTAMP]: ErrorCode.INVALID_TIMESTAMP,
[STATUS_MESSAGE.INVALID_TIMEZONE_FORMAT]: ErrorCode.INVALID_TIMEZONE_FORMAT,
[STATUS_MESSAGE.UNBALANCED_DEBIT_CREDIT]: ErrorCode.UNBALANCED_DEBIT_CREDIT,
[STATUS_MESSAGE.UNAUTHORIZED_ACCESS]: ErrorCode.UNAUTHORIZED_ACCESS,
[STATUS_MESSAGE.FORBIDDEN]: ErrorCode.FORBIDDEN,
[STATUS_MESSAGE.RESOURCE_NOT_FOUND]: ErrorCode.RESOURCE_NOT_FOUND,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { FileFolder } from '@/constants/file';
import { KYCFiles, UploadDocumentKeys } from '@/constants/kyc';
import { ROCDate } from '@/interfaces/locale';

export function isFloatsEqual(a: number, b: number, tolerance = Number.EPSILON): boolean {
return Math.abs(a - b) < tolerance;
}

export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs));
};
Expand Down
19 changes: 14 additions & 5 deletions src/lib/utils/zod_schema/voucher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,7 @@ const voucherGetAllQueryValidatorV2 = z.object({
page: zodStringToNumberWithDefault(DEFAULT_PAGE_START_AT),
pageSize: zodStringToNumberWithDefault(DEFAULT_PAGE_LIMIT),
type: z.preprocess((input) => {
const result = (typeof input === 'string' && input.toLowerCase() === 'all') ?
undefined :
input;
const result = typeof input === 'string' && input.toLowerCase() === 'all' ? undefined : input;
return result;
}, z.nativeEnum(EventType).optional()),
tab: z.nativeEnum(VoucherListTabV2),
Expand Down Expand Up @@ -386,7 +384,10 @@ const voucherPostBodyValidatorV2 = z.object({
}),
});

const voucherPostOutputValidatorV2 = voucherEntityValidator.transform((data) => {
const voucherPostOutputValidatorV2 = voucherEntityValidator.nullable().transform((data) => {
if (data === null) {
return null;
}
return data.id;
});
const voucherPostFrontendValidatorV2 = z.number();
Expand Down Expand Up @@ -811,7 +812,15 @@ export const voucherPutSchema = {
querySchema: voucherPutQueryValidatorV2,
bodySchema: voucherPostBodyValidatorV2,
},
outputSchema: z.number(),
outputSchema: z
.number()
.nullable()
.transform((data) => {
if (data === null) {
return null;
}
return data;
}),
frontend: voucherNullSchema,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ export const handlePutRequest: IHandleRequest<APIName.VOUCHER_PUT_V2, number> =
});
}

// Info: (20241226 - Murky) Check if lineItems credit and debit are equal
const isLineItemsBalanced = postUtils.isLineItemsBalanced(lineItems);
if (!isLineItemsBalanced) {
postUtils.throwErrorAndLog(loggerBack, {
errorMessage: 'lineItems credit and debit should be equal',
statusMessage: STATUS_MESSAGE.UNBALANCED_DEBIT_CREDIT,
});
}

if (!isVoucherInfoExist) {
putUtils.throwErrorAndLog(loggerBack, {
errorMessage: 'voucherInfo is required when post voucher',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { getImageUrlFromFileIdV1 } from '@/lib/utils/file';
import { initInvoiceEntity } from '@/lib/utils/invoice';
import { InvoiceTaxType, InvoiceTransactionDirection, InvoiceType } from '@/constants/invoice';
import { CurrencyType } from '@/constants/currency';
import { isFloatsEqual } from '@/lib/utils/common';

export const voucherAPIGetOneUtils = {
/**
Expand Down Expand Up @@ -416,6 +417,23 @@ export const voucherAPIGetOneUtils = {
// return undefined;
// },

isLineItemsBalanced: (
lineItems: {
amount: number;
debit: boolean;
}[]
) => {
let debit = 0;
let credit = 0;
lineItems.forEach((lineItem) => {
if (lineItem.debit) {
debit += lineItem.amount;
} else {
credit += lineItem.amount;
}
});
return isFloatsEqual(debit, credit);
},
setLineItemIntoMap: (lineItems: ILineItemEntity, map: Map<string, ILineItemEntity>) => {
if (!lineItems.account) return;

Expand Down
12 changes: 11 additions & 1 deletion src/pages/api/v2/company/[companyId]/voucher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export const handlePostRequest: IHandleRequest<APIName.VOUCHER_POST_V2, IVoucher
});
}
}

// Info: (20241025 - Murky) Early throw error if lineItems is empty and voucherInfo is empty
if (!isLineItemsHasItems) {
postUtils.throwErrorAndLog(loggerBack, {
Expand All @@ -271,6 +272,15 @@ export const handlePostRequest: IHandleRequest<APIName.VOUCHER_POST_V2, IVoucher
});
}

// Info: (20241226 - Murky) Check if lineItems credit and debit are equal
const isLineItemsBalanced = postUtils.isLineItemsBalanced(lineItems);
if (!isLineItemsBalanced) {
postUtils.throwErrorAndLog(loggerBack, {
errorMessage: 'lineItems credit and debit should be equal',
statusMessage: STATUS_MESSAGE.UNBALANCED_DEBIT_CREDIT,
});
}

if (!isVoucherInfoExist) {
postUtils.throwErrorAndLog(loggerBack, {
errorMessage: 'voucherInfo is required when post voucher',
Expand Down Expand Up @@ -458,13 +468,13 @@ export const handlePostRequest: IHandleRequest<APIName.VOUCHER_POST_V2, IVoucher

// Info: (20241111 - Murky) Output formatter 只要回傳新的voucherId就可以了
payload = parsePrismaVoucherToVoucherEntity(createdVoucher);
statusMessage = STATUS_MESSAGE.CREATED;
} catch (_error) {
const error = _error as Error;
statusMessage = error.message;
loggerBack.error(error);
}

statusMessage = STATUS_MESSAGE.CREATED;
return {
statusMessage,
payload,
Expand Down
18 changes: 18 additions & 0 deletions src/pages/api/v2/company/[companyId]/voucher/route_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { parsePrismaAssetToAssetEntity } from '@/lib/utils/formatter/asset.forma
import {
getDaysBetweenDates,
getLastDatesOfMonthsBetweenDates,
isFloatsEqual,
timestampInSeconds,
} from '@/lib/utils/common';
import { PUBLIC_COUNTER_PARTY } from '@/constants/counterparty';
Expand Down Expand Up @@ -393,6 +394,23 @@ export const voucherAPIPostUtils = {
return !!asset;
},

isLineItemsBalanced: (
lineItems: {
amount: number;
debit: boolean;
}[]
) => {
let debit = 0;
let credit = 0;
lineItems.forEach((lineItem) => {
if (lineItem.debit) {
debit += lineItem.amount;
} else {
credit += lineItem.amount;
}
});
return isFloatsEqual(debit, credit);
},
/**
* Info: (20241025 - Murky)
* @describe convert voucherId to IVoucherEntity
Expand Down
Loading