feat(pl): 吨千克克和升毫升按固定比例折算用证量。
同质量或体积单位不再要求手填系数,普通 http 页面也能生成幂等操作号。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
e7f942b399
commit
da3ec62719
21 files changed
+277
-43
No files matched your search
@@ -286,6 +286,7 @@
|
||||
import { usePlFreightCalc, resolveFreightUnitLabel } from '@/composables/usePlFreightCalc'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { pickBxData } from '@/utils/http/bx'
|
||||
import { randomOperationId } from '@/utils/random-id'
|
||||
import {
|
||||
mapDeliveriesFromApi,
|
||||
mapLegacyPlanToStops,
|
||||
@@ -1641,7 +1642,8 @@
|
||||
try {
|
||||
const archiveBody = { ...frozenBody, confirmOverage }
|
||||
const signature = JSON.stringify({ planId: targetId, archiveBody })
|
||||
if (signature !== quotaArchiveAttempt.signature) quotaArchiveAttempt = { signature, id: crypto.randomUUID() }
|
||||
if (signature !== quotaArchiveAttempt.signature)
|
||||
quotaArchiveAttempt = { signature, id: randomOperationId() }
|
||||
const response = await planApi.archive(
|
||||
targetId,
|
||||
{ ...archiveBody, operationId: quotaArchiveAttempt.id },
|
||||
|
||||
@@ -663,6 +663,7 @@
|
||||
} from '@/api/pl'
|
||||
import { planQuotaApi, type QuotaSourceOption } from '@/api/pl/quota-cert'
|
||||
import { toxicArrangeBlockMessage } from '@/utils/pl-quota-cert-waiver'
|
||||
import { randomOperationId } from '@/utils/random-id'
|
||||
import { arrangeBlockedMessage, isExecutionStarted } from '@/utils/pl-plan-action-guards'
|
||||
import {
|
||||
usePlRemoteCarrierOptions,
|
||||
@@ -2720,7 +2721,7 @@
|
||||
})
|
||||
|
||||
const signature = JSON.stringify({ planId, payload })
|
||||
if (signature !== arrangeAttempt.signature) arrangeAttempt = { signature, id: crypto.randomUUID() }
|
||||
if (signature !== arrangeAttempt.signature) arrangeAttempt = { signature, id: randomOperationId() }
|
||||
try {
|
||||
await submitPlanArrange(
|
||||
planId,
|
||||
|
||||
@@ -36,6 +36,22 @@ export function sameCertUnit(left?: string | null, right?: string | null) {
|
||||
return normalizeProductUnitValue(planUnit) === normalizeProductUnitValue(certUnit)
|
||||
}
|
||||
|
||||
const MASS_IN_GRAMS: Record<string, number> = { g: 1, kg: 1000, dun: 1000000 }
|
||||
const VOLUME_IN_MILLILITRES: Record<string, number> = { hs: 1, sheng: 1000 }
|
||||
|
||||
/** 与后端 standard_conversion_ratio 同一口径:吨/千克/克、升/毫升不用手填系数。 */
|
||||
export function hasStandardUnitConversion(left?: string | null, right?: string | null) {
|
||||
const planUnit = String(left || '').trim()
|
||||
const certUnit = String(right || '').trim()
|
||||
if (!planUnit || !certUnit) return false
|
||||
const source = normalizeProductUnitValue(planUnit)
|
||||
const permit = normalizeProductUnitValue(certUnit)
|
||||
if (!source || !permit || source === permit) return false
|
||||
return [MASS_IN_GRAMS, VOLUME_IN_MILLILITRES].some(
|
||||
(table) => source in table && permit in table
|
||||
)
|
||||
}
|
||||
|
||||
export function coefficientFromQuantities(source: unknown, permit: unknown) {
|
||||
const sourceQty = Number(source)
|
||||
const permitQty = Number(permit)
|
||||
@@ -62,7 +78,13 @@ export function unitConversions(plan: PlanQuota | undefined, sources: QuotaSourc
|
||||
row.sourceStopProductId === source.sourceStopProductId
|
||||
)?.certificates || []
|
||||
return certs
|
||||
.filter((cert) => cert.quantityUnit && source.unit && !sameCertUnit(source.unit, cert.quantityUnit))
|
||||
.filter(
|
||||
(cert) =>
|
||||
cert.quantityUnit &&
|
||||
source.unit &&
|
||||
!sameCertUnit(source.unit, cert.quantityUnit) &&
|
||||
!hasStandardUnitConversion(source.unit, cert.quantityUnit)
|
||||
)
|
||||
.map((cert) => ({
|
||||
sourceId: `${source.sourceStopType}:${source.sourceStopProductId}`,
|
||||
source,
|
||||
|
||||
@@ -78,6 +78,7 @@ export const DEFAULT_PRODUCT_UNIT_VALUE = 'dun'
|
||||
|
||||
/** 展示文案 → 字典值;避免「吨」与合同行 dun 混用导致保存校验失败 */
|
||||
const PRODUCT_UNIT_LABEL_TO_VALUE: Record<string, string> = {
|
||||
克: 'g',
|
||||
千克: 'kg',
|
||||
吨: 'dun',
|
||||
升: 'sheng',
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
type RandomSource = {
|
||||
randomUUID?: () => string
|
||||
getRandomValues?: (bytes: Uint8Array) => Uint8Array
|
||||
}
|
||||
|
||||
/** 幂等操作号。安全上下文用 randomUUID;http 页面没有该接口时改用 getRandomValues。 */
|
||||
export function randomOperationId(source?: RandomSource): string {
|
||||
const cryptoSource = source === undefined ? globalThis.crypto : source
|
||||
if (typeof cryptoSource?.randomUUID === 'function') return cryptoSource.randomUUID()
|
||||
const bytes = new Uint8Array(16)
|
||||
if (typeof cryptoSource?.getRandomValues === 'function') cryptoSource.getRandomValues(bytes)
|
||||
else {
|
||||
for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256)
|
||||
}
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
||||
import { quotaCertApi, type CertType, type QuotaCert } from '@/api/pl/quota-cert'
|
||||
import { randomOperationId } from '@/utils/random-id'
|
||||
import './cert-mobile.scss'
|
||||
|
||||
const props = defineProps<{ row?: QuotaCert | null }>()
|
||||
@@ -57,7 +58,7 @@
|
||||
watch(
|
||||
() => props.row?.id,
|
||||
() => {
|
||||
opening.operationId = crypto.randomUUID()
|
||||
opening.operationId = randomOperationId()
|
||||
opening.usedQty = '0'
|
||||
opening.singleUseConsumed = false
|
||||
opening.reason = ''
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
} from '@/api/pl/quota-cert'
|
||||
import { formatCertQty } from '@/views/pl/quota-cert/model'
|
||||
import { getProductUnitLabel } from '@/utils/crm-product-display'
|
||||
import { randomOperationId } from '@/utils/random-id'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import './cert-mobile.scss'
|
||||
|
||||
@@ -131,7 +132,7 @@
|
||||
return
|
||||
}
|
||||
const signature = JSON.stringify({ id: cert.value.id, ...body })
|
||||
if (signature !== attempt.signature) attempt = { signature, id: crypto.randomUUID() }
|
||||
if (signature !== attempt.signature) attempt = { signature, id: randomOperationId() }
|
||||
saving.value = true
|
||||
try {
|
||||
await quotaAdjustmentApi.total(props.kind, cert.value.id, { ...body, operationId: attempt.id })
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
import { formatCertQty } from './model'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { getProductUnitLabel } from '@/utils/crm-product-display'
|
||||
import { randomOperationId } from '@/utils/random-id'
|
||||
function inputQty(value: unknown) {
|
||||
const text = formatCertQty(value)
|
||||
return text === '—' ? '' : text
|
||||
@@ -144,7 +145,7 @@
|
||||
const id = cert.value.id,
|
||||
type = kind.value,
|
||||
signature = JSON.stringify({ id, type, ...body })
|
||||
if (signature !== attempt.signature) attempt = { signature, id: crypto.randomUUID() }
|
||||
if (signature !== attempt.signature) attempt = { signature, id: randomOperationId() }
|
||||
saving.value = true
|
||||
try {
|
||||
await quotaAdjustmentApi.total(type, id, { ...body, operationId: attempt.id })
|
||||
|
||||
@@ -607,6 +607,7 @@
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { getProductUnitLabel } from '@/utils/crm-product-display'
|
||||
import { randomOperationId } from '@/utils/random-id'
|
||||
import { fetchGetMenuCodes } from '@/api/auth'
|
||||
import {
|
||||
quotaCertApi,
|
||||
@@ -1573,7 +1574,7 @@
|
||||
function openConfirm(row: QuotaCert) {
|
||||
confirmRow.value = row
|
||||
Object.assign(opening, {
|
||||
operationId: crypto.randomUUID(),
|
||||
operationId: randomOperationId(),
|
||||
usedQty: '0',
|
||||
singleUseConsumed: false,
|
||||
reason: '',
|
||||
|
||||
Reference in new issue
Block a user