@open-mercato/core 0.9.1-develop.7519.1.38f51f8dbb → 0.9.1-develop.7536.1.f9a5776a07
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/logout.js +14 -6
- package/dist/modules/auth/api/logout.js.map +2 -2
- package/dist/modules/auth/api/session/refresh.js +6 -1
- package/dist/modules/auth/api/session/refresh.js.map +2 -2
- package/dist/modules/catalog/ai-tools/mutation-pack.js +10 -5
- package/dist/modules/catalog/ai-tools/mutation-pack.js.map +2 -2
- package/dist/modules/catalog/ai-tools/types.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +5 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +399 -137
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/currencies/lib/seeds.js +2 -0
- package/dist/modules/currencies/lib/seeds.js.map +2 -2
- package/dist/modules/customer_accounts/api/login.js +14 -7
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerUserService.js +10 -10
- package/dist/modules/customer_accounts/services/customerUserService.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +10 -3
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/interactionRestoreDetails.js +75 -0
- package/dist/modules/customers/commands/interactionRestoreDetails.js.map +7 -0
- package/dist/modules/customers/commands/people.js +10 -3
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.js +1 -1
- package/dist/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.js.map +1 -1
- package/dist/modules/forms/api/form-submissions/[id]/submit/route.js +12 -2
- package/dist/modules/forms/api/form-submissions/[id]/submit/route.js.map +2 -2
- package/dist/modules/forms/api/form-submissions/route.js +16 -1
- package/dist/modules/forms/api/form-submissions/route.js.map +2 -2
- package/dist/modules/forms/api/public/submissions/[id]/submit/route.js +6 -1
- package/dist/modules/forms/api/public/submissions/[id]/submit/route.js.map +2 -2
- package/dist/modules/forms/api/runtime-helpers.js +16 -0
- package/dist/modules/forms/api/runtime-helpers.js.map +2 -2
- package/dist/modules/forms/data/validators.js.map +2 -2
- package/dist/modules/forms/lib/portal-subject-policy.js +17 -0
- package/dist/modules/forms/lib/portal-subject-policy.js.map +7 -0
- package/dist/modules/forms/lib/runtime-principal.js +27 -10
- package/dist/modules/forms/lib/runtime-principal.js.map +2 -2
- package/dist/modules/forms/services/distribution-service.js +35 -13
- package/dist/modules/forms/services/distribution-service.js.map +2 -2
- package/dist/modules/forms/services/submission-service.js +4 -0
- package/dist/modules/forms/services/submission-service.js.map +2 -2
- package/dist/modules/staff/commands/leave-requests.js +54 -24
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +19 -7
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
- package/dist/modules/warranty_claims/commands/claims.js +1 -1
- package/dist/modules/warranty_claims/commands/claims.js.map +2 -2
- package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js +6 -6
- package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js.map +2 -2
- package/dist/modules/wms/commands/inventory-actions.js +19 -3
- package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
- package/dist/modules/workflows/lib/async-activity-wait.js +43 -0
- package/dist/modules/workflows/lib/async-activity-wait.js.map +7 -0
- package/dist/modules/workflows/lib/parallel-handler.js +20 -14
- package/dist/modules/workflows/lib/parallel-handler.js.map +2 -2
- package/dist/modules/workflows/lib/workflow-executor.js +21 -23
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/logout.ts +15 -6
- package/src/modules/auth/api/session/refresh.ts +6 -1
- package/src/modules/catalog/ai-tools/mutation-pack.ts +14 -5
- package/src/modules/catalog/ai-tools/types.ts +1 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +5 -1
- package/src/modules/currencies/commands/currencies.ts +475 -168
- package/src/modules/currencies/i18n/de.json +4 -0
- package/src/modules/currencies/i18n/en.json +4 -0
- package/src/modules/currencies/i18n/es.json +4 -0
- package/src/modules/currencies/i18n/ko.json +4 -0
- package/src/modules/currencies/i18n/pl.json +4 -0
- package/src/modules/currencies/lib/seeds.ts +2 -0
- package/src/modules/customer_accounts/api/login.ts +15 -7
- package/src/modules/customer_accounts/services/customerUserService.ts +34 -10
- package/src/modules/customers/commands/companies.ts +16 -4
- package/src/modules/customers/commands/interactionRestoreDetails.ts +110 -0
- package/src/modules/customers/commands/people.ts +16 -4
- package/src/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.ts +1 -1
- package/src/modules/forms/api/form-submissions/[id]/submit/route.ts +16 -3
- package/src/modules/forms/api/form-submissions/route.ts +21 -2
- package/src/modules/forms/api/public/submissions/[id]/submit/route.ts +7 -0
- package/src/modules/forms/api/runtime-helpers.ts +16 -0
- package/src/modules/forms/data/validators.ts +2 -1
- package/src/modules/forms/i18n/de.json +1 -0
- package/src/modules/forms/i18n/en.json +1 -0
- package/src/modules/forms/i18n/es.json +1 -0
- package/src/modules/forms/i18n/ko.json +1 -0
- package/src/modules/forms/i18n/pl.json +1 -0
- package/src/modules/forms/lib/portal-subject-policy.ts +42 -0
- package/src/modules/forms/lib/runtime-principal.ts +35 -11
- package/src/modules/forms/services/distribution-service.ts +35 -13
- package/src/modules/forms/services/submission-service.ts +12 -0
- package/src/modules/staff/commands/leave-requests.ts +54 -23
- package/src/modules/staff/i18n/de.json +2 -0
- package/src/modules/staff/i18n/en.json +2 -0
- package/src/modules/staff/i18n/es.json +2 -0
- package/src/modules/staff/i18n/ko.json +2 -0
- package/src/modules/staff/i18n/pl.json +2 -0
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +20 -8
- package/src/modules/warranty_claims/commands/claims.ts +1 -1
- package/src/modules/warranty_claims/subscribers/return-shipment-tracking.ts +5 -5
- package/src/modules/wms/commands/inventory-actions.ts +30 -10
- package/src/modules/workflows/lib/async-activity-wait.ts +71 -0
- package/src/modules/workflows/lib/parallel-handler.ts +24 -15
- package/src/modules/workflows/lib/workflow-executor.ts +24 -27
|
@@ -2,7 +2,7 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
|
2
2
|
import type { CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
3
3
|
import { buildChanges, requireId, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
4
4
|
import { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
5
|
-
import {
|
|
5
|
+
import { resolveRedoSnapshot, restoreCreatedRow, reviveSnapshotSeed } from '@open-mercato/shared/lib/commands/redo'
|
|
6
6
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
7
7
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
8
8
|
import { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'
|
|
@@ -47,8 +47,6 @@ type CurrencySnapshot = {
|
|
|
47
47
|
updatedAt: string
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
type CurrencyUndoPayload = UndoPayload<CurrencySnapshot>
|
|
51
|
-
|
|
52
50
|
async function loadCurrencySnapshot(
|
|
53
51
|
em: EntityManager,
|
|
54
52
|
id: string,
|
|
@@ -77,71 +75,218 @@ async function loadCurrencySnapshot(
|
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
em: EntityManager,
|
|
82
|
-
currencyId: string,
|
|
83
|
-
organizationId: string,
|
|
78
|
+
type CurrencyScope = {
|
|
84
79
|
tenantId: string
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
organizationId: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type BaseCurrencyTransition = {
|
|
84
|
+
promoted: boolean
|
|
85
|
+
demotedBaseCurrencyIds: string[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type CurrencyCommandResult = {
|
|
89
|
+
currencyId: string
|
|
90
|
+
base?: BaseCurrencyTransition
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type CurrencyUndoPayload = UndoPayload<CurrencySnapshot> & {
|
|
94
|
+
base?: BaseCurrencyTransition
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const NO_BASE_TRANSITION: BaseCurrencyTransition = { promoted: false, demotedBaseCurrencyIds: [] }
|
|
98
|
+
|
|
99
|
+
const BASE_CURRENCY_LOCK_SQL = 'select pg_advisory_xact_lock(hashtextextended(?, 0))'
|
|
100
|
+
|
|
101
|
+
const UPDATABLE_CURRENCY_FIELDS = [
|
|
102
|
+
'code',
|
|
103
|
+
'name',
|
|
104
|
+
'symbol',
|
|
105
|
+
'decimalPlaces',
|
|
106
|
+
'thousandsSeparator',
|
|
107
|
+
'decimalSeparator',
|
|
108
|
+
'isBase',
|
|
109
|
+
'isActive',
|
|
110
|
+
] as const
|
|
111
|
+
|
|
112
|
+
function baseCurrencyLockKey(scope: CurrencyScope): string {
|
|
113
|
+
return `currencies:base:${scope.tenantId}:${scope.organizationId}`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Serialize every currency write inside one tenant and organization so the
|
|
118
|
+
* "exactly one live base currency" invariant is decided on fresh rows. The
|
|
119
|
+
* transaction owns the lock, so PostgreSQL releases it on commit/rollback.
|
|
120
|
+
*/
|
|
121
|
+
async function lockBaseCurrencyScope(em: EntityManager, scope: CurrencyScope): Promise<void> {
|
|
122
|
+
if (!em.isInTransaction()) {
|
|
123
|
+
throw new Error('[internal] Currency base lock requires an active transaction')
|
|
124
|
+
}
|
|
125
|
+
await em.execute(BASE_CURRENCY_LOCK_SQL, [baseCurrencyLockKey(scope)])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function findLiveBaseCurrencyIds(
|
|
129
|
+
em: EntityManager,
|
|
130
|
+
scope: CurrencyScope,
|
|
131
|
+
excludeId?: string,
|
|
132
|
+
): Promise<string[]> {
|
|
133
|
+
const rows = await em.find(
|
|
87
134
|
Currency,
|
|
88
135
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
id: { $ne: currencyId },
|
|
136
|
+
tenantId: scope.tenantId,
|
|
137
|
+
organizationId: scope.organizationId,
|
|
92
138
|
isBase: true,
|
|
93
139
|
deletedAt: null,
|
|
140
|
+
...(excludeId ? { id: { $ne: excludeId } } : {}),
|
|
94
141
|
},
|
|
95
|
-
{
|
|
142
|
+
{ orderBy: { createdAt: 'asc', id: 'asc' } },
|
|
96
143
|
)
|
|
144
|
+
return rows.map((row) => row.id)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function setBaseFlag(
|
|
148
|
+
em: EntityManager,
|
|
149
|
+
scope: CurrencyScope,
|
|
150
|
+
ids: string[],
|
|
151
|
+
isBase: boolean,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
if (ids.length === 0) return
|
|
154
|
+
await em.nativeUpdate(
|
|
155
|
+
Currency,
|
|
156
|
+
{
|
|
157
|
+
tenantId: scope.tenantId,
|
|
158
|
+
organizationId: scope.organizationId,
|
|
159
|
+
id: { $in: ids },
|
|
160
|
+
},
|
|
161
|
+
{ isBase, updatedAt: new Date() },
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function findFirstLivePredecessorId(
|
|
166
|
+
em: EntityManager,
|
|
167
|
+
scope: CurrencyScope,
|
|
168
|
+
candidateIds: string[],
|
|
169
|
+
): Promise<string | null> {
|
|
170
|
+
if (candidateIds.length === 0) return null
|
|
171
|
+
const rows = await em.find(Currency, {
|
|
172
|
+
tenantId: scope.tenantId,
|
|
173
|
+
organizationId: scope.organizationId,
|
|
174
|
+
id: { $in: candidateIds },
|
|
175
|
+
deletedAt: null,
|
|
176
|
+
})
|
|
177
|
+
const liveIds = new Set(rows.map((row) => row.id))
|
|
178
|
+
return candidateIds.find((id) => liveIds.has(id)) ?? null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function translatedHttpError(status: number, key: string, fallback: string): Promise<CrudHttpError> {
|
|
182
|
+
const { translate } = await resolveTranslations()
|
|
183
|
+
return new CrudHttpError(status, { error: translate(key, fallback) })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function resolvePredecessorOrRefuse(
|
|
187
|
+
em: EntityManager,
|
|
188
|
+
scope: CurrencyScope,
|
|
189
|
+
candidateIds: string[],
|
|
190
|
+
): Promise<string> {
|
|
191
|
+
const predecessorId = await findFirstLivePredecessorId(em, scope, candidateIds)
|
|
192
|
+
if (!predecessorId) {
|
|
193
|
+
throw await translatedHttpError(
|
|
194
|
+
409,
|
|
195
|
+
'currencies.errors.undoWouldClearBase',
|
|
196
|
+
"This change can't be undone because it would leave the organization without a base currency.",
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
return predecessorId
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function countActiveRatesForCode(em: EntityManager, scope: CurrencyScope, code: string): Promise<number> {
|
|
203
|
+
return em.count(ExchangeRate, {
|
|
204
|
+
$or: [
|
|
205
|
+
{ fromCurrencyCode: code },
|
|
206
|
+
{ toCurrencyCode: code },
|
|
207
|
+
],
|
|
208
|
+
organizationId: scope.organizationId,
|
|
209
|
+
tenantId: scope.tenantId,
|
|
210
|
+
deletedAt: null,
|
|
211
|
+
isActive: true,
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function readScopedSnapshotRecord(em: EntityManager, snapshot: CurrencySnapshot): Promise<Currency | null> {
|
|
216
|
+
return em.findOne(Currency, {
|
|
217
|
+
id: snapshot.id,
|
|
218
|
+
tenantId: snapshot.tenantId,
|
|
219
|
+
organizationId: snapshot.organizationId,
|
|
220
|
+
})
|
|
97
221
|
}
|
|
98
222
|
|
|
99
|
-
|
|
223
|
+
function scopeOf(record: CurrencyScope): CurrencyScope {
|
|
224
|
+
return { tenantId: record.tenantId, organizationId: record.organizationId }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function omitIsBase(input: CurrencyUpdateInput): Partial<CurrencyUpdateInput> {
|
|
228
|
+
const rest: Partial<CurrencyUpdateInput> = { ...input }
|
|
229
|
+
delete rest.isBase
|
|
230
|
+
return rest
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isPromotionByLegacyLog(payload: CurrencyUndoPayload | null): boolean {
|
|
234
|
+
return payload?.before?.isBase === false && payload?.after?.isBase === true
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function isDemotionByLegacyLog(payload: CurrencyUndoPayload | null): boolean {
|
|
238
|
+
return !payload?.base && payload?.before?.isBase === true && payload?.after?.isBase === false
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const createCurrencyCommand: CommandHandler<CurrencyCreateInput, CurrencyCommandResult> = {
|
|
100
242
|
id: 'currencies.currencies.create',
|
|
101
243
|
async execute(input, ctx) {
|
|
102
244
|
const parsed = currencyCreateSchema.parse(input)
|
|
103
245
|
ensureCurrencyCommandScope(ctx, parsed)
|
|
104
246
|
|
|
105
247
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
248
|
+
const scope = scopeOf(parsed)
|
|
249
|
+
let demotedBaseCurrencyIds: string[] = []
|
|
250
|
+
let record!: Currency
|
|
106
251
|
|
|
107
|
-
// Check for duplicate code
|
|
108
|
-
const existing = await em.findOne(Currency, {
|
|
109
|
-
code: parsed.code,
|
|
110
|
-
organizationId: parsed.organizationId,
|
|
111
|
-
tenantId: parsed.tenantId,
|
|
112
|
-
deletedAt: null,
|
|
113
|
-
})
|
|
114
|
-
if (existing) {
|
|
115
|
-
throw conflict('Currency code already exists for this organization.')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const now = new Date()
|
|
119
|
-
const record = em.create(Currency, {
|
|
120
|
-
organizationId: parsed.organizationId,
|
|
121
|
-
tenantId: parsed.tenantId,
|
|
122
|
-
code: parsed.code,
|
|
123
|
-
name: parsed.name,
|
|
124
|
-
symbol: parsed.symbol ?? null,
|
|
125
|
-
decimalPlaces: parsed.decimalPlaces ?? 2,
|
|
126
|
-
thousandsSeparator: parsed.thousandsSeparator ?? null,
|
|
127
|
-
decimalSeparator: parsed.decimalSeparator ?? null,
|
|
128
|
-
isBase: parsed.isBase ?? false,
|
|
129
|
-
isActive: parsed.isActive !== false,
|
|
130
|
-
createdAt: now,
|
|
131
|
-
updatedAt: now,
|
|
132
|
-
})
|
|
133
|
-
em.persist(record)
|
|
134
|
-
|
|
135
|
-
// Demote any existing base currency and insert the new record in one
|
|
136
|
-
// transaction; a partial commit would leave zero or two base currencies.
|
|
137
252
|
try {
|
|
138
253
|
await withAtomicFlush(
|
|
139
254
|
em,
|
|
140
255
|
[
|
|
141
|
-
() =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
:
|
|
256
|
+
async () => {
|
|
257
|
+
await lockBaseCurrencyScope(em, scope)
|
|
258
|
+
const existing = await em.findOne(Currency, {
|
|
259
|
+
code: parsed.code,
|
|
260
|
+
organizationId: parsed.organizationId,
|
|
261
|
+
tenantId: parsed.tenantId,
|
|
262
|
+
deletedAt: null,
|
|
263
|
+
})
|
|
264
|
+
if (existing) {
|
|
265
|
+
throw conflict('Currency code already exists for this organization.')
|
|
266
|
+
}
|
|
267
|
+
if (parsed.isBase) {
|
|
268
|
+
demotedBaseCurrencyIds = await findLiveBaseCurrencyIds(em, scope)
|
|
269
|
+
await setBaseFlag(em, scope, demotedBaseCurrencyIds, false)
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
() => {
|
|
273
|
+
const now = new Date()
|
|
274
|
+
record = em.create(Currency, {
|
|
275
|
+
organizationId: parsed.organizationId,
|
|
276
|
+
tenantId: parsed.tenantId,
|
|
277
|
+
code: parsed.code,
|
|
278
|
+
name: parsed.name,
|
|
279
|
+
symbol: parsed.symbol ?? null,
|
|
280
|
+
decimalPlaces: parsed.decimalPlaces ?? 2,
|
|
281
|
+
thousandsSeparator: parsed.thousandsSeparator ?? null,
|
|
282
|
+
decimalSeparator: parsed.decimalSeparator ?? null,
|
|
283
|
+
isBase: parsed.isBase ?? false,
|
|
284
|
+
isActive: parsed.isActive !== false,
|
|
285
|
+
createdAt: now,
|
|
286
|
+
updatedAt: now,
|
|
287
|
+
})
|
|
288
|
+
em.persist(record)
|
|
289
|
+
},
|
|
145
290
|
],
|
|
146
291
|
{ transaction: true },
|
|
147
292
|
)
|
|
@@ -165,13 +310,15 @@ const createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: s
|
|
|
165
310
|
events: currencyCrudEvents,
|
|
166
311
|
})
|
|
167
312
|
|
|
168
|
-
return
|
|
313
|
+
return record.isBase
|
|
314
|
+
? { currencyId: record.id, base: { promoted: true, demotedBaseCurrencyIds } }
|
|
315
|
+
: { currencyId: record.id }
|
|
169
316
|
},
|
|
170
317
|
captureAfter: async (_input, result, ctx) => {
|
|
171
318
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
172
319
|
return loadCurrencySnapshot(em, result.currencyId, ctx)
|
|
173
320
|
},
|
|
174
|
-
buildLog: async ({ snapshots }) => {
|
|
321
|
+
buildLog: async ({ snapshots, result }) => {
|
|
175
322
|
const after = snapshots.after as CurrencySnapshot | undefined
|
|
176
323
|
if (!after) return null
|
|
177
324
|
const { translate } = await resolveTranslations()
|
|
@@ -182,34 +329,113 @@ const createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: s
|
|
|
182
329
|
tenantId: after.tenantId,
|
|
183
330
|
organizationId: after.organizationId,
|
|
184
331
|
snapshotAfter: after,
|
|
185
|
-
payload: { undo: { after } },
|
|
332
|
+
payload: { undo: { after, base: result?.base ?? NO_BASE_TRANSITION } },
|
|
186
333
|
}
|
|
187
334
|
},
|
|
188
335
|
undo: async ({ logEntry, ctx }) => {
|
|
189
336
|
const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)
|
|
190
337
|
const after = payload?.after ?? null
|
|
191
338
|
if (!after) return
|
|
339
|
+
const scope = scopeOf(after)
|
|
340
|
+
const promoted = payload?.base ? payload.base.promoted : after.isBase
|
|
341
|
+
const predecessorIds = payload?.base?.demotedBaseCurrencyIds ?? []
|
|
192
342
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
343
|
+
let record: Currency | null = null
|
|
344
|
+
let restoredBaseId: string | null = null
|
|
345
|
+
|
|
346
|
+
await withAtomicFlush(
|
|
347
|
+
em,
|
|
348
|
+
[
|
|
349
|
+
async () => {
|
|
350
|
+
await lockBaseCurrencyScope(em, scope)
|
|
351
|
+
const current = await readScopedSnapshotRecord(em, after)
|
|
352
|
+
if (!current || current.deletedAt) return
|
|
353
|
+
const activeRatesCount = await countActiveRatesForCode(em, scope, current.code)
|
|
354
|
+
if (activeRatesCount > 0) {
|
|
355
|
+
throw await translatedHttpError(
|
|
356
|
+
409,
|
|
357
|
+
'currencies.errors.undoHasRates',
|
|
358
|
+
"This change can't be undone because exchange rates now use this currency. Delete or deactivate those exchange rates first.",
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
if (current.isBase) {
|
|
362
|
+
restoredBaseId = await resolvePredecessorOrRefuse(em, scope, promoted ? predecessorIds : [])
|
|
363
|
+
}
|
|
364
|
+
record = current
|
|
365
|
+
},
|
|
366
|
+
() => {
|
|
367
|
+
if (!record) return
|
|
368
|
+
record.deletedAt = new Date()
|
|
369
|
+
record.isActive = false
|
|
370
|
+
record.isBase = false
|
|
371
|
+
},
|
|
372
|
+
() => (restoredBaseId ? setBaseFlag(em, scope, [restoredBaseId], true) : undefined),
|
|
373
|
+
],
|
|
374
|
+
{ transaction: true },
|
|
375
|
+
)
|
|
198
376
|
},
|
|
199
|
-
redo:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
377
|
+
redo: async ({ ctx, logEntry }) => {
|
|
378
|
+
const snapshot = resolveRedoSnapshot<CurrencySnapshot>(logEntry)
|
|
379
|
+
if (!snapshot || !snapshot.id) {
|
|
380
|
+
throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for create command' })
|
|
381
|
+
}
|
|
382
|
+
const scope = scopeOf(snapshot)
|
|
383
|
+
const isBase = snapshot.isBase === true
|
|
384
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
385
|
+
let demotedBaseCurrencyIds: string[] = []
|
|
386
|
+
let record!: Currency
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await withAtomicFlush(
|
|
390
|
+
em,
|
|
391
|
+
[
|
|
392
|
+
async () => {
|
|
393
|
+
await lockBaseCurrencyScope(em, scope)
|
|
394
|
+
if (isBase) {
|
|
395
|
+
demotedBaseCurrencyIds = await findLiveBaseCurrencyIds(em, scope, snapshot.id)
|
|
396
|
+
await setBaseFlag(em, scope, demotedBaseCurrencyIds, false)
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async () => {
|
|
400
|
+
record = await restoreCreatedRow(
|
|
401
|
+
em,
|
|
402
|
+
Currency,
|
|
403
|
+
snapshot.id,
|
|
404
|
+
() => reviveSnapshotSeed(snapshot as unknown as Record<string, unknown>),
|
|
405
|
+
(lookupEm) => readScopedSnapshotRecord(lookupEm, snapshot),
|
|
406
|
+
)
|
|
407
|
+
record.isBase = isBase
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
{ transaction: true },
|
|
411
|
+
)
|
|
412
|
+
} catch (err) {
|
|
413
|
+
if (isUniqueViolation(err)) {
|
|
414
|
+
throw conflict('[internal] Cannot redo: a record with the same unique key already exists.')
|
|
207
415
|
}
|
|
208
|
-
|
|
209
|
-
|
|
416
|
+
throw err
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
420
|
+
await emitCrudSideEffects({
|
|
421
|
+
dataEngine: de,
|
|
422
|
+
action: 'created',
|
|
423
|
+
entity: record,
|
|
424
|
+
identifiers: {
|
|
425
|
+
id: record.id,
|
|
426
|
+
organizationId: record.organizationId,
|
|
427
|
+
tenantId: record.tenantId,
|
|
428
|
+
},
|
|
429
|
+
events: currencyCrudEvents,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return isBase
|
|
433
|
+
? { currencyId: record.id, base: { promoted: true, demotedBaseCurrencyIds } }
|
|
434
|
+
: { currencyId: record.id }
|
|
435
|
+
},
|
|
210
436
|
}
|
|
211
437
|
|
|
212
|
-
const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput,
|
|
438
|
+
const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, CurrencyCommandResult> = {
|
|
213
439
|
id: 'currencies.currencies.update',
|
|
214
440
|
async prepare(input, ctx) {
|
|
215
441
|
requireId(input.id, 'Currency ID is required')
|
|
@@ -221,71 +447,91 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
|
|
|
221
447
|
const parsed = currencyUpdateSchema.parse(input)
|
|
222
448
|
requireId(parsed.id, 'Currency ID is required')
|
|
223
449
|
|
|
224
|
-
const
|
|
225
|
-
const record = await em.findOne(
|
|
450
|
+
const scopeRecord = await (ctx.container.resolve('em') as EntityManager).fork().findOne(
|
|
226
451
|
Currency,
|
|
227
452
|
buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),
|
|
228
453
|
)
|
|
229
|
-
if (!
|
|
454
|
+
if (!scopeRecord) {
|
|
230
455
|
throw new CrudHttpError(404, { error: 'Currency not found' })
|
|
231
456
|
}
|
|
232
|
-
ensureCurrencyCommandScope(ctx,
|
|
233
|
-
|
|
234
|
-
// Check code uniqueness if changing code
|
|
235
|
-
if (parsed.code && parsed.code !== record.code) {
|
|
236
|
-
const existing = await em.findOne(Currency, {
|
|
237
|
-
code: parsed.code,
|
|
238
|
-
organizationId: record.organizationId,
|
|
239
|
-
tenantId: record.tenantId,
|
|
240
|
-
id: { $ne: record.id },
|
|
241
|
-
deletedAt: null,
|
|
242
|
-
})
|
|
243
|
-
if (existing) {
|
|
244
|
-
throw conflict('Currency code already exists for this organization.')
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const allChanges = buildChanges(record as unknown as Record<string, unknown>, parsed, [
|
|
249
|
-
'code',
|
|
250
|
-
'name',
|
|
251
|
-
'symbol',
|
|
252
|
-
'decimalPlaces',
|
|
253
|
-
'thousandsSeparator',
|
|
254
|
-
'decimalSeparator',
|
|
255
|
-
'isBase',
|
|
256
|
-
'isActive',
|
|
257
|
-
])
|
|
258
|
-
const changes = Object.fromEntries(
|
|
259
|
-
Object.entries(allChanges).filter(([, c]) => c.to !== undefined),
|
|
260
|
-
) as Record<string, { from: unknown; to: unknown }>
|
|
457
|
+
ensureCurrencyCommandScope(ctx, scopeRecord)
|
|
458
|
+
const scope = scopeOf(scopeRecord)
|
|
261
459
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
460
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
461
|
+
let record!: Currency
|
|
462
|
+
let changes: Record<string, { from: unknown; to: unknown }> = {}
|
|
463
|
+
let promoted = false
|
|
464
|
+
let demotedBaseCurrencyIds: string[] = []
|
|
265
465
|
|
|
266
|
-
// Demote any existing base currency and persist the scalar changes in one
|
|
267
|
-
// transaction; a partial commit would leave zero or two base currencies.
|
|
268
|
-
// The scalar mutations live in the first phase so the per-phase flush issues
|
|
269
|
-
// the pending changeset on the managed `record` before `enforceBaseCurrency`
|
|
270
|
-
// runs its interleaved `nativeUpdate` (which would otherwise drop the pending
|
|
271
|
-
// UPDATE under v7).
|
|
272
466
|
await withAtomicFlush(
|
|
273
467
|
em,
|
|
274
468
|
[
|
|
275
|
-
() => {
|
|
276
|
-
|
|
277
|
-
|
|
469
|
+
async () => {
|
|
470
|
+
await lockBaseCurrencyScope(em, scope)
|
|
471
|
+
const current = await em.findOne(
|
|
472
|
+
Currency,
|
|
473
|
+
buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),
|
|
474
|
+
)
|
|
475
|
+
if (!current) {
|
|
476
|
+
throw new CrudHttpError(404, { error: 'Currency not found' })
|
|
278
477
|
}
|
|
279
|
-
|
|
478
|
+
ensureCurrencyCommandScope(ctx, current)
|
|
479
|
+
record = current
|
|
480
|
+
|
|
481
|
+
if (parsed.code && parsed.code !== record.code) {
|
|
482
|
+
const existing = await em.findOne(Currency, {
|
|
483
|
+
code: parsed.code,
|
|
484
|
+
organizationId: record.organizationId,
|
|
485
|
+
tenantId: record.tenantId,
|
|
486
|
+
id: { $ne: record.id },
|
|
487
|
+
deletedAt: null,
|
|
488
|
+
})
|
|
489
|
+
if (existing) {
|
|
490
|
+
throw conflict('Currency code already exists for this organization.')
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (parsed.isBase === false && record.isBase) {
|
|
495
|
+
throw await translatedHttpError(
|
|
496
|
+
400,
|
|
497
|
+
'currencies.errors.baseCurrencyUnset',
|
|
498
|
+
'Choose another currency as the base instead of clearing it.',
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const allChanges = buildChanges(
|
|
503
|
+
record as unknown as Record<string, unknown>,
|
|
504
|
+
parsed,
|
|
505
|
+
UPDATABLE_CURRENCY_FIELDS,
|
|
506
|
+
)
|
|
507
|
+
changes = Object.fromEntries(
|
|
508
|
+
Object.entries(allChanges).filter(([, change]) => change.to !== undefined),
|
|
509
|
+
) as Record<string, { from: unknown; to: unknown }>
|
|
510
|
+
|
|
511
|
+
promoted = changes.isBase?.to === true
|
|
512
|
+
if (promoted) {
|
|
513
|
+
demotedBaseCurrencyIds = await findLiveBaseCurrencyIds(em, scope, record.id)
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
() => {
|
|
517
|
+
if (Object.keys(changes).length === 0) return
|
|
518
|
+
const assignments = Object.fromEntries(
|
|
519
|
+
Object.entries(changes).map(([key, change]) => [key, change.to]),
|
|
520
|
+
)
|
|
521
|
+
Object.assign(record, assignments, { updatedAt: new Date() })
|
|
280
522
|
},
|
|
281
|
-
() =>
|
|
282
|
-
parsed.isBase === true && record.isBase
|
|
283
|
-
? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)
|
|
284
|
-
: undefined,
|
|
523
|
+
() => setBaseFlag(em, scope, demotedBaseCurrencyIds, false),
|
|
285
524
|
],
|
|
286
525
|
{ transaction: true },
|
|
287
526
|
)
|
|
288
527
|
|
|
528
|
+
const result: CurrencyCommandResult = promoted
|
|
529
|
+
? { currencyId: record.id, base: { promoted: true, demotedBaseCurrencyIds } }
|
|
530
|
+
: { currencyId: record.id }
|
|
531
|
+
if (Object.keys(changes).length === 0) {
|
|
532
|
+
return result
|
|
533
|
+
}
|
|
534
|
+
|
|
289
535
|
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
290
536
|
await emitCrudSideEffects({
|
|
291
537
|
dataEngine: de,
|
|
@@ -299,17 +545,18 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
|
|
|
299
545
|
events: currencyCrudEvents,
|
|
300
546
|
})
|
|
301
547
|
|
|
302
|
-
return
|
|
548
|
+
return result
|
|
303
549
|
},
|
|
304
550
|
captureAfter: async (_input, result, ctx) => {
|
|
305
551
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
306
552
|
return loadCurrencySnapshot(em, result.currencyId, ctx)
|
|
307
553
|
},
|
|
308
|
-
buildLog: async ({ snapshots, result }) => {
|
|
554
|
+
buildLog: async ({ input, snapshots, result }) => {
|
|
309
555
|
const before = snapshots.before as CurrencySnapshot | undefined
|
|
310
556
|
const after = snapshots.after as CurrencySnapshot | undefined
|
|
311
557
|
if (!after) return null
|
|
312
558
|
const { translate } = await resolveTranslations()
|
|
559
|
+
const base = result?.base ?? NO_BASE_TRANSITION
|
|
313
560
|
return {
|
|
314
561
|
actionLabel: translate('currencies.audit.update', 'Update currency'),
|
|
315
562
|
resourceKind: 'currencies.currency',
|
|
@@ -318,28 +565,57 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
|
|
|
318
565
|
organizationId: after.organizationId,
|
|
319
566
|
snapshotBefore: before ?? undefined,
|
|
320
567
|
snapshotAfter: after,
|
|
321
|
-
payload: { undo: { before, after } },
|
|
568
|
+
payload: { undo: { before, after, base } },
|
|
569
|
+
redoInput: base.promoted ? input : omitIsBase(input),
|
|
322
570
|
}
|
|
323
571
|
},
|
|
324
572
|
undo: async ({ logEntry, ctx }) => {
|
|
325
573
|
const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)
|
|
326
574
|
const before = payload?.before ?? null
|
|
327
575
|
if (!before) return
|
|
576
|
+
const scope = scopeOf(before)
|
|
577
|
+
const promoted = payload?.base ? payload.base.promoted : isPromotionByLegacyLog(payload)
|
|
578
|
+
const predecessorIds = payload?.base?.demotedBaseCurrencyIds ?? []
|
|
328
579
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
580
|
+
const demotedByLegacyLog = isDemotionByLegacyLog(payload)
|
|
581
|
+
let record: Currency | null = null
|
|
582
|
+
let restoredBaseId: string | null = null
|
|
583
|
+
let repromote = false
|
|
584
|
+
|
|
585
|
+
await withAtomicFlush(
|
|
586
|
+
em,
|
|
587
|
+
[
|
|
588
|
+
async () => {
|
|
589
|
+
await lockBaseCurrencyScope(em, scope)
|
|
590
|
+
const current = await readScopedSnapshotRecord(em, before)
|
|
591
|
+
if (!current) return
|
|
592
|
+
if (promoted && current.isBase && !current.deletedAt) {
|
|
593
|
+
restoredBaseId = await resolvePredecessorOrRefuse(em, scope, predecessorIds)
|
|
594
|
+
}
|
|
595
|
+
if (demotedByLegacyLog && !current.isBase && !current.deletedAt) {
|
|
596
|
+
repromote = (await findLiveBaseCurrencyIds(em, scope)).length === 0
|
|
597
|
+
}
|
|
598
|
+
record = current
|
|
599
|
+
},
|
|
600
|
+
() => {
|
|
601
|
+
if (!record) return
|
|
602
|
+
Object.assign(record, {
|
|
603
|
+
code: before.code,
|
|
604
|
+
name: before.name,
|
|
605
|
+
symbol: before.symbol,
|
|
606
|
+
decimalPlaces: before.decimalPlaces,
|
|
607
|
+
thousandsSeparator: before.thousandsSeparator,
|
|
608
|
+
decimalSeparator: before.decimalSeparator,
|
|
609
|
+
isActive: before.isActive,
|
|
610
|
+
updatedAt: new Date(),
|
|
611
|
+
})
|
|
612
|
+
if (restoredBaseId) record.isBase = false
|
|
613
|
+
if (repromote) record.isBase = true
|
|
614
|
+
},
|
|
615
|
+
() => (restoredBaseId ? setBaseFlag(em, scope, [restoredBaseId], true) : undefined),
|
|
616
|
+
],
|
|
617
|
+
{ transaction: true },
|
|
618
|
+
)
|
|
343
619
|
},
|
|
344
620
|
}
|
|
345
621
|
|
|
@@ -355,42 +631,52 @@ const deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: s
|
|
|
355
631
|
const parsed = currencyDeleteSchema.parse(input)
|
|
356
632
|
requireId(parsed.id, 'Currency ID is required')
|
|
357
633
|
|
|
358
|
-
const
|
|
359
|
-
const record = await em.findOne(
|
|
634
|
+
const scopeRecord = await (ctx.container.resolve('em') as EntityManager).fork().findOne(
|
|
360
635
|
Currency,
|
|
361
636
|
buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),
|
|
362
637
|
)
|
|
363
|
-
if (!
|
|
638
|
+
if (!scopeRecord) {
|
|
364
639
|
throw new CrudHttpError(404, { error: 'Currency not found' })
|
|
365
640
|
}
|
|
366
|
-
ensureCurrencyCommandScope(ctx,
|
|
641
|
+
ensureCurrencyCommandScope(ctx, scopeRecord)
|
|
642
|
+
const scope = scopeOf(scopeRecord)
|
|
367
643
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
throw new CrudHttpError(400, { error: 'Cannot delete the base currency' })
|
|
371
|
-
}
|
|
644
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
645
|
+
let record!: Currency
|
|
372
646
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
647
|
+
await withAtomicFlush(
|
|
648
|
+
em,
|
|
649
|
+
[
|
|
650
|
+
async () => {
|
|
651
|
+
await lockBaseCurrencyScope(em, scope)
|
|
652
|
+
const current = await em.findOne(
|
|
653
|
+
Currency,
|
|
654
|
+
buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),
|
|
655
|
+
)
|
|
656
|
+
if (!current) {
|
|
657
|
+
throw new CrudHttpError(404, { error: 'Currency not found' })
|
|
658
|
+
}
|
|
659
|
+
ensureCurrencyCommandScope(ctx, current)
|
|
660
|
+
|
|
661
|
+
if (current.isBase) {
|
|
662
|
+
throw new CrudHttpError(400, { error: 'Cannot delete the base currency' })
|
|
663
|
+
}
|
|
390
664
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
665
|
+
const activeRatesCount = await countActiveRatesForCode(em, scope, current.code)
|
|
666
|
+
if (activeRatesCount > 0) {
|
|
667
|
+
throw new CrudHttpError(400, {
|
|
668
|
+
error: `Cannot delete currency ${current.code} because it has ${activeRatesCount} active exchange rate(s). Please delete or deactivate the exchange rates first.`,
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
record = current
|
|
672
|
+
},
|
|
673
|
+
() => {
|
|
674
|
+
record.deletedAt = new Date()
|
|
675
|
+
record.isActive = false
|
|
676
|
+
},
|
|
677
|
+
],
|
|
678
|
+
{ transaction: true },
|
|
679
|
+
)
|
|
394
680
|
|
|
395
681
|
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
396
682
|
await emitCrudSideEffects({
|
|
@@ -407,7 +693,7 @@ const deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: s
|
|
|
407
693
|
|
|
408
694
|
return { currencyId: record.id }
|
|
409
695
|
},
|
|
410
|
-
buildLog: async ({ snapshots
|
|
696
|
+
buildLog: async ({ snapshots }) => {
|
|
411
697
|
const before = snapshots.before as CurrencySnapshot | undefined
|
|
412
698
|
if (!before) return null
|
|
413
699
|
const { translate } = await resolveTranslations()
|
|
@@ -425,13 +711,34 @@ const deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: s
|
|
|
425
711
|
const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)
|
|
426
712
|
const before = payload?.before ?? null
|
|
427
713
|
if (!before) return
|
|
714
|
+
const scope = scopeOf(before)
|
|
428
715
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
716
|
+
let record: Currency | null = null
|
|
717
|
+
let reviveAsNonBase = false
|
|
718
|
+
|
|
719
|
+
await withAtomicFlush(
|
|
720
|
+
em,
|
|
721
|
+
[
|
|
722
|
+
async () => {
|
|
723
|
+
await lockBaseCurrencyScope(em, scope)
|
|
724
|
+
const current = await readScopedSnapshotRecord(em, before)
|
|
725
|
+
if (!current) return
|
|
726
|
+
if (current.isBase) {
|
|
727
|
+
const otherBaseIds = await findLiveBaseCurrencyIds(em, scope, current.id)
|
|
728
|
+
reviveAsNonBase = otherBaseIds.length > 0
|
|
729
|
+
}
|
|
730
|
+
record = current
|
|
731
|
+
},
|
|
732
|
+
() => {
|
|
733
|
+
if (!record) return
|
|
734
|
+
record.deletedAt = null
|
|
735
|
+
record.isActive = before.isActive
|
|
736
|
+
if (reviveAsNonBase) record.isBase = false
|
|
737
|
+
record.updatedAt = new Date()
|
|
738
|
+
},
|
|
739
|
+
],
|
|
740
|
+
{ transaction: true },
|
|
741
|
+
)
|
|
435
742
|
},
|
|
436
743
|
}
|
|
437
744
|
|