@mbc-cqrs-serverless/master 0.1.66-beta.0 → 0.1.68-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +2 -1
- package/dist/constants/index.js.map +1 -1
- package/dist/interfaces/master-data.interface.d.ts +3 -0
- package/dist/interfaces/master-setting.interface.d.ts +4 -1
- package/dist/services/master-data.service.d.ts +14 -0
- package/dist/services/master-data.service.js +10 -0
- package/dist/services/master-data.service.js.map +1 -1
- package/dist/services/master-setting.service.d.ts +15 -1
- package/dist/services/master-setting.service.js +10 -0
- package/dist/services/master-setting.service.js.map +1 -1
- package/dist/services/master-setting.service.spec.d.ts +1 -0
- package/dist/services/master-setting.service.spec.js +662 -0
- package/dist/services/master-setting.service.spec.js.map +1 -0
- package/dist/update-scheme.js +262 -14
- package/dist/update-scheme.js.map +1 -1
- package/package.json +6 -4
- package/src/templates/custom-task/custom-task.module.ts +18 -0
- package/src/templates/custom-task/event/task-queue-event-factory.ts +26 -0
- package/src/templates/custom-task/my-task.controller.ts +28 -0
- package/src/templates/custom-task/my-task.service.ts +99 -0
- package/src/templates/master/dto/master-copy.dto.ts +73 -0
- package/src/templates/master/dto/master-data-create.dto.ts +20 -0
- package/src/templates/master/dto/master-data-search.dto.ts +20 -0
- package/src/templates/master/dto/master-data-update.dto.ts +25 -0
- package/src/templates/master/dto/master-rds-list.entity.ts +15 -0
- package/src/templates/master/dto/master-rds.entity.ts +77 -0
- package/src/templates/master/dto/master-setting-search.dto.ts +20 -0
- package/src/templates/master/dto/master-setting-update.dto.ts +11 -0
- package/src/templates/master/entity/master-command.entity.ts +11 -0
- package/src/templates/master/entity/master-data-list.entity.ts +13 -0
- package/src/templates/master/entity/master-data.entity.ts +11 -0
- package/src/templates/master/handler/master-rds.handler.ts +75 -0
- package/src/templates/master/handler/master-sfn-task.event.ts +3 -0
- package/src/templates/master/handler/master-sfn-task.handler.ts +334 -0
- package/src/templates/master/helpers/get-order.ts +19 -0
- package/src/templates/master/helpers/id.ts +42 -0
- package/src/templates/master/helpers/index.ts +3 -0
- package/src/templates/master/helpers/key.ts +11 -0
- package/src/templates/master/master-data.controller.ts +98 -0
- package/src/templates/master/master-data.service.ts +181 -0
- package/src/templates/master/master-setting.controller.ts +103 -0
- package/src/templates/master/master-setting.service.ts +201 -0
- package/src/templates/master/master.module.ts +36 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IsBoolean,
|
|
3
|
+
IsNumber,
|
|
4
|
+
IsObject,
|
|
5
|
+
IsOptional,
|
|
6
|
+
IsString,
|
|
7
|
+
} from 'class-validator'
|
|
8
|
+
|
|
9
|
+
export class MasterDataUpdateDto {
|
|
10
|
+
@IsString()
|
|
11
|
+
@IsOptional()
|
|
12
|
+
name?: string
|
|
13
|
+
|
|
14
|
+
@IsBoolean()
|
|
15
|
+
@IsOptional()
|
|
16
|
+
isDeleted?: boolean
|
|
17
|
+
|
|
18
|
+
@IsNumber()
|
|
19
|
+
@IsOptional()
|
|
20
|
+
seq?: number
|
|
21
|
+
|
|
22
|
+
@IsObject()
|
|
23
|
+
@IsOptional()
|
|
24
|
+
attributes?: object
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger'
|
|
2
|
+
|
|
3
|
+
import { MasterRdsEntity } from './master-rds.entity'
|
|
4
|
+
|
|
5
|
+
export class MasterRdsListEntity {
|
|
6
|
+
@ApiProperty({ type: Number })
|
|
7
|
+
total?: number
|
|
8
|
+
|
|
9
|
+
@ApiProperty({ type: MasterRdsEntity, isArray: true })
|
|
10
|
+
items: MasterRdsEntity[]
|
|
11
|
+
|
|
12
|
+
constructor(data: Partial<MasterRdsListEntity>) {
|
|
13
|
+
Object.assign(this, data)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger'
|
|
2
|
+
import { Master } from '@prisma/client'
|
|
3
|
+
|
|
4
|
+
export class MasterRdsEntity {
|
|
5
|
+
@ApiProperty()
|
|
6
|
+
id: string
|
|
7
|
+
|
|
8
|
+
@ApiProperty()
|
|
9
|
+
cpk: string
|
|
10
|
+
|
|
11
|
+
@ApiProperty()
|
|
12
|
+
csk: string
|
|
13
|
+
|
|
14
|
+
@ApiProperty()
|
|
15
|
+
pk: string
|
|
16
|
+
|
|
17
|
+
@ApiProperty()
|
|
18
|
+
sk: string
|
|
19
|
+
|
|
20
|
+
@ApiProperty()
|
|
21
|
+
masterType: string
|
|
22
|
+
|
|
23
|
+
@ApiProperty()
|
|
24
|
+
masterTypeCode: string
|
|
25
|
+
|
|
26
|
+
@ApiProperty()
|
|
27
|
+
masterCode: string
|
|
28
|
+
|
|
29
|
+
@ApiProperty()
|
|
30
|
+
tenantCode: string
|
|
31
|
+
|
|
32
|
+
@ApiProperty()
|
|
33
|
+
seq: number
|
|
34
|
+
|
|
35
|
+
@ApiProperty()
|
|
36
|
+
code: string
|
|
37
|
+
|
|
38
|
+
@ApiProperty()
|
|
39
|
+
name: string
|
|
40
|
+
|
|
41
|
+
@ApiProperty()
|
|
42
|
+
version: number
|
|
43
|
+
|
|
44
|
+
@ApiProperty()
|
|
45
|
+
isDeleted: boolean
|
|
46
|
+
|
|
47
|
+
@ApiProperty()
|
|
48
|
+
createdBy: string
|
|
49
|
+
|
|
50
|
+
@ApiProperty()
|
|
51
|
+
createdIp: string
|
|
52
|
+
|
|
53
|
+
@ApiProperty()
|
|
54
|
+
createdAt: Date
|
|
55
|
+
|
|
56
|
+
@ApiProperty()
|
|
57
|
+
updatedBy: string
|
|
58
|
+
|
|
59
|
+
@ApiProperty()
|
|
60
|
+
updatedIp: string
|
|
61
|
+
|
|
62
|
+
@ApiProperty()
|
|
63
|
+
updatedAt: Date
|
|
64
|
+
|
|
65
|
+
@ApiProperty({ required: false })
|
|
66
|
+
syncFrom?: string
|
|
67
|
+
|
|
68
|
+
@ApiProperty({ required: false })
|
|
69
|
+
syncDate?: Date
|
|
70
|
+
|
|
71
|
+
@ApiProperty({ type: 'object', required: false })
|
|
72
|
+
attributes?: Record<string, any>
|
|
73
|
+
|
|
74
|
+
constructor(data: Partial<Master>) {
|
|
75
|
+
Object.assign(this, data)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SearchDto } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
import { Transform } from 'class-transformer'
|
|
3
|
+
import { IsBoolean, IsOptional, IsString } from 'class-validator'
|
|
4
|
+
|
|
5
|
+
export class MasterSettingSearchDto extends SearchDto {
|
|
6
|
+
@IsOptional()
|
|
7
|
+
@IsString()
|
|
8
|
+
code?: string
|
|
9
|
+
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@IsString()
|
|
12
|
+
name?: string
|
|
13
|
+
|
|
14
|
+
@IsBoolean()
|
|
15
|
+
@Transform(({ value }) =>
|
|
16
|
+
value === 'true' ? true : value === 'false' ? false : value,
|
|
17
|
+
)
|
|
18
|
+
@IsOptional()
|
|
19
|
+
isDeleted?: boolean
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CommandEntity } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
|
|
3
|
+
export class MasterCommandEntity extends CommandEntity {
|
|
4
|
+
attributes: Record<string, any>
|
|
5
|
+
|
|
6
|
+
constructor(partial: Partial<MasterCommandEntity>) {
|
|
7
|
+
super()
|
|
8
|
+
|
|
9
|
+
Object.assign(this, partial)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DataListEntity } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
|
|
3
|
+
import { MasterDataEntity } from './master-data.entity'
|
|
4
|
+
|
|
5
|
+
export class MasterDataListEntity extends DataListEntity {
|
|
6
|
+
items: MasterDataEntity[]
|
|
7
|
+
|
|
8
|
+
constructor(partial: Partial<MasterDataListEntity>) {
|
|
9
|
+
super(partial)
|
|
10
|
+
|
|
11
|
+
Object.assign(this, partial)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommandModel,
|
|
3
|
+
IDataSyncHandler,
|
|
4
|
+
KEY_SEPARATOR,
|
|
5
|
+
removeSortKeyVersion,
|
|
6
|
+
} from '@mbc-cqrs-serverless/core'
|
|
7
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
8
|
+
import { DATA_SK_PREFIX, SETTING_SK_PREFIX } from 'src/helpers/id'
|
|
9
|
+
import { PrismaService } from 'src/prisma'
|
|
10
|
+
|
|
11
|
+
import { MasterCommandEntity } from '../entity/master-command.entity'
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class MasterDataSyncRdsHandler implements IDataSyncHandler {
|
|
15
|
+
private readonly logger = new Logger(MasterDataSyncRdsHandler.name)
|
|
16
|
+
|
|
17
|
+
constructor(private readonly prismaService: PrismaService) {}
|
|
18
|
+
// MASTER#9999
|
|
19
|
+
// MASTER_SETTING#ABC
|
|
20
|
+
|
|
21
|
+
// MASTER#9999
|
|
22
|
+
// ABC#1
|
|
23
|
+
async up(cmd: MasterCommandEntity): Promise<any> {
|
|
24
|
+
const sk = removeSortKeyVersion(cmd.sk)
|
|
25
|
+
const skSplit = sk.split(KEY_SEPARATOR)
|
|
26
|
+
|
|
27
|
+
const masterType =
|
|
28
|
+
skSplit[0] === SETTING_SK_PREFIX ? SETTING_SK_PREFIX : DATA_SK_PREFIX
|
|
29
|
+
const masterTypeCode = skSplit[0]
|
|
30
|
+
await this.prismaService.master.upsert({
|
|
31
|
+
where: {
|
|
32
|
+
id: cmd.id,
|
|
33
|
+
},
|
|
34
|
+
update: {
|
|
35
|
+
csk: cmd.sk,
|
|
36
|
+
name: cmd.name,
|
|
37
|
+
version: cmd.version,
|
|
38
|
+
seq: cmd.seq,
|
|
39
|
+
updatedAt: cmd.updatedAt,
|
|
40
|
+
updatedBy: cmd.updatedBy,
|
|
41
|
+
updatedIp: cmd.updatedIp,
|
|
42
|
+
isDeleted: cmd.isDeleted || false,
|
|
43
|
+
|
|
44
|
+
attributes: cmd.attributes as object,
|
|
45
|
+
},
|
|
46
|
+
create: {
|
|
47
|
+
id: cmd.id,
|
|
48
|
+
cpk: cmd.pk,
|
|
49
|
+
csk: cmd.sk,
|
|
50
|
+
pk: cmd.pk,
|
|
51
|
+
sk,
|
|
52
|
+
masterType,
|
|
53
|
+
masterTypeCode,
|
|
54
|
+
masterCode: skSplit[1],
|
|
55
|
+
tenantCode: cmd.tenantCode,
|
|
56
|
+
code: sk,
|
|
57
|
+
name: cmd.name,
|
|
58
|
+
version: cmd.version,
|
|
59
|
+
seq: cmd.seq,
|
|
60
|
+
createdAt: cmd.createdAt,
|
|
61
|
+
createdBy: cmd.createdBy,
|
|
62
|
+
createdIp: cmd.createdIp,
|
|
63
|
+
updatedAt: cmd.updatedAt,
|
|
64
|
+
updatedBy: cmd.updatedBy,
|
|
65
|
+
updatedIp: cmd.updatedIp,
|
|
66
|
+
isDeleted: cmd.isDeleted || false,
|
|
67
|
+
|
|
68
|
+
attributes: cmd.attributes as object,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
async down(cmd: CommandModel): Promise<any> {
|
|
73
|
+
this.logger.debug(cmd)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataModel,
|
|
3
|
+
DataService,
|
|
4
|
+
DynamoDbService,
|
|
5
|
+
EventHandler,
|
|
6
|
+
extractInvokeContext,
|
|
7
|
+
IEventHandler,
|
|
8
|
+
IInvoke,
|
|
9
|
+
KEY_SEPARATOR,
|
|
10
|
+
} from '@mbc-cqrs-serverless/core'
|
|
11
|
+
import {
|
|
12
|
+
MasterDataService,
|
|
13
|
+
MasterSettingService,
|
|
14
|
+
} from '@mbc-cqrs-serverless/master'
|
|
15
|
+
import { RotateByEnum, SequencesService } from '@mbc-cqrs-serverless/sequence'
|
|
16
|
+
import { Logger } from '@nestjs/common'
|
|
17
|
+
import { Prisma } from '@prisma/client'
|
|
18
|
+
import { chunk } from 'lodash'
|
|
19
|
+
import {
|
|
20
|
+
DATA_SK_PREFIX,
|
|
21
|
+
DEFAULT_TENANT_CODE,
|
|
22
|
+
generateMasterPk,
|
|
23
|
+
genSequenceSk,
|
|
24
|
+
parseId,
|
|
25
|
+
sequencePk,
|
|
26
|
+
} from 'src/helpers/id'
|
|
27
|
+
import { PrismaService } from 'src/prisma'
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
CopyType,
|
|
31
|
+
DataCopyMode,
|
|
32
|
+
DataCopyOptionDto,
|
|
33
|
+
MasterCopyDto,
|
|
34
|
+
} from '../dto/master-copy.dto'
|
|
35
|
+
import { MasterSfnTaskEvent } from './master-sfn-task.event'
|
|
36
|
+
|
|
37
|
+
const BATCH_SIZE = 100
|
|
38
|
+
|
|
39
|
+
@EventHandler(MasterSfnTaskEvent)
|
|
40
|
+
export class MasterSfnTaskEventHandler
|
|
41
|
+
implements IEventHandler<MasterSfnTaskEvent>
|
|
42
|
+
{
|
|
43
|
+
private readonly logger = new Logger(MasterSfnTaskEventHandler.name)
|
|
44
|
+
private sequenceTableName
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly dynamoDbService: DynamoDbService,
|
|
48
|
+
|
|
49
|
+
private readonly prismaService: PrismaService,
|
|
50
|
+
private readonly masterSettingService: MasterSettingService,
|
|
51
|
+
private readonly masterDataService: MasterDataService,
|
|
52
|
+
private readonly dataService: DataService,
|
|
53
|
+
private readonly sequencesService: SequencesService,
|
|
54
|
+
) {
|
|
55
|
+
this.sequenceTableName = this.dynamoDbService.getTableName('sequences')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async execute(event: MasterSfnTaskEvent): Promise<any> {
|
|
59
|
+
const invokeContext = extractInvokeContext()
|
|
60
|
+
const masterCopyDto = event.input?.input as unknown as MasterCopyDto
|
|
61
|
+
|
|
62
|
+
this.logger.debug('sfn-event:masterCopyDto:', masterCopyDto)
|
|
63
|
+
|
|
64
|
+
const { masterSettingId, targetTenants, copyType, dataCopyOption } =
|
|
65
|
+
masterCopyDto
|
|
66
|
+
const targetTenant = `${targetTenants[0]}`
|
|
67
|
+
|
|
68
|
+
const setting = await this.fetchSetting(masterSettingId)
|
|
69
|
+
const masterCode = this.getMasterCodeFromSetting(setting)
|
|
70
|
+
if (copyType === CopyType.SETTING_ONLY || copyType === CopyType.BOTH) {
|
|
71
|
+
await this.copySettingToTenant(setting, targetTenant, invokeContext)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (copyType === CopyType.DATA_ONLY || copyType === CopyType.BOTH) {
|
|
75
|
+
const isCopySequence = await this.shouldCopySequence(
|
|
76
|
+
setting,
|
|
77
|
+
targetTenant,
|
|
78
|
+
)
|
|
79
|
+
if (isCopySequence) {
|
|
80
|
+
await this.copySeqToTenant(setting, targetTenant)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const dataToCopy = await this.fetchMasterData(masterCode, dataCopyOption)
|
|
84
|
+
|
|
85
|
+
await this.copyDataToTenant(dataToCopy, targetTenant, invokeContext)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.logger.debug('Completed copy process for tenant:', targetTenant)
|
|
89
|
+
return { message: 'Copy successfully', event }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async fetchSetting(id: string) {
|
|
93
|
+
const setting = await this.dataService.getItem(parseId(id))
|
|
94
|
+
this.logger.debug('sfn-event-setting', setting)
|
|
95
|
+
return setting
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private async fetchMasterData(
|
|
99
|
+
masterCode: string,
|
|
100
|
+
dataCopyOption?: DataCopyOptionDto,
|
|
101
|
+
tenant: string = DEFAULT_TENANT_CODE,
|
|
102
|
+
): Promise<Prisma.MasterUncheckedCreateInput[]> {
|
|
103
|
+
const where: Prisma.MasterWhereInput = {
|
|
104
|
+
masterType: DATA_SK_PREFIX,
|
|
105
|
+
masterTypeCode: masterCode,
|
|
106
|
+
pk: `MASTER${KEY_SEPARATOR}${tenant}`,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (dataCopyOption?.mode === DataCopyMode.PARTIAL) {
|
|
110
|
+
where.id = { in: dataCopyOption.id }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.logger.debug('sfn-event-whereCondition', where)
|
|
114
|
+
const data = await this.prismaService.master.findMany({ where })
|
|
115
|
+
this.logger.debug('sfn-event-dataToCopy', data.length)
|
|
116
|
+
return data
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private async copySettingToTenant(
|
|
120
|
+
setting: DataModel,
|
|
121
|
+
tenantCode: string,
|
|
122
|
+
invokeContext: IInvoke,
|
|
123
|
+
) {
|
|
124
|
+
const sk = setting.sk
|
|
125
|
+
const pk = generateMasterPk(tenantCode)
|
|
126
|
+
const masterCode = this.getMasterCodeFromSetting(setting)
|
|
127
|
+
|
|
128
|
+
const tenantSetting = await this.dataService.getItem({ pk, sk })
|
|
129
|
+
|
|
130
|
+
this.logger.debug(
|
|
131
|
+
'sfn-event-copySettingToTenant-tenantSetting',
|
|
132
|
+
tenantSetting,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if (tenantSetting && tenantSetting.isDeleted === false) {
|
|
136
|
+
this.logger.debug('sfn-event-copySettingToTenant-updateSetting', {
|
|
137
|
+
pk,
|
|
138
|
+
sk,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
await this.masterSettingService.updateSetting(
|
|
142
|
+
{ pk, sk },
|
|
143
|
+
{
|
|
144
|
+
name: setting.name,
|
|
145
|
+
code: masterCode,
|
|
146
|
+
settingValue: setting.attributes as object,
|
|
147
|
+
tenantCode,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
invokeContext,
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
} else {
|
|
154
|
+
this.logger.debug('sfn-event-copySettingToTenant-createTenantSetting', {
|
|
155
|
+
pk,
|
|
156
|
+
sk,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
await this.masterSettingService.createTenantSetting(
|
|
160
|
+
{
|
|
161
|
+
name: setting.name,
|
|
162
|
+
code: masterCode,
|
|
163
|
+
settingValue: setting.attributes,
|
|
164
|
+
tenantCode,
|
|
165
|
+
},
|
|
166
|
+
{ invokeContext },
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async copyDataToTenant(
|
|
172
|
+
dataToCopy: Prisma.MasterUncheckedCreateInput[],
|
|
173
|
+
tenantCode: string,
|
|
174
|
+
invokeContext: IInvoke,
|
|
175
|
+
) {
|
|
176
|
+
const chunks = chunk(dataToCopy, BATCH_SIZE)
|
|
177
|
+
for (const batch of chunks) {
|
|
178
|
+
await Promise.all(
|
|
179
|
+
batch.map(async (data) => {
|
|
180
|
+
const parts = data.sk.split(KEY_SEPARATOR)
|
|
181
|
+
const sk =
|
|
182
|
+
parts.length > 1 && parts[1].trim() === ''
|
|
183
|
+
? `${data.sk}${data.masterCode}`
|
|
184
|
+
: data.sk
|
|
185
|
+
const pk = generateMasterPk(tenantCode)
|
|
186
|
+
|
|
187
|
+
const tenantData = await this.dataService.getItem({ pk, sk })
|
|
188
|
+
|
|
189
|
+
this.logger.debug('sfn-event-copyDataToTenant-tenantData', tenantData)
|
|
190
|
+
|
|
191
|
+
// des tenant data is exist and not deleted => update des data same as src data
|
|
192
|
+
if (tenantData && tenantData.isDeleted === false) {
|
|
193
|
+
this.logger.debug('sfn-event-copyDataToTenant-update', { pk, sk })
|
|
194
|
+
|
|
195
|
+
return this.masterDataService.update(
|
|
196
|
+
{ pk, sk },
|
|
197
|
+
{
|
|
198
|
+
name: data.name,
|
|
199
|
+
attributes: data.attributes as object,
|
|
200
|
+
isDeleted: data.isDeleted,
|
|
201
|
+
seq: data.seq,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
invokeContext,
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src data is deleted => do nothing
|
|
210
|
+
if (data.isDeleted === true) return
|
|
211
|
+
|
|
212
|
+
// src data is exist => create des data
|
|
213
|
+
return this.masterDataService.create(
|
|
214
|
+
{
|
|
215
|
+
code: data.masterCode,
|
|
216
|
+
tenantCode,
|
|
217
|
+
name: data.name,
|
|
218
|
+
settingCode: data.masterTypeCode,
|
|
219
|
+
attributes: data.attributes as object,
|
|
220
|
+
seq: data.seq,
|
|
221
|
+
},
|
|
222
|
+
{ invokeContext },
|
|
223
|
+
)
|
|
224
|
+
}),
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async shouldCopySequence(
|
|
230
|
+
setting: DataModel,
|
|
231
|
+
tenantCode: string,
|
|
232
|
+
): Promise<boolean> {
|
|
233
|
+
const fields = setting?.attributes?.['fields'] || []
|
|
234
|
+
const codeField = fields.find((f) => f.physicalName === 'code')
|
|
235
|
+
if (codeField?.dataType !== 'auto_number') {
|
|
236
|
+
this.logger.debug('Sequence not required: code field not auto_number')
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const jcciSeqKey = this.generateSequenceKey(tenantCode, setting)
|
|
241
|
+
const { seq: jcciSeq = 0 } =
|
|
242
|
+
(await this.sequencesService.getCurrentSequence(jcciSeqKey)) ?? {}
|
|
243
|
+
this.logger.debug('sfn-event-shouldCopySequence-jcciSeq', {
|
|
244
|
+
jcciSeqKey,
|
|
245
|
+
jcciSeq,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
if (jcciSeq === 0) {
|
|
249
|
+
this.logger.debug('Skipping sequence copy: JCCI sequence is 0')
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const tenantSeqKey = this.generateSequenceKey(tenantCode, setting)
|
|
254
|
+
const { seq: tenantSeq } =
|
|
255
|
+
(await this.sequencesService.getCurrentSequence(tenantSeqKey)) ?? {}
|
|
256
|
+
this.logger.debug('sfn-event-shouldCopySequence-tenantSeq', {
|
|
257
|
+
tenantSeqKey,
|
|
258
|
+
tenantSeq,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (tenantSeq === undefined || tenantSeq === null) {
|
|
262
|
+
this.logger.debug('Tenant sequence missing: copying sequence required')
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (jcciSeq > tenantSeq) {
|
|
267
|
+
this.logger.debug('Tenant sequence is behind: copying required')
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.logger.debug('Tenant sequence is up to date or ahead: no copy needed')
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private async copySeqToTenant(setting: DataModel, tenantCode: string) {
|
|
276
|
+
const fields = setting?.attributes?.['fields'] || []
|
|
277
|
+
const codeField = fields.find((f) => f.physicalName === 'code')
|
|
278
|
+
const typeCode = codeField.formatCode ?? codeField.dataFormat
|
|
279
|
+
|
|
280
|
+
const jcciSeqKey = this.generateSequenceKey(tenantCode, setting)
|
|
281
|
+
const { seq: jcciSeq = 0 } =
|
|
282
|
+
(await this.sequencesService.getCurrentSequence(jcciSeqKey)) ?? {}
|
|
283
|
+
|
|
284
|
+
const tenantSeqKey = this.generateSequenceKey(tenantCode, setting)
|
|
285
|
+
const { seq: tenantSeq = 0 } =
|
|
286
|
+
(await this.sequencesService.getCurrentSequence(tenantSeqKey)) ?? {}
|
|
287
|
+
|
|
288
|
+
const distance = jcciSeq - tenantSeq
|
|
289
|
+
this.logger.debug('Copying sequence gap:', {
|
|
290
|
+
jcciSeq,
|
|
291
|
+
tenantSeq,
|
|
292
|
+
distance,
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
this.logger.debug('putItem', {
|
|
296
|
+
pk: tenantSeqKey.pk,
|
|
297
|
+
sk: tenantSeqKey.sk,
|
|
298
|
+
code: tenantSeqKey.sk,
|
|
299
|
+
name: tenantSeqKey.sk.split(KEY_SEPARATOR).at(-1),
|
|
300
|
+
seq: jcciSeq,
|
|
301
|
+
tenantCode,
|
|
302
|
+
type: typeCode,
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
this.logger.debug('this.sequenceTableName', this.sequenceTableName)
|
|
306
|
+
|
|
307
|
+
await this.dynamoDbService.putItem(this.sequenceTableName, {
|
|
308
|
+
pk: tenantSeqKey.pk,
|
|
309
|
+
sk: tenantSeqKey.sk,
|
|
310
|
+
code: tenantSeqKey.sk,
|
|
311
|
+
name: tenantSeqKey.sk.split(KEY_SEPARATOR).at(-1),
|
|
312
|
+
seq: jcciSeq,
|
|
313
|
+
tenantCode,
|
|
314
|
+
type: typeCode,
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private generateSequenceKey(tenantCode: string, setting: DataModel) {
|
|
319
|
+
const fields = setting?.attributes?.['fields'] || []
|
|
320
|
+
const codeField = fields.find((f) => f.physicalName === 'code')
|
|
321
|
+
const seqSk = codeField.formatCode ?? codeField.dataFormat
|
|
322
|
+
|
|
323
|
+
const pk = sequencePk(tenantCode)
|
|
324
|
+
const masterCode = this.getMasterCodeFromSetting(setting)
|
|
325
|
+
const sk = genSequenceSk(seqSk, masterCode, RotateByEnum.NONE)
|
|
326
|
+
|
|
327
|
+
return { pk, sk }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private getMasterCodeFromSetting(setting: DataModel): string {
|
|
331
|
+
const parts = setting.sk.split(KEY_SEPARATOR)
|
|
332
|
+
return parts[1]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Prisma } from '@prisma/client'
|
|
2
|
+
|
|
3
|
+
export function getOrderBy(order: string) {
|
|
4
|
+
let orderValue: Prisma.SortOrder = Prisma.SortOrder.asc,
|
|
5
|
+
orderKey = order
|
|
6
|
+
if (order.startsWith('-')) {
|
|
7
|
+
orderValue = Prisma.SortOrder.desc
|
|
8
|
+
orderKey = order.slice(1)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return { [orderKey]: orderValue }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getOrderBys<T>(orders: string[]): T[] {
|
|
15
|
+
if (!orders) {
|
|
16
|
+
return undefined
|
|
17
|
+
}
|
|
18
|
+
return orders.map((order) => getOrderBy(order)) as T[]
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { KEY_SEPARATOR } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
import { RotateByEnum } from '@mbc-cqrs-serverless/sequence'
|
|
3
|
+
|
|
4
|
+
import { MASTER_PK_PREFIX } from './key'
|
|
5
|
+
|
|
6
|
+
const genSequenceSk = (
|
|
7
|
+
seqSettingCode: string,
|
|
8
|
+
settingCode: string,
|
|
9
|
+
rotateBy: RotateByEnum,
|
|
10
|
+
) =>
|
|
11
|
+
`${seqSettingCode}${KEY_SEPARATOR}${settingCode}${KEY_SEPARATOR}${rotateBy}`
|
|
12
|
+
|
|
13
|
+
const sequencePk = (tenantCode: string) => `SEQ${KEY_SEPARATOR}${tenantCode}`
|
|
14
|
+
|
|
15
|
+
function generateMasterPk(tenantCode: string) {
|
|
16
|
+
return `${MASTER_PK_PREFIX}${KEY_SEPARATOR}${tenantCode}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parsePk(pk: string): { type: string; tenantCode: string } {
|
|
20
|
+
if (pk.split(KEY_SEPARATOR).length !== 2) {
|
|
21
|
+
throw new Error('Invalid PK')
|
|
22
|
+
}
|
|
23
|
+
const [type, tenantCode] = pk.split(KEY_SEPARATOR)
|
|
24
|
+
return {
|
|
25
|
+
type,
|
|
26
|
+
tenantCode,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseId(masterId: string): { pk: string; sk: string } {
|
|
30
|
+
const parts = masterId.split('#')
|
|
31
|
+
|
|
32
|
+
if (parts.length < 4 || parts[0] !== 'MASTER') {
|
|
33
|
+
throw new Error('Invalid masterId format')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pk = `${parts[0]}#${parts[1]}`
|
|
37
|
+
const sk = `${parts[2]}#${parts[3]}`
|
|
38
|
+
|
|
39
|
+
return { pk, sk }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { generateMasterPk, genSequenceSk, parseId, parsePk, sequencePk }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const MASTER_PK_PREFIX = 'MASTER'
|
|
2
|
+
const SETTING_SK_PREFIX = 'MASTER_SETTING'
|
|
3
|
+
const DATA_SK_PREFIX = 'MASTER_DATA'
|
|
4
|
+
const MASTER_COPY_SK_PREFIX = 'MASTER_COPY'
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
DATA_SK_PREFIX,
|
|
8
|
+
MASTER_COPY_SK_PREFIX,
|
|
9
|
+
MASTER_PK_PREFIX,
|
|
10
|
+
SETTING_SK_PREFIX,
|
|
11
|
+
}
|