32 lines
2.7 KiB
TypeScript
32 lines
2.7 KiB
TypeScript
import type { FleetQuoteDraft, FleetQuoteKind } from './api/fleetQuotes'
|
|
export const fleetQuoteCsvHeader = ['发货城市', '目的仓码', '距离(mile)', '币种', '整车自提', '整车派送', '散板派送', '生效日', '失效日', '备注']
|
|
export function parseFleetQuoteCsv(text: string, kind: FleetQuoteKind, partyId: string): FleetQuoteDraft[] {
|
|
if (text.length > 500000) throw new Error('CSV不能超过500KB')
|
|
const rows: string[][] = []; let row: string[] = [], cell = '', quoted = false
|
|
const input = text.replace(/^\uFEFF/, '')
|
|
for (let i = 0; i < input.length; i++) {
|
|
const c = input[i]
|
|
if (c === '"') { if (quoted && input[i + 1] === '"') { cell += '"'; i++ } else quoted = !quoted }
|
|
else if (!quoted && (c === ',' || c === '\n')) { row.push(cell.trim()); cell = ''; if (c === '\n') { rows.push(row); row = [] } }
|
|
else if (!quoted && c === '\r') continue
|
|
else cell += c
|
|
}
|
|
if (quoted) throw new Error('CSV引号未闭合')
|
|
if (cell || row.length) { row.push(cell.trim()); rows.push(row) }
|
|
if (rows.shift()?.join('|') !== fleetQuoteCsvHeader.join('|')) throw new Error('CSV表头不匹配,请下载模板')
|
|
const data = rows.filter(r => r.some(Boolean))
|
|
if (!data.length || data.length > 200) throw new Error('每次导入1至200条报价')
|
|
return data.map((r, index) => {
|
|
const fail = (message: string): never => { throw new Error(`第${index + 2}行:${message}`) }
|
|
if (r.length !== 10 || !r[0] || !r[1]) fail('请填写发货城市、目的仓码,并核对列数')
|
|
const amount = (v: string | undefined): number | null => { if (!v) return null; if (!/^\d{1,10}(\.\d{1,2})?$/.test(v)) return fail('报价须为非负金额,最多两位小数'); return Number(v) }
|
|
if (!/^\d{1,7}(\.\d)?$/.test(r[2] || '')) fail('请填写非负距离,最多一位小数')
|
|
if (r[3] !== 'USD' && r[3] !== 'CNY') fail('币种仅支持USD或CNY')
|
|
const date = (v: string | undefined): string => { if (!v || !/^\d{4}-\d{2}-\d{2}$/.test(v) || Number.isNaN(Date.parse(v)) || new Date(v).toISOString().slice(0, 10) !== v) return fail('日期须为有效的YYYY-MM-DD'); return v }
|
|
const draft: FleetQuoteDraft = { kind, partyId, originCity: r[0]!, destinationCode: r[1]!, distanceMiles: Number(r[2]), currency: r[3] as 'USD' | 'CNY', selfPickupPrice: amount(r[4]), fullDeliveryPrice: amount(r[5]), looseDeliveryPrice: amount(r[6]), validFrom: date(r[7]), validTo: date(r[8]), remark: r[9] || null }
|
|
if (draft.validTo < draft.validFrom) fail('失效日不得早于生效日')
|
|
if (draft.selfPickupPrice == null && draft.fullDeliveryPrice == null && draft.looseDeliveryPrice == null) fail('至少填写一种价格')
|
|
return draft
|
|
})
|
|
}
|