UNPKG

@mbc-cqrs-serverless/sequence

Version:

Generate increment sequence with time-rotation

339 lines 14.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var SequencesService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.SequencesService = void 0; const core_1 = require("@mbc-cqrs-serverless/core"); const common_1 = require("@nestjs/common"); const sequence_entity_1 = require("./entities/sequence.entity"); const rotate_by_enum_1 = require("./enums/rotate-by.enum"); const sequence_master_factory_1 = require("./sequence-master-factory"); let SequencesService = SequencesService_1 = class SequencesService { constructor(dynamoDbService, masterDataProvider) { this.dynamoDbService = dynamoDbService; this.masterDataProvider = masterDataProvider; this.logger = new common_1.Logger(SequencesService_1.name); this.tableName = dynamoDbService.getTableName('sequences'); this.logger.debug('tableName: ' + this.tableName); } /** * @deprecated This method is deprecated at V0.2. */ async getCurrentSequence(key) { return await this.dynamoDbService.getItem(this.tableName, key); } /** * @deprecated This method is deprecated at V0.2. * Seq data structure * - pk: SEQ#tenantCode * - sk: typeCode#rotateValue ( e.x: `user#20230401` ) * - code: typeCode#rotateValue * - name: rotateBy ( e.x: `daily` ) * - tenant_code: tenantCode * - type: typeCode * - seq: sequence value ( atomic counter ) */ async genNewSequence(dto, options) { const rotateVal = this.getRotateValue(dto.rotateBy, dto.date); const pk = `SEQ${core_1.KEY_SEPARATOR}${dto.tenantCode}`; const sk = `${dto.typeCode}${core_1.KEY_SEPARATOR}${rotateVal}`; const sourceIp = options?.invokeContext?.event?.requestContext?.http?.sourceIp; const userContext = (0, core_1.getUserContext)(options.invokeContext); const userId = userContext.userId || 'system'; const now = new Date(); const item = await this.dynamoDbService.updateItem(this.tableName, { pk, sk }, { set: { code: sk, name: dto.rotateBy || 'none', tenantCode: dto.tenantCode, type: dto.typeCode, seq: { ifNotExists: 0, incrementBy: 1 }, requestId: options.invokeContext?.context?.awsRequestId, createdAt: { ifNotExists: now }, createdBy: { ifNotExists: userId }, createdIp: { ifNotExists: sourceIp }, updatedAt: now, updatedBy: userId, updatedIp: sourceIp, }, }); return item; } /** * Seq data structure * - pk: SEQ#tenantCode * - sk: typeCode#code1#code2#code3#code4#code5rotateValue ( e.x: `user#20230401` ) * - code: typeCode#rotateValue * - name: rotateBy ( e.x: `daily` ) * - tenant_code: tenantCode * - type: typeCode * - seq: sequence value ( atomic counter ) * - requestId: requestId * - createdAt: createdAt * - createdBy: createdBy * - createdIp: createdIp * - attributes: { * formatted_no: formattedNo ( e.x: `2023-04-01-0001` ) * fiscal_year: fiscalYear * issued_at: issuedAt * } */ async generateSequenceItem(dto, options) { const { date, rotateBy, tenantCode, params, typeCode, prefix, postfix } = dto; const generalMasterPk = (0, core_1.masterPk)(tenantCode); const generalMasterSk = `MASTER_DATA${core_1.KEY_SEPARATOR}${typeCode}`; this.logger.debug('general master pk: ', generalMasterPk); this.logger.debug('general master sk: ', generalMasterSk); const masterData = await this.masterDataProvider.getData({ pk: generalMasterPk, sk: generalMasterSk, }); // Get master data for the tenant const { format, registerDate, startMonth } = masterData; const pk = (0, core_1.seqPk)(tenantCode); // Construct the sort key for the sequence let sk = [ typeCode, params?.code1, params?.code2, params?.code3, params?.code4, params?.code5, ] .filter(Boolean) .join(core_1.KEY_SEPARATOR); const now = new Date(); const issuedAt = (0, core_1.toISOStringWithTimezone)(date || now); const nowFiscalYear = this.getFiscalYear({ now: date || now, registerTime: registerDate ? new Date(registerDate) : undefined, startMonth, }); const sourceIp = options?.invokeContext?.event?.requestContext?.http?.sourceIp; const userContext = options ? (0, core_1.getUserContext)(options.invokeContext) : undefined; const userId = userContext?.userId || 'system'; const rotateVal = this.getRotateValue(rotateBy, date); sk = `${sk}${core_1.KEY_SEPARATOR}${rotateVal}`; const item = await this.dynamoDbService.updateItem(this.tableName, { pk, sk }, { set: { code: sk, name: dto.rotateBy || 'none', tenantCode: dto.tenantCode, type: typeCode, seq: { ifNotExists: 0, incrementBy: 1 }, requestId: options?.invokeContext?.context?.awsRequestId, createdAt: { ifNotExists: now }, createdBy: { ifNotExists: userId }, createdIp: { ifNotExists: sourceIp }, updatedAt: now, updatedBy: userId, updatedIp: sourceIp, }, }); const formatDict = this.createFormatDict(nowFiscalYear, item.seq, date || now, { ...params }); const formatted = this.createFormattedNo(format, formatDict); const formattedNo = `${prefix ?? ''}${formatted}${postfix ?? ''}`; return new sequence_entity_1.SequenceEntity({ id: (0, core_1.generateId)(item.pk, item.sk), no: item.seq, formattedNo, issuedAt: new Date(issuedAt), }); } async generateSequenceItemWithProvideSetting(dto, options) { const { date, rotateBy, tenantCode, params, typeCode, prefix, postfix, format, registerDate, startMonth, } = dto; const pk = (0, core_1.seqPk)(tenantCode); // Construct the sort key for the sequence let sk = [ typeCode, params?.code1, params?.code2, params?.code3, params?.code4, params?.code5, ] .filter(Boolean) .join(core_1.KEY_SEPARATOR); const now = new Date(); const issuedAt = (0, core_1.toISOStringWithTimezone)(date || now); const nowFiscalYear = this.getFiscalYear({ now: date || now, registerTime: registerDate ? new Date(registerDate) : undefined, startMonth, }); const sourceIp = options?.invokeContext?.event?.requestContext?.http?.sourceIp; const userContext = options ? (0, core_1.getUserContext)(options.invokeContext) : undefined; const userId = userContext?.userId || 'system'; const rotateVal = this.getRotateValue(rotateBy, date); sk = `${sk}${core_1.KEY_SEPARATOR}${rotateVal}`; const item = await this.dynamoDbService.updateItem(this.tableName, { pk, sk }, { set: { code: sk, name: dto.rotateBy || 'none', tenantCode: dto.tenantCode, type: typeCode, seq: { ifNotExists: 0, incrementBy: 1 }, requestId: options?.invokeContext?.context?.awsRequestId, createdAt: { ifNotExists: now }, createdBy: { ifNotExists: userId }, createdIp: { ifNotExists: sourceIp }, updatedAt: now, updatedBy: userId, updatedIp: sourceIp, }, }); const formatDict = this.createFormatDict(nowFiscalYear, item.seq, date || now, { ...params }); const formatted = this.createFormattedNo(format, formatDict); const formattedNo = `${prefix ?? ''}${formatted}${postfix ?? ''}`; return new sequence_entity_1.SequenceEntity({ id: (0, core_1.generateId)(item.pk, item.sk), no: item.seq, formattedNo, issuedAt: new Date(issuedAt), }); } getRotateValue(rotateBy, forDate) { const date = forDate || new Date(); switch (rotateBy) { case rotate_by_enum_1.RotateByEnum.FISCAL_YEARLY: const year = date.getFullYear(); // new fiscal year from April return date.getMonth() < 3 ? (year - 1).toString() : year.toString(); case rotate_by_enum_1.RotateByEnum.YEARLY: return date.getFullYear().toString(); case rotate_by_enum_1.RotateByEnum.MONTHLY: return (date.getFullYear().toString() + (date.getMonth() + 1).toString().padStart(2, '0')); case rotate_by_enum_1.RotateByEnum.DAILY: return (date.getFullYear().toString() + (date.getMonth() + 1).toString().padStart(2, '0') + date.getDate().toString().padStart(2, '0')); default: return rotate_by_enum_1.RotateByEnum.NONE; } } isIncrementNo(rotateBy, nowFiscalYear, fiscalYear, issuedAt) { /** * Determine whether to increment the number (no) * based on rotateBy. If rotateBy matches the fiscal year, year, or month, * depending on the value, it will return true for incrementing. */ // If rotateBy is not provided, increment if (!rotateBy) { return true; } // Reset the number if fiscal year changes if (rotateBy === rotate_by_enum_1.RotateByEnum.FISCAL_YEARLY) { if (nowFiscalYear === fiscalYear) { return true; } } // Use the current date in Japan time (JST) const nowDate = new Date(); // Assuming the server time is in JST // Reset the number if year changes if (rotateBy === rotate_by_enum_1.RotateByEnum.YEARLY) { if (nowDate.getFullYear() === issuedAt.getFullYear()) { return true; } } // Reset the number if month changes if (rotateBy === rotate_by_enum_1.RotateByEnum.MONTHLY) { if (nowDate.getFullYear() === issuedAt.getFullYear()) { if (nowDate.getMonth() === issuedAt.getMonth()) { return true; } } } return false; } getFiscalYear(options) { /** * Calculates the fiscal year based on the provided `now` and `registerTime`. * * - If `registerTime` is provided, the fiscal year will be calculated starting from * the month of the registration date (`registerTime`). * - If `registerTime` is not provided, the fiscal year will start from the `startMonth` (default is April). * * The fiscal year calculation considers the following: * - The default start month is April (month 4). * - The reference year for the fiscal year calculation is 1953. */ const { now, startMonth = 4, registerTime } = options; const effectiveStartMonth = registerTime ? registerTime.getMonth() + 1 : (startMonth ?? 4); const referenceYear = registerTime ? registerTime.getFullYear() : 1953; // Reference year // Determine the current fiscal year const fiscalYear = now.getMonth() + 1 < effectiveStartMonth ? now.getFullYear() - 1 : now.getFullYear(); // Return the fiscal year number starting from `referenceYear` return fiscalYear - referenceYear + 1; } createFormatDict(fiscalYear, fixNo, now, sequenceParams) { return { ...sequenceParams, fiscal_year: fiscalYear, no: fixNo, year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate(), date: now, }; } createFormattedNo(format, formatDict) { let result = ''; const words = format.split('%%'); for (const word of words) { if (word.includes('#')) { const wordList = word.split('#'); if (formatDict[wordList[0]]) { const key = wordList[0]; const value = formatDict[key].toString(); const paddingInfo = this.extractPaddingInfo(wordList[1]); const paddingValue = value.padStart(paddingInfo.paddingNumber, paddingInfo.paddingValue); result += paddingValue; } else { result += word; } } else { if (formatDict[word]) { result += formatDict[word].toString(); } else { result += word; } } } return result; } extractPaddingInfo(str) { const regex = /:(\d)>(\d)/; const match = str.match(regex); return { paddingValue: match[1], paddingNumber: parseInt(match[2], 10), }; } }; exports.SequencesService = SequencesService; exports.SequencesService = SequencesService = SequencesService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [core_1.DynamoDbService, sequence_master_factory_1.SequenceMasterDataProvider]) ], SequencesService); //# sourceMappingURL=sequences.service.js.map