feat(pl): 装送货段增加同市标记,并同步额度证件与计划表单。

同市易制毒段只校验购买证;同步 Web/H5 表单、额度统计展示与相关回归测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-11-12 16:34:58 +08:00
1 parent d58f6ea5bd
commit 522b4be705
40 files changed
+576 -107

No files matched your search

@@ -2,7 +2,8 @@ import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
canManualToggleStopDibang,
resolveStopDibangDefaults
resolveStopDibangDefaults,
toxicStopCertBindingMessage
} from './pl-plan-stop-form'
import { isPlDibangOptionalVehiclePlate } from './pl-internal-customer'
@@ -23,6 +24,28 @@ describe('isPlDibangOptionalVehiclePlate', () => {
})
})
describe('toxicStopCertBindingMessage', () => {
it('same-city stop only requires the purchase certificate', () => {
assert.equal(
toxicStopCertBindingMessage([
{
contractId: 'c1',
contractNo: 'HT-1',
sameCity: 1,
certificates: [{ certType: 'purchase' }]
}
]),
''
)
assert.equal(
toxicStopCertBindingMessage([
{ contractId: 'c1', contractNo: 'HT-1', sameCity: true, certificates: [] }
]),
'合同HT-1的同市段尚未绑定购买证,请先到合同中绑定后再安排运输'
)
})
})
describe('resolveStopDibangDefaults + canManualToggleStopDibang', () => {
it('internal + normal vehicle: auto-check, not manual', () => {
assert.deepEqual(resolveStopDibangDefaults(INTERNAL, 'load', '皖A11111'), {
+55
View File
@@ -102,6 +102,8 @@ export interface PlanLoadForm {
notes: string
generateDibang: number
dibangDirection: string
/** 1 时本段易制毒只校验购买证 */
sameCity: number
contracts: PlanContractLink[]
products: PlanStopProductFormRow[]
}
@@ -119,6 +121,8 @@ export interface PlanDeliveryForm {
notes: string
generateDibang: number
dibangDirection: string
/** 1 时本段易制毒只校验购买证 */
sameCity: number
contracts: PlanContractLink[]
products: PlanStopProductFormRow[]
}
@@ -146,6 +150,49 @@ export function resolveStopDibangDefaults(
return { generateDibang: 1, dibangDirection: 'inbound' }
}
/** 安排前检查易制毒段的合同证件。同市段只要求购买证。 */
export function toxicStopCertBindingMessage(
rows: Array<{
partyName?: string | null
sameCity?: boolean | number | null
certificates?: { certType?: string | null }[] | null
contractId?: string | null
contractNo?: string | null
productName?: string | null
sourceStopProductId?: string | null
}>
): string {
const missingBoth = new Map<string, string>()
const missingPurchase = new Map<string, string>()
for (const row of rows) {
if (isPlInternalCustomerName(row.partyName)) continue
const certificates = row.certificates || []
const hasPurchase = certificates.some((cert) => cert.certType === 'purchase')
const hasTransport = certificates.some((cert) => cert.certType === 'transport')
const sameCity = row.sameCity === true || Number(row.sameCity) === 1
const key = row.contractId || row.contractNo || row.productName || row.sourceStopProductId || ''
const label = row.contractNo || '未编号合同'
if (sameCity) {
if (!hasPurchase) missingPurchase.set(key, label)
continue
}
if (hasPurchase && hasTransport) continue
missingBoth.set(key, label)
}
const parts: string[] = []
if (missingBoth.size) {
parts.push(
`合同${[...missingBoth.values()].join('、')}尚未绑定购买证和运输证,请先到合同中绑定后再安排运输`
)
}
if (missingPurchase.size) {
parts.push(
`合同${[...missingPurchase.values()].join('、')}的同市段尚未绑定购买证,请先到合同中绑定后再安排运输`
)
}
return parts.join(';')
}
/** 仅「内部客户 + 地磅可选车」允许用户手动勾选地磅单 */
export function canManualToggleStopDibang(
customerName?: string | null,
@@ -433,6 +480,7 @@ export function createEmptyLoadForm(): PlanLoadForm {
notes: '',
generateDibang: 0,
dibangDirection: 'none',
sameCity: 0,
contracts: [],
products: []
}
@@ -451,6 +499,7 @@ export function createEmptyDeliveryForm(seq = 1): PlanDeliveryForm {
notes: '',
generateDibang: 0,
dibangDirection: 'none',
sameCity: 0,
contracts: [],
products: []
}
@@ -540,6 +589,7 @@ export function mapLoadFromApi(
notes: load.notes || '',
generateDibang: dibang.generateDibang,
dibangDirection: dibang.dibangDirection,
sameCity: Number(load.sameCity) === 1 ? 1 : 0,
contracts: (load.contracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
@@ -574,6 +624,7 @@ export function mapDeliveriesFromApi(
notes: row.notes || '',
generateDibang: dibang.generateDibang,
dibangDirection: dibang.dibangDirection,
sameCity: Number(row.sameCity) === 1 ? 1 : 0,
contracts: (row.contracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
@@ -617,6 +668,7 @@ export function mapLegacyPlanToStops(plan: Plan): {
notes: plan.pickupNotes || '',
generateDibang: loadDefaults.generateDibang,
dibangDirection: loadDefaults.dibangDirection,
sameCity: 0,
contracts: (plan.supplierContracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
@@ -636,6 +688,7 @@ export function mapLegacyPlanToStops(plan: Plan): {
notes: plan.destinationNotes || '',
generateDibang: deliveryDefaults.generateDibang,
dibangDirection: deliveryDefaults.dibangDirection,
sameCity: 0,
contracts: (plan.demanderContracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
@@ -900,6 +953,7 @@ export function buildLoadPayload(load: PlanLoadForm) {
notes: load.notes || undefined,
generateDibang: load.generateDibang,
dibangDirection: load.dibangDirection,
sameCity: load.sameCity === 1 ? 1 : 0,
contracts: load.contracts,
products: load.products.map(buildStopProductPayload)
}
@@ -919,6 +973,7 @@ export function buildDeliveriesPayload(deliveries: PlanDeliveryForm[]) {
notes: row.notes || undefined,
generateDibang: row.generateDibang,
dibangDirection: row.dibangDirection,
sameCity: row.sameCity === 1 ? 1 : 0,
contracts: row.contracts,
products: row.products.map(buildStopProductPayload)
}))