@mbc-cqrs-serverless/sequence
Version:
Generate increment sequence with time-rotation
339 lines • 14.7 kB
JavaScript
"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