@open-mercato/core 0.6.5-develop.4490.1.d8e873f3cf → 0.6.5-develop.4498.1.55dc06a57c
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/customers/commands/personCompanyLinks.js +77 -64
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +15 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/lib/calculations.js +18 -1
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/commands/personCompanyLinks.ts +80 -65
- package/src/modules/sales/commands/payments.ts +29 -0
- package/src/modules/sales/lib/calculations.ts +32 -4
|
@@ -294,33 +294,38 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
|
|
|
294
294
|
)
|
|
295
295
|
if (!link) return
|
|
296
296
|
|
|
297
|
+
let person: CustomerEntity | null = null
|
|
298
|
+
let profile: CustomerPersonProfile | null = null
|
|
299
|
+
let remainingLinks: CustomerPersonCompanyLink[] = []
|
|
300
|
+
|
|
297
301
|
await withAtomicFlush(em, [
|
|
298
302
|
async () => {
|
|
299
|
-
|
|
300
|
-
link.deletedAt = new Date()
|
|
301
|
-
const person = await findOneWithDecryption(
|
|
303
|
+
person = await findOneWithDecryption(
|
|
302
304
|
em,
|
|
303
305
|
CustomerEntity,
|
|
304
306
|
{ id: after.personEntityId, kind: 'person', tenantId: after.tenantId, organizationId: after.organizationId, deletedAt: null },
|
|
305
307
|
undefined,
|
|
306
308
|
{ tenantId: after.tenantId, organizationId: after.organizationId },
|
|
307
309
|
)
|
|
308
|
-
if (person)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
310
|
+
if (!person) return
|
|
311
|
+
profile = await findOneWithDecryption(
|
|
312
|
+
em,
|
|
313
|
+
CustomerPersonProfile,
|
|
314
|
+
{ entity: person },
|
|
315
|
+
{ populate: ['company'] },
|
|
316
|
+
{ tenantId: person.tenantId, organizationId: person.organizationId },
|
|
317
|
+
)
|
|
318
|
+
if (!profile) return
|
|
319
|
+
remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)
|
|
320
|
+
},
|
|
321
|
+
async () => {
|
|
322
|
+
link.isPrimary = false
|
|
323
|
+
link.deletedAt = new Date()
|
|
324
|
+
if (!person || !profile) return
|
|
325
|
+
if (after.isPrimary) {
|
|
326
|
+
await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, after.companyEntityId)
|
|
327
|
+
} else if (profile.company && typeof profile.company !== 'string' && profile.company.id === after.companyEntityId) {
|
|
328
|
+
profile.company = null
|
|
324
329
|
}
|
|
325
330
|
},
|
|
326
331
|
], { transaction: true })
|
|
@@ -453,39 +458,44 @@ const updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInpu
|
|
|
453
458
|
)
|
|
454
459
|
if (!link) return
|
|
455
460
|
|
|
461
|
+
let person: CustomerEntity | null = null
|
|
462
|
+
let profile: CustomerPersonProfile | null = null
|
|
463
|
+
let company: CustomerEntity | null = null
|
|
464
|
+
|
|
456
465
|
await withAtomicFlush(em, [
|
|
457
466
|
async () => {
|
|
458
|
-
|
|
467
|
+
person = await findOneWithDecryption(
|
|
459
468
|
em,
|
|
460
469
|
CustomerEntity,
|
|
461
470
|
{ id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
|
|
462
471
|
undefined,
|
|
463
472
|
{ tenantId: before.tenantId, organizationId: before.organizationId },
|
|
464
473
|
)
|
|
465
|
-
if (person)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
474
|
+
if (!person) return
|
|
475
|
+
profile = await findOneWithDecryption(
|
|
476
|
+
em,
|
|
477
|
+
CustomerPersonProfile,
|
|
478
|
+
{ entity: person },
|
|
479
|
+
{ populate: ['company'] },
|
|
480
|
+
{ tenantId: person.tenantId, organizationId: person.organizationId },
|
|
481
|
+
)
|
|
482
|
+
if (!profile || !before.isPrimary) return
|
|
483
|
+
company = await findOneWithDecryption(
|
|
484
|
+
em,
|
|
485
|
+
CustomerEntity,
|
|
486
|
+
{ id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
|
|
487
|
+
undefined,
|
|
488
|
+
{ tenantId: before.tenantId, organizationId: before.organizationId },
|
|
489
|
+
)
|
|
490
|
+
},
|
|
491
|
+
async () => {
|
|
492
|
+
if (!person || !profile) return
|
|
493
|
+
if (before.isPrimary) {
|
|
494
|
+
await clearPrimaryFlagsForPerson(em, person)
|
|
495
|
+
link.isPrimary = true
|
|
496
|
+
if (company) profile.company = company
|
|
497
|
+
} else {
|
|
498
|
+
link.isPrimary = false
|
|
489
499
|
}
|
|
490
500
|
},
|
|
491
501
|
], { transaction: true })
|
|
@@ -610,38 +620,43 @@ const deletePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkDeleteInpu
|
|
|
610
620
|
)
|
|
611
621
|
if (!link) return
|
|
612
622
|
|
|
623
|
+
let person: CustomerEntity | null = null
|
|
624
|
+
let profile: CustomerPersonProfile | null = null
|
|
625
|
+
let company: CustomerEntity | null = null
|
|
626
|
+
|
|
613
627
|
await withAtomicFlush(em, [
|
|
614
628
|
async () => {
|
|
615
|
-
|
|
616
|
-
link.isPrimary = before.isPrimary
|
|
617
|
-
|
|
618
|
-
const person = await findOneWithDecryption(
|
|
629
|
+
person = await findOneWithDecryption(
|
|
619
630
|
em,
|
|
620
631
|
CustomerEntity,
|
|
621
632
|
{ id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
|
|
622
633
|
undefined,
|
|
623
634
|
{ tenantId: before.tenantId, organizationId: before.organizationId },
|
|
624
635
|
)
|
|
636
|
+
if (!person || !before.isPrimary) return
|
|
637
|
+
profile = await findOneWithDecryption(
|
|
638
|
+
em,
|
|
639
|
+
CustomerPersonProfile,
|
|
640
|
+
{ entity: person },
|
|
641
|
+
{ populate: ['company'] },
|
|
642
|
+
{ tenantId: person.tenantId, organizationId: person.organizationId },
|
|
643
|
+
)
|
|
644
|
+
if (!profile) return
|
|
645
|
+
company = await findOneWithDecryption(
|
|
646
|
+
em,
|
|
647
|
+
CustomerEntity,
|
|
648
|
+
{ id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
|
|
649
|
+
undefined,
|
|
650
|
+
{ tenantId: before.tenantId, organizationId: before.organizationId },
|
|
651
|
+
)
|
|
652
|
+
},
|
|
653
|
+
async () => {
|
|
654
|
+
link.deletedAt = null
|
|
655
|
+
link.isPrimary = before.isPrimary
|
|
625
656
|
if (person && before.isPrimary) {
|
|
626
657
|
await clearPrimaryFlagsForPerson(em, person)
|
|
627
658
|
link.isPrimary = true
|
|
628
|
-
|
|
629
|
-
em,
|
|
630
|
-
CustomerPersonProfile,
|
|
631
|
-
{ entity: person },
|
|
632
|
-
{ populate: ['company'] },
|
|
633
|
-
{ tenantId: person.tenantId, organizationId: person.organizationId },
|
|
634
|
-
)
|
|
635
|
-
if (profile) {
|
|
636
|
-
const company = await findOneWithDecryption(
|
|
637
|
-
em,
|
|
638
|
-
CustomerEntity,
|
|
639
|
-
{ id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
|
|
640
|
-
undefined,
|
|
641
|
-
{ tenantId: before.tenantId, organizationId: before.organizationId },
|
|
642
|
-
)
|
|
643
|
-
if (company) profile.company = company
|
|
644
|
-
}
|
|
659
|
+
if (profile && company) profile.company = company
|
|
645
660
|
}
|
|
646
661
|
},
|
|
647
662
|
], { transaction: true })
|
|
@@ -757,6 +757,12 @@ const updatePaymentCommand: CommandHandler<
|
|
|
757
757
|
}
|
|
758
758
|
payment.updatedAt = new Date()
|
|
759
759
|
|
|
760
|
+
// Persist the payment scalar changes before any allocation query below.
|
|
761
|
+
// MikroORM discards pending scalar mutations when a find() runs on the
|
|
762
|
+
// same EntityManager before they are flushed, which would otherwise drop
|
|
763
|
+
// the updated amount/reference when allocations are (re)synced.
|
|
764
|
+
await tx.flush()
|
|
765
|
+
|
|
760
766
|
if (input.allocations !== undefined) {
|
|
761
767
|
const existingAllocations = await findWithDecryption(tx, SalesPaymentAllocation, { payment }, {}, { tenantId: payment.tenantId, organizationId: payment.organizationId })
|
|
762
768
|
existingAllocations.forEach((allocation) => tx.remove(allocation))
|
|
@@ -816,6 +822,29 @@ const updatePaymentCommand: CommandHandler<
|
|
|
816
822
|
})
|
|
817
823
|
tx.persist(entity)
|
|
818
824
|
}
|
|
825
|
+
} else if (input.amount !== undefined || input.currencyCode !== undefined) {
|
|
826
|
+
// The caller changed the payment amount/currency without managing
|
|
827
|
+
// allocations explicitly. A simple payment carries a single
|
|
828
|
+
// auto-created allocation covering the full amount (see create); keep
|
|
829
|
+
// it in sync so recomputeOrderPaymentTotals — which sums allocations in
|
|
830
|
+
// preference to the payment amount — does not report a stale paid total
|
|
831
|
+
// after a payment edit (#2455).
|
|
832
|
+
const existingAllocations = await findWithDecryption(tx, SalesPaymentAllocation, { payment }, {}, { tenantId: payment.tenantId, organizationId: payment.organizationId })
|
|
833
|
+
const paymentOrderId =
|
|
834
|
+
(typeof payment.order === 'string' ? payment.order : payment.order?.id) ?? null
|
|
835
|
+
const isDefaultAllocation = (allocation: SalesPaymentAllocation): boolean => {
|
|
836
|
+
const allocationOrderId =
|
|
837
|
+
typeof allocation.order === 'string' ? allocation.order : allocation.order?.id ?? null
|
|
838
|
+
const allocationInvoiceId =
|
|
839
|
+
typeof allocation.invoice === 'string' ? allocation.invoice : allocation.invoice?.id ?? null
|
|
840
|
+
return allocationInvoiceId === null && allocationOrderId === paymentOrderId
|
|
841
|
+
}
|
|
842
|
+
if (existingAllocations.length === 1 && isDefaultAllocation(existingAllocations[0])) {
|
|
843
|
+
const [allocation] = existingAllocations
|
|
844
|
+
allocation.amount = toNumericString(toNumber(payment.amount)) ?? '0'
|
|
845
|
+
allocation.currencyCode = payment.currencyCode
|
|
846
|
+
tx.persist(allocation)
|
|
847
|
+
}
|
|
819
848
|
}
|
|
820
849
|
})
|
|
821
850
|
|
|
@@ -94,14 +94,23 @@ function buildBaseLineResult(line: SalesLineSnapshot): SalesLineCalculationResul
|
|
|
94
94
|
const netSubtotalBeforeDiscount = toNumber(unitNet, 0) * quantity
|
|
95
95
|
const discountTotal = Math.min(Math.max(discountPerUnit * quantity, 0), netSubtotalBeforeDiscount)
|
|
96
96
|
const netSubtotal = Math.max(netSubtotalBeforeDiscount - discountTotal, 0)
|
|
97
|
-
const taxAmount
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
const explicitTaxAmount = line.taxAmount !== null && line.taxAmount !== undefined
|
|
98
|
+
let taxAmount = explicitTaxAmount
|
|
99
|
+
? toNumber(line.taxAmount, 0)
|
|
100
|
+
: round(netSubtotal * Math.max(taxRate, 0))
|
|
101
101
|
const grossSubtotal =
|
|
102
102
|
line.totalGrossAmount !== null && line.totalGrossAmount !== undefined
|
|
103
103
|
? toNumber(line.totalGrossAmount, 0)
|
|
104
104
|
: round(netSubtotal + taxAmount)
|
|
105
|
+
// When tax was not supplied explicitly and the rate-derived tax is zero but
|
|
106
|
+
// the gross total already embeds tax (gross > net) — e.g. a tax-class-priced
|
|
107
|
+
// line whose resolved rate was not persisted — derive the tax from the
|
|
108
|
+
// net/gross delta so the document-level tax total is not silently zeroed
|
|
109
|
+
// while per-line net/gross stay correct (#2457).
|
|
110
|
+
if (!explicitTaxAmount && taxAmount <= 0) {
|
|
111
|
+
const grossNetDelta = round(grossSubtotal - netSubtotal)
|
|
112
|
+
if (grossNetDelta > 0) taxAmount = grossNetDelta
|
|
113
|
+
}
|
|
105
114
|
|
|
106
115
|
return {
|
|
107
116
|
line,
|
|
@@ -356,6 +365,25 @@ class SalesCalculationRegistry {
|
|
|
356
365
|
})
|
|
357
366
|
}
|
|
358
367
|
|
|
368
|
+
// Payment totals (paid/refunded) are authoritative inputs, not derived from
|
|
369
|
+
// lines or adjustments. Totals calculators rebuild the document result from
|
|
370
|
+
// lines+adjustments and would otherwise reset paid/refunded to 0 (and
|
|
371
|
+
// outstanding back to the full grand total), producing a stale paid/
|
|
372
|
+
// outstanding display after a payment. Re-apply the input totals last and
|
|
373
|
+
// recompute outstanding against the post-calculation grand total.
|
|
374
|
+
if (existingTotals) {
|
|
375
|
+
const paidTotalAmount = Math.max(toNumber(existingTotals.paidTotalAmount, 0), 0)
|
|
376
|
+
const refundedTotalAmount = Math.max(toNumber(existingTotals.refundedTotalAmount, 0), 0)
|
|
377
|
+
current.totals = {
|
|
378
|
+
...current.totals,
|
|
379
|
+
paidTotalAmount,
|
|
380
|
+
refundedTotalAmount,
|
|
381
|
+
outstandingAmount: round(
|
|
382
|
+
Math.max(current.totals.grandTotalGrossAmount - paidTotalAmount + refundedTotalAmount, 0)
|
|
383
|
+
),
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
359
387
|
return current
|
|
360
388
|
}
|
|
361
389
|
}
|