feat(pl): 实现合同产品用证与额度结转以支持购买证和运输证全流程管理。

This commit is contained in:
wuzilong committed 2026-11-01 18:56:05 +08:00
1 parent cb0344498a
commit fb866c7fa7
104 files changed
+10187 -3833

No files matched your search

+11
View File
@@ -1,3 +1,4 @@
import type { QuotaConfiguration, ManualQuotaQuantity } from './quota-cert'
/**
* 运输计划管理 API
*/
@@ -858,6 +859,13 @@ export interface PlanArchiveFeedbacksBlock {
}
export interface PlanArchiveBody {
operationId?: string
quotaAllowSupplement?: boolean
quotaConfiguration?: QuotaConfiguration
quotaManualActuals?: ManualQuotaQuantity[]
quotaReason?: string
quotaEvidenceUrls?: string
quotaSourceNote?: string
products?: PlanArchiveProductItem[]
load?: PlanArchiveLoadBlock
deliveries?: PlanArchiveDeliveryBlock[]
@@ -1165,6 +1173,9 @@ export interface TransportStopDibangOption {
}
export interface TransportArrange {
operationId?: string
quotaConfiguration?: QuotaConfiguration
quotaManualActuals?: ManualQuotaQuantity[]
vehicleId: string
driverId?: string
escortId?: string
+270
View File
@@ -0,0 +1,270 @@
import request, { pickBxData } from '@/utils/http/bx'
export type CertType = 'purchase' | 'transport'
export interface PurchaseReference {
id?: string
referenceType: 'purchase_permit' | 'purchase_filing' | 'export_permit' | 'unknown'
referenceNo: string
purchaseCertId?: string | null
remark?: string | null
orderNum?: number | null
}
export interface CertBody {
certNo: string
issueDate?: string | null
productNameOnCert: string
totalQty: string | number
quantityUnit: string
validFrom?: string | null
validTo?: string | null
validUseText: string
entryMode: 'manual' | 'image_parse'
attachmentUrls?: string | null
status?: 'draft' | 'suspended' | 'void'
remark?: string | null
orderNum?: number | null
buyerName?: string | null
buyerAddress?: string | null
buyerLegalRepresentative?: string | null
buyerPhone?: string | null
sellerName?: string | null
sellerAddress?: string | null
sellerLegalRepresentative?: string | null
sellerPhone?: string | null
purchasePurpose?: string | null
shipperName?: string | null
shipperAddress?: string | null
shipperLegalRepresentative?: string | null
shipperPhone?: string | null
packagingText?: string | null
carrierName?: string | null
carrierAddressPhone?: string | null
transportMode?: string | null
vehicleNumbersJson?: string[]
routeText?: string | null
consigneeName?: string | null
applicantName?: string | null
purchaseRefs?: PurchaseReference[]
}
export interface QuotaCert extends Omit<CertBody, 'status'> {
id: string
status: 'draft' | 'active' | 'suspended' | 'void'
revision: number
reservedQty: string | number
usedQty: string | number
availableQty: string | number
verifiedTime?: string | null
openingConfirmed: boolean
usagePolicy: 'once' | 'multiple' | 'needs_confirmation'
createTime?: string
}
export interface ScopeProduct {
id?: string
contractProductId: string
scopeStatus?: 'active' | 'inactive'
remark?: string | null
orderNum?: number | null
}
export interface CertBinding {
id?: string
certType: CertType
certId: string
linkStatus?: 'active' | 'inactive'
products: ScopeProduct[]
scopeRevision?: number
remark?: string | null
orderNum?: number | null
}
export interface ContractOption {
id: string
contractNo: string
contractName: string
contractType?: string
}
export interface ContractProduct {
id: string
productId: string
productName: string
model?: string
unit?: string
quantity?: string | number
}
export interface LegalDocument {
id: string
planNo: string
archiveStatus: string
transportStatus: string
products: {
id: string
productId: string
productName: string
model?: string
legalUseCertUrl: string | null
documentStatus: 'uploaded' | 'pending'
contexts?: {
stopType: string
stopProductId: string
partyName?: string
address?: string
contractId?: string
contractNo?: string
contractProductId?: string
}[]
}[]
}
const base = '/transport/quota-certs'
export const quotaCertApi = {
list: (kind: CertType, params: { keyword?: string; status?: string } = {}) =>
request.get(`${base}/${kind}`, { params }).then(pickBxData<QuotaCert[]>),
get: (kind: CertType, id: string) =>
request.get(`${base}/${kind}/${id}`).then(pickBxData<QuotaCert>),
save: (kind: CertType, body: CertBody, id?: string) =>
(id ? request.put(`${base}/${kind}/${id}`, body) : request.post(`${base}/${kind}`, body)).then(
pickBxData<QuotaCert>
),
remove: (kind: CertType, id: string) => request.delete(`${base}/${kind}/${id}`),
confirm: (
kind: CertType,
id: string,
body: {
operationId: string
usedQty: string
singleUseConsumed: boolean
reason: string
sourceNote?: string
evidenceUrls?: string
}
) => request.post(`${base}/${kind}/${id}/confirm`, body).then(pickBxData<QuotaCert>),
verify: (kind: CertType, id: string, body: { operationId: string; reason: string }) =>
request.post(`${base}/${kind}/${id}/verify`, body).then(pickBxData<QuotaCert>),
recognize: (kind: CertType, attachmentUrl: string) =>
request.post(`${base}/${kind}/recognize`, { attachmentUrl }, { timeout: 90000 }).then(
pickBxData<{
candidates: Partial<CertBody>
invalidFields: Record<string, string>
requiresConfirmation: boolean
}>
),
contracts: (keyword = '') =>
request.get(`${base}/contracts`, { params: { keyword } }).then(pickBxData<ContractOption[]>),
products: (id: string) =>
request.get(`${base}/contracts/${id}/products`).then(pickBxData<ContractProduct[]>),
bindings: (id: string) =>
request.get(`${base}/contracts/${id}/bindings`).then(pickBxData<CertBinding[]>),
saveBindings: (id: string, bindings: CertBinding[]) =>
request.put(`${base}/contracts/${id}/bindings`, { bindings }).then(pickBxData<CertBinding[]>),
certificateContracts: (kind: CertType, id: string) =>
request.get(`${base}/${kind}/${id}/contracts`).then(pickBxData<ContractOption[]>)
}
const legalBase = '/transport/legal-use-documents'
export const legalDocumentApi = {
list: (params: { keyword?: string; status?: string }) =>
request.get(legalBase, { params }).then(pickBxData<LegalDocument[]>),
get: (id: string) => request.get(`${legalBase}/${id}`).then(pickBxData<LegalDocument>),
save: (id: string, products: { id: string; legalUseCertUrl?: string | null }[]) =>
request.patch(`${legalBase}/${id}`, { products }).then(pickBxData<LegalDocument>)
}
export interface QuotaSourceOption {
sourceStopType: 'load' | 'delivery'
sourceStopProductId: string
planProductId: string
productName: string
model?: string
quantity: string | number
unit: string
contractId: string
contractProductId: string
partyName?: string
}
export interface QuotaSourcePair {
sourceStopType: 'load' | 'delivery'
sourceStopProductId: string
relatedStopProductIds: string[]
}
export interface ManualQuotaQuantity {
permitKind: CertType
sourceId: string
permitId: string
sourceQuantity: string | number
permitQuantity: string | number
note: string
}
export interface QuotaCondition {
certType: CertType
certId: string
certRevision: number
sourceFingerprint: string
partiesConfirmed: boolean
routeConfirmed: boolean
note: string
}
export interface QuotaConfiguration {
sourceMapping?: QuotaSourcePair[] | null
purchaseCertIds: string[]
transportCertIds: string[]
manualAllocations: ManualQuotaQuantity[]
conditions: QuotaCondition[]
}
export interface PlanQuota {
planId: string
planNo: string
licensePlate?: string
canSaveConfiguration?: boolean
configuration: Partial<QuotaConfiguration>
sourceOptions: QuotaSourceOption[]
usages: Record<string, any>[]
}
export interface QuotaContext {
sourceFingerprint: string
sources: {
source_stop_type: string
source_stop_product_id: string
quantity: string
unit: string
snapshot: { stops: Record<string, any>[]; vehicle?: string; trailer?: string }
}[]
}
export interface QuotaPreview extends Partial<QuotaContext> {
status: string
reasons?: string[]
diagnostics?: {
certType: CertType
sourceIds: string[]
unit: string
demand: string
availableUpperBound: string | null
gapLowerBound: string | null
reason: string
}[]
purchaseAllocations?: Record<string, any>[]
transportAllocations?: Record<string, any>[]
}
export const planQuotaApi = {
get: (id: string) => request.get(`${base}/plans/${id}/usage`).then(pickBxData<PlanQuota>),
context: (id: string, body: QuotaConfiguration & QuotaPreviewVehicle) =>
request.post(`${base}/plans/${id}/context`, body).then(pickBxData<QuotaContext>),
preview: (id: string, body: QuotaConfiguration & QuotaPreviewVehicle) =>
request.post(`${base}/plans/${id}/preview`, body).then(pickBxData<QuotaPreview>),
save: (id: string, body: QuotaConfiguration) =>
request.put(`${base}/plans/${id}/configuration`, body).then(pickBxData<QuotaConfiguration>)
}
export interface QuotaPreviewVehicle {
targetLicensePlate?: string
targetTrailerNo?: string | null
}
export interface CertTotalAdjustment {
operationId: string
totalQty: string
reason: string
sourceNote: string
evidenceUrls: string
}
export const quotaAdjustmentApi = {
logs: (kind: CertType, id: string) =>
request.get(`${base}/${kind}/${id}/logs`).then(pickBxData<Record<string, any>[]>),
total: (kind: CertType, id: string, body: CertTotalAdjustment) =>
request.post(`${base}/${kind}/${id}/total-adjust`, body)
}