Skip to content

Commit

Permalink
kyc zod complete
Browse files Browse the repository at this point in the history
  • Loading branch information
TinyMurky committed Sep 12, 2024
1 parent 4d03536 commit e3bd2c5
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 79 deletions.
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.1+9",
"version": "0.8.1+10",
"private": false,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 2 additions & 0 deletions src/constants/zod_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
journalGetByIdValidator,
journalListValidator,
} from '@/lib/utils/zod_schema/journal';
import { kycUploadValidator } from '@/lib/utils/zod_schema/kyc';
import {
ocrDeleteValidator,
ocrListValidator,
Expand Down Expand Up @@ -43,4 +44,5 @@ export const API_ZOD_SCHEMA = {
[APIName.JOURNAL_LIST]: journalListValidator,
[APIName.JOURNAL_GET_BY_ID]: journalGetByIdValidator,
[APIName.JOURNAL_DELETE]: journalDeleteValidator,
[APIName.KYC_UPLOAD]: kycUploadValidator,
};
2 changes: 1 addition & 1 deletion src/interfaces/company_kyc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface ICompanyKYCForm {
[RegistrationInfoKeys.COUNTRY]: CountryOptions;
[RegistrationInfoKeys.LEGAL_STRUCTURE]: LegalStructureOptions;
[RegistrationInfoKeys.BUSINESS_REGISTRATION_NUMBER]: string;
[RegistrationInfoKeys.REGISTRATION_DATE]: string;
[RegistrationInfoKeys.REGISTRATION_DATE]: string; // Info: (20240912 - Murky) Maybe this suppose to be number?
[RegistrationInfoKeys.INDUSTRY]: IndustryOptions;
[ContactInfoKeys.KEY_CONTACT_PERSON]: string;
[ContactInfoKeys.CONTACT_PHONE]: string;
Expand Down
17 changes: 16 additions & 1 deletion src/lib/utils/repo/company_kyc.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,22 @@ export async function createCompanyKYC(
const companyKYC: CompanyKYC = await prisma.companyKYC.create({
data: {
companyId,
...companyKYCForm,
// ...companyKYCForm,
legalName: companyKYCForm.legalName,
city: companyKYCForm.city,
zipCode: companyKYCForm.zipCode,
address: companyKYCForm.address,
representativeName: companyKYCForm.representativeName,
country: companyKYCForm.country,
structure: companyKYCForm.structure,
registrationNumber: companyKYCForm.registrationNumber,
registrationDate: companyKYCForm.registrationDate,
industry: companyKYCForm.industry,
contactPerson: companyKYCForm.contactPerson,
contactPhone: companyKYCForm.contactPhone,
contactEmail: companyKYCForm.contactEmail,
website: companyKYCForm.website,
representativeIdType: companyKYCForm.representativeIdType,
registrationCertificateFileId: companyKYCForm.registrationCertificateFileId,
taxCertificateFileId: companyKYCForm.taxCertificateFileId,
representativeIdCardFileId: companyKYCForm.representativeIdCardFileId,
Expand Down
15 changes: 8 additions & 7 deletions src/lib/utils/report/report_401_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import {
} from '@/interfaces/report';
import { getCompanyKYCByCompanyId } from '@/lib/utils/repo/company_kyc.repo';
import { convertTimestampToROCDate } from '@/lib/utils/common';
import { STATUS_MESSAGE } from '@/constants/status_code';
import { listJournalFor401 } from '@/lib/utils/repo/journal.repo';
import { SPECIAL_ACCOUNTS } from '@/constants/account';
import { IJournalIncludeVoucherLineItemsInvoicePayment } from '@/interfaces/journal';
import { importsCategories, purchasesCategories, salesCategories } from '@/constants/invoice';
import { CompanyKYC } from '@prisma/client';

export default class Report401Generator extends ReportGenerator {
constructor(companyId: number, startDateInSecond: number, endDateInSecond: number) {
Expand Down Expand Up @@ -198,20 +198,21 @@ export default class Report401Generator extends ReportGenerator {
from: number,
to: number
): Promise<TaxReport401> {
const companyKYC = await getCompanyKYCByCompanyId(companyId);
const companyKYC: CompanyKYC | null = await getCompanyKYCByCompanyId(companyId);
if (!companyKYC) {
throw new Error(STATUS_MESSAGE.FORBIDDEN);
// Info: (20240912 - Murky) temporary allow to generate report without KYC
// throw new Error(STATUS_MESSAGE.FORBIDDEN);
}
const ROCStartDate = convertTimestampToROCDate(from);
const ROCEndDate = convertTimestampToROCDate(to);
// 1. 獲取所有發票
const journalList = await listJournalFor401(companyId, from, to);
const basicInfo = {
uniformNumber: companyKYC.registrationNumber,
businessName: companyKYC.legalName,
personInCharge: companyKYC.representativeName,
uniformNumber: companyKYC?.registrationNumber ?? '',
businessName: companyKYC?.legalName ?? '',
personInCharge: companyKYC?.representativeName ?? '',
taxSerialNumber: 'ABC123', // TODO (20240808 - Jacky): Implement this field in next sprint
businessAddress: companyKYC.address,
businessAddress: companyKYC?.address ?? '',
currentYear: ROCStartDate.year.toString(),
startMonth: ROCStartDate.month.toString(),
endMonth: ROCEndDate.month.toString(),
Expand Down
137 changes: 70 additions & 67 deletions src/lib/utils/type_guard/company_kyc.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,83 @@
import {
BasicInfoKeys,
ContactInfoKeys,
CountryOptions,
IndustryOptions,
LegalStructureOptions,
RegistrationInfoKeys,
RepresentativeIDType,
UploadDocumentKeys,
} from '@/constants/kyc';
import { ICompanyKYC, ICompanyKYCForm } from '@/interfaces/company_kyc';
import { getEnumValue } from '@/lib/utils/common';
import { CompanyKYC } from '@prisma/client';
import { iCompanyKYCFormValidator, iCompanyKYCValidator } from '@/lib/utils/zod_schema/kyc';

export function isCompanyKYC(data: CompanyKYC): data is ICompanyKYC {
return (
typeof data.id === 'number' &&
typeof data.companyId === 'number' &&
typeof data.legalName === 'string' &&
Object.values(CountryOptions).includes(data.country as CountryOptions) &&
typeof data.city === 'string' &&
typeof data.address === 'string' &&
typeof data.zipCode === 'string' &&
typeof data.representativeName === 'string' &&
Object.values(LegalStructureOptions).includes(data.structure as LegalStructureOptions) &&
typeof data.registrationNumber === 'string' &&
typeof data.registrationDate === 'string' &&
Object.values(IndustryOptions).includes(data.industry as IndustryOptions) &&
typeof data.contactPerson === 'string' &&
typeof data.contactPhone === 'string' &&
typeof data.contactEmail === 'string' &&
// (typeof data.website === 'string' || data.website === undefined) && Info: (20240719 - Tzuhan) this field should be optional, but db schema is not nullable
typeof data.website === 'string' &&
Object.values(RepresentativeIDType).includes(
data.representativeIdType as RepresentativeIDType
) &&
typeof data.registrationCertificateFileId === 'number' &&
typeof data.taxCertificateFileId === 'number' &&
typeof data.representativeIdCardFileId === 'number' &&
typeof data.createdAt === 'number' &&
typeof data.updatedAt === 'number' &&
(typeof data.deletedAt === 'number' || data.deletedAt === null)
);
export function isCompanyKYC(data: unknown): data is ICompanyKYC {
// Deprecated: (20240912 - Murky) Use zod validator instead
// return (
// typeof data.id === 'number' &&
// typeof data.companyId === 'number' &&
// typeof data.legalName === 'string' &&
// Object.values(CountryOptions).includes(data.country as CountryOptions) &&
// typeof data.city === 'string' &&
// typeof data.address === 'string' &&
// typeof data.zipCode === 'string' &&
// typeof data.representativeName === 'string' &&
// Object.values(LegalStructureOptions).includes(data.structure as LegalStructureOptions) &&
// typeof data.registrationNumber === 'string' &&
// typeof data.registrationDate === 'string' &&
// Object.values(IndustryOptions).includes(data.industry as IndustryOptions) &&
// typeof data.contactPerson === 'string' &&
// typeof data.contactPhone === 'string' &&
// typeof data.contactEmail === 'string' &&
// // (typeof data.website === 'string' || data.website === undefined) && Info: (20240719 - Tzuhan) this field should be optional, but db schema is not nullable
// typeof data.website === 'string' &&
// Object.values(RepresentativeIDType).includes(
// data.representativeIdType as RepresentativeIDType
// ) &&
// typeof data.registrationCertificateFileId === 'number' &&
// typeof data.taxCertificateFileId === 'number' &&
// typeof data.representativeIdCardFileId === 'number' &&
// typeof data.createdAt === 'number' &&
// typeof data.updatedAt === 'number' &&
// (typeof data.deletedAt === 'number' || data.deletedAt === null)
// );

const isValid = iCompanyKYCValidator.safeParse(data);
return isValid.success;
}

export function isCompanyKYCForm(obj: ICompanyKYCForm): obj is ICompanyKYCForm {
const countryEnumValue = getEnumValue(CountryOptions, obj.country);
const structureEnumValue = getEnumValue(LegalStructureOptions, obj.structure);
const industryEnumValue = getEnumValue(IndustryOptions, obj.industry);
const representativeIdTypeEnumValue = getEnumValue(
RepresentativeIDType,
obj.representativeIdType
);
return (
typeof obj === 'object' &&
!!countryEnumValue &&
!!structureEnumValue &&
!!industryEnumValue &&
!!representativeIdTypeEnumValue &&
typeof obj[BasicInfoKeys.LEGAL_COMPANY_NAME] === 'string' &&
typeof obj[BasicInfoKeys.CITY] === 'string' &&
typeof obj[BasicInfoKeys.ZIP_CODE] === 'string' &&
typeof obj[BasicInfoKeys.ADDRESS] === 'string' &&
typeof obj[BasicInfoKeys.KEY_COMPANY_REPRESENTATIVES_NAME] === 'string' &&
typeof obj[RegistrationInfoKeys.LEGAL_STRUCTURE] === 'string' &&
typeof obj[RegistrationInfoKeys.BUSINESS_REGISTRATION_NUMBER] === 'string' &&
typeof obj[RegistrationInfoKeys.REGISTRATION_DATE] === 'string' &&
typeof obj[RegistrationInfoKeys.INDUSTRY] === 'string' &&
typeof obj[ContactInfoKeys.KEY_CONTACT_PERSON] === 'string' &&
typeof obj[ContactInfoKeys.CONTACT_PHONE] === 'string' &&
typeof obj[ContactInfoKeys.EMAIL_ADDRESS] === 'string' &&
typeof obj[ContactInfoKeys.COMPANY_WEBSITE] === 'string' &&
typeof obj[UploadDocumentKeys.REPRESENTATIVE_ID_TYPE] === 'string' &&
typeof obj.registrationCertificateFileId === 'number' &&
typeof obj.taxCertificateFileId === 'number' &&
typeof obj.representativeIdCardFileId === 'number'
);
export function isCompanyKYCForm(obj: unknown): obj is ICompanyKYCForm {
// Deprecated: (20240912 - Murky) Use zod validator instead
// const countryEnumValue = getEnumValue(CountryOptions, obj.country);
// const structureEnumValue = getEnumValue(LegalStructureOptions, obj.structure);
// const industryEnumValue = getEnumValue(IndustryOptions, obj.industry);
// const representativeIdTypeEnumValue = getEnumValue(
// RepresentativeIDType,
// obj.representativeIdType
// );
// return (
// typeof obj === 'object' &&
// !!countryEnumValue &&
// !!structureEnumValue &&
// !!industryEnumValue &&
// !!representativeIdTypeEnumValue &&
// typeof obj[BasicInfoKeys.LEGAL_COMPANY_NAME] === 'string' &&
// typeof obj[BasicInfoKeys.CITY] === 'string' &&
// typeof obj[BasicInfoKeys.ZIP_CODE] === 'string' &&
// typeof obj[BasicInfoKeys.ADDRESS] === 'string' &&
// typeof obj[BasicInfoKeys.KEY_COMPANY_REPRESENTATIVES_NAME] === 'string' &&
// typeof obj[RegistrationInfoKeys.LEGAL_STRUCTURE] === 'string' &&
// typeof obj[RegistrationInfoKeys.BUSINESS_REGISTRATION_NUMBER] === 'string' &&
// typeof obj[RegistrationInfoKeys.REGISTRATION_DATE] === 'string' &&
// typeof obj[RegistrationInfoKeys.INDUSTRY] === 'string' &&
// typeof obj[ContactInfoKeys.KEY_CONTACT_PERSON] === 'string' &&
// typeof obj[ContactInfoKeys.CONTACT_PHONE] === 'string' &&
// typeof obj[ContactInfoKeys.EMAIL_ADDRESS] === 'string' &&
// typeof obj[ContactInfoKeys.COMPANY_WEBSITE] === 'string' &&
// typeof obj[UploadDocumentKeys.REPRESENTATIVE_ID_TYPE] === 'string' &&
// typeof obj.registrationCertificateFileId === 'number' &&
// typeof obj.taxCertificateFileId === 'number' &&
// typeof obj.representativeIdCardFileId === 'number'
// );

const isValid = iCompanyKYCFormValidator.safeParse(obj);
return isValid.success;
}

export function isKYCFormComplete(data: ICompanyKYCForm): {
Expand Down
76 changes: 76 additions & 0 deletions src/lib/utils/zod_schema/kyc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
BasicInfoKeys,
ContactInfoKeys,
CountryOptions,
IndustryOptions,
LegalStructureOptions,
RegistrationInfoKeys,
RepresentativeIDType,
UploadDocumentKeys,
} from '@/constants/kyc';
import { z } from 'zod';
import { IZodValidator } from '@/interfaces/zod_validator';
import { zodTimestampInSeconds } from './common';

export const iCompanyKYCFormValidator = z.object({
[BasicInfoKeys.LEGAL_COMPANY_NAME]: z.string(),
[BasicInfoKeys.CITY]: z.string(),
[BasicInfoKeys.ZIP_CODE]: z.string(),
[BasicInfoKeys.ADDRESS]: z.string(),
[BasicInfoKeys.KEY_COMPANY_REPRESENTATIVES_NAME]: z.string(),
[RegistrationInfoKeys.COUNTRY]: z.nativeEnum(CountryOptions),
[RegistrationInfoKeys.LEGAL_STRUCTURE]: z.nativeEnum(LegalStructureOptions),
[RegistrationInfoKeys.BUSINESS_REGISTRATION_NUMBER]: z.string(),
[RegistrationInfoKeys.REGISTRATION_DATE]: zodTimestampInSeconds(false).transform(String), // Info: (20240912 - Murky) Date in second but store in string
[RegistrationInfoKeys.INDUSTRY]: z.nativeEnum(IndustryOptions),
[ContactInfoKeys.KEY_CONTACT_PERSON]: z.string(),
[ContactInfoKeys.CONTACT_PHONE]: z.string(),
[ContactInfoKeys.EMAIL_ADDRESS]: z.string().email(),
[ContactInfoKeys.COMPANY_WEBSITE]: z.string().optional(),
[UploadDocumentKeys.REPRESENTATIVE_ID_TYPE]: z.nativeEnum(RepresentativeIDType),
[UploadDocumentKeys.BUSINESS_REGISTRATION_CERTIFICATE_ID]: z.number(),
[UploadDocumentKeys.TAX_STATUS_CERTIFICATE_ID]: z.number(),
[UploadDocumentKeys.REPRESENTATIVE_CERTIFICATE_ID]: z.number(),
});

const kycUploadQueryValidator = z.object({});

const kycUploadBodyValidator = iCompanyKYCFormValidator;

export const kycUploadValidator: IZodValidator<
(typeof kycUploadQueryValidator)['shape'],
(typeof kycUploadBodyValidator)['shape']
> = {
query: kycUploadQueryValidator,
body: kycUploadBodyValidator,
};

export const iCompanyKYCValidator = z.object({
id: z.number(),
companyId: z.number(),
legalName: z.string(),
country: z.nativeEnum(CountryOptions),
city: z.string(),
address: z.string(),
zipCode: z.string(),
representativeName: z.string(),
structure: z.nativeEnum(LegalStructureOptions),
registrationNumber: z.string(),
registrationDate: z.string(),
industry: z.nativeEnum(IndustryOptions),
contactPerson: z.string(),
contactPhone: z.string(),
contactEmail: z.string().email(),
website: z.string(),
representativeIdType: z.nativeEnum(RepresentativeIDType),
registrationCertificateFileId: z.number(),
taxCertificateFileId: z.number(),
representativeIdCardFileId: z.number(),
status: z.string(),
reviewer: z.string(),
note: z.string(),
reviewAt: z.number(),
createdAt: z.number(),
updatedAt: z.number(),
deletedAt: z.number().nullable(),
});
14 changes: 12 additions & 2 deletions src/pages/api/v1/company/[companyId]/kyc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { NextApiRequest, NextApiResponse } from 'next';
import { IResponseData } from '@/interfaces/response_data';
import { formatApiResponse } from '@/lib/utils/common';
import { STATUS_MESSAGE } from '@/constants/status_code';
import { ICompanyKYC, ICompanyKYCForm } from '@/interfaces/company_kyc';
import { ICompanyKYC } from '@/interfaces/company_kyc';
import { getSession } from '@/lib/utils/session';
import { checkAuthorization } from '@/lib/utils/auth_check';
import { AuthFunctionsKeys } from '@/interfaces/auth';
import { createCompanyKYC } from '@/lib/utils/repo/company_kyc.repo';
import { isCompanyKYC, isCompanyKYCForm } from '@/lib/utils/type_guard/company_kyc';
import { validateRequest } from '@/lib/utils/request_validator';
import { APIName } from '@/constants/api_connection';
import loggerBack, { loggerError } from '@/lib/utils/logger_back';

async function handlePostRequest(
req: NextApiRequest,
Expand All @@ -26,7 +29,8 @@ async function handlePostRequest(
if (!isAuth) {
statusMessage = STATUS_MESSAGE.FORBIDDEN;
} else {
const companyKYCForm: ICompanyKYCForm = req.body;
const { body: companyKYCForm } = validateRequest(APIName.KYC_UPLOAD, req, userId);
loggerBack.info({ userId, companyId, companyKYCForm });
if (!isCompanyKYCForm(companyKYCForm)) {
statusMessage = STATUS_MESSAGE.INVALID_INPUT_PARAMETER;
} else {
Expand All @@ -39,6 +43,12 @@ async function handlePostRequest(
statusMessage = STATUS_MESSAGE.CREATED;
}
} catch (error) {
const logger = loggerError(
userId,
'post /api/v1/company/[companyId]/kyc',
'Failed to create company KYC'
);
logger.error(error);
statusMessage = STATUS_MESSAGE.INTERNAL_SERVICE_ERROR;
}
}
Expand Down

0 comments on commit e3bd2c5

Please sign in to comment.