feat: 入库预报查看编辑合一、箱唛/SKU打印与订单改销售产品
All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m25s
All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m25s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
336f8657ee
commit
6ec41b2dbe
@ -2222,6 +2222,19 @@ export async function postOrderUpdate(body: Record<string, any>): Promise<any> {
|
||||
return res.data?.data ?? res.data?.Data ?? res.data
|
||||
}
|
||||
|
||||
/** POST /order/change-product — 改销售产品并自动重排货/重算费 */
|
||||
export async function postOrderChangeProduct(body: {
|
||||
id: number | string
|
||||
productCode: string
|
||||
}): Promise<any> {
|
||||
const res = await axiosInstance.post(orderUrl('/order/change-product'), {
|
||||
id: Number(body.id),
|
||||
productCode: String(body.productCode ?? '').trim(),
|
||||
})
|
||||
assertBusinessOk(res.data)
|
||||
return res.data?.data ?? res.data?.Data ?? res.data
|
||||
}
|
||||
|
||||
/** POST /order/weight/update */
|
||||
export async function postOrderWeightUpdate(body: {
|
||||
id: number | string
|
||||
|
||||
@ -26,6 +26,22 @@ export const deleteWarehouseSku = (id: number) => postOk('/warehouse/sku/delete'
|
||||
export const bindWarehouseSkuMapping = (body: Record<string, unknown>) =>
|
||||
postOk('/warehouse/sku/bind-mapping', body)
|
||||
|
||||
/** POST /warehouse/sku/print-label → TemplateType=sku 单页 PDF */
|
||||
export async function postWarehouseSkuPrintLabel(body: {
|
||||
id: number
|
||||
customerId?: number
|
||||
}): Promise<{ printUrl: string; skuCode: string }> {
|
||||
const data = await postOk('/warehouse/sku/print-label', {
|
||||
id: body.id,
|
||||
customerId: body.customerId,
|
||||
})
|
||||
const d = (data ?? {}) as Record<string, unknown>
|
||||
return {
|
||||
printUrl: String(d.printUrl ?? d.PrintUrl ?? '').trim(),
|
||||
skuCode: String(d.skuCode ?? d.SkuCode ?? '').trim(),
|
||||
}
|
||||
}
|
||||
|
||||
/** 上传 SKU 图片:优先 COS 直传,落库存 key;返回 key + 预览 URL */
|
||||
export async function uploadWarehouseSkuImage(
|
||||
file: File,
|
||||
@ -119,6 +135,23 @@ export const confirmWarehouseInboundReceive = (body: Record<string, unknown>) =>
|
||||
export const getWarehouseInboundSummary = (params?: Record<string, unknown>) =>
|
||||
getData('/warehouse/inbound/summary', params)
|
||||
|
||||
/** POST /warehouse/inbound/print-labels → 多箱合并为一个 PDF */
|
||||
export async function postWarehouseInboundPrintLabels(body: {
|
||||
id: number
|
||||
customerId?: number
|
||||
}): Promise<{ printUrl: string; noticeNo: string; pageCount: number }> {
|
||||
const data = await postOk('/warehouse/inbound/print-labels', {
|
||||
id: body.id,
|
||||
customerId: body.customerId,
|
||||
})
|
||||
const d = (data ?? {}) as Record<string, unknown>
|
||||
return {
|
||||
printUrl: String(d.printUrl ?? d.PrintUrl ?? '').trim(),
|
||||
noticeNo: String(d.noticeNo ?? d.NoticeNo ?? '').trim(),
|
||||
pageCount: Number(d.pageCount ?? d.PageCount ?? 0) || 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Parcel
|
||||
export const getWarehouseParcelList = (body: Record<string, unknown>) => postList('/warehouse/parcel/list', body)
|
||||
export const getWarehouseParcelSummary = (params?: Record<string, unknown>) =>
|
||||
|
||||
@ -96,6 +96,7 @@ export const PERMISSION_ACTION_LABELS: Record<string, string> = {
|
||||
PackVersions: '打包版本',
|
||||
BinDetail: '库位明细',
|
||||
History: '出入库记录',
|
||||
PrintLabel: '打印箱唛',
|
||||
BatchPay: '批量支付',
|
||||
CreatePickup: '创建揽收',
|
||||
AddMapping: '添加映射',
|
||||
@ -644,6 +645,7 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
||||
{ code: 'Query', name: '查询' },
|
||||
{ code: 'Save', name: '新增' },
|
||||
{ code: 'Update', name: '编辑' },
|
||||
{ code: 'PrintLabel', name: '打印标签' },
|
||||
{ code: 'Delete', name: '删除' },
|
||||
]),
|
||||
B('Warehouse.Inventory', [
|
||||
@ -663,6 +665,7 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
||||
{ code: 'Save', name: '新增' },
|
||||
{ code: 'View', name: '详情' },
|
||||
{ code: 'ConfirmReceive', name: '确认收货' },
|
||||
{ code: 'PrintLabel', name: '打印箱唛' },
|
||||
{ code: 'Delete', name: '删除' },
|
||||
]),
|
||||
B('Warehouse.BinLocation', [
|
||||
|
||||
@ -213,6 +213,7 @@ export default {
|
||||
'warehouse.sku.modal.add': 'Add SKU',
|
||||
'warehouse.sku.modal.edit': 'Edit SKU',
|
||||
'warehouse.sku.actions.delete': 'Delete SKU',
|
||||
'warehouse.sku.actions.printLabel': 'Print Label',
|
||||
'warehouse.sku.message.loadFailed': 'Failed to load SKU list',
|
||||
'warehouse.sku.message.saveSuccess': 'Saved',
|
||||
'warehouse.sku.message.saveFailed': 'Save failed',
|
||||
@ -220,6 +221,8 @@ export default {
|
||||
'warehouse.sku.message.deleteFailed': 'Delete failed',
|
||||
'warehouse.sku.message.deleteConfirm': 'Delete {code}?',
|
||||
'warehouse.sku.message.required': 'SKU code and name are required',
|
||||
'warehouse.sku.message.printFailed': 'Failed to print SKU label',
|
||||
'warehouse.sku.message.printEmpty': 'No SKU label file available',
|
||||
'warehouse.inventory.table.warehouse': 'Warehouse',
|
||||
'warehouse.inventory.table.binName': 'Bin Name',
|
||||
'warehouse.inventory.table.skuCode': 'SKU Code',
|
||||
@ -409,7 +412,7 @@ export default {
|
||||
'warehouse.inbound.search.keyword': 'Notice / Mark',
|
||||
'warehouse.inbound.tip': 'Forecast inbound goods. Stock increases after receive & putaway. Status: Forecasted → Received → Completed.',
|
||||
'warehouse.inbound.tip.currentWarehouse': 'Notice belongs to the current warehouse; switch it from the top bar',
|
||||
'warehouse.inbound.tip.markReference': 'Usually the tracking number or master mark for receiving checks',
|
||||
'warehouse.inbound.tip.markReference': 'Optional; usually the tracking number or master mark for receiving checks',
|
||||
'warehouse.inbound.tip.inboundType': 'Normal or return inbound; used for later reporting',
|
||||
'warehouse.inbound.tip.boxSkuDetail': 'Maintain SKUs and planned qty per box; add one box or five at once',
|
||||
'warehouse.inbound.tip.markBoxNo': 'Per-box mark / box number (optional)',
|
||||
@ -419,7 +422,7 @@ export default {
|
||||
'warehouse.inbound.form.markBoxNoPlaceholder': 'Box mark (optional)',
|
||||
'warehouse.inbound.form.sku': 'SKU',
|
||||
'warehouse.inbound.form.plannedQty': 'Planned Qty',
|
||||
'warehouse.inbound.form.markPlaceholder': 'Usually the tracking number',
|
||||
'warehouse.inbound.form.markPlaceholder': 'Usually the tracking number (optional)',
|
||||
'warehouse.inbound.form.remarkPlaceholder': 'Optional',
|
||||
'warehouse.inbound.form.optional': 'Optional',
|
||||
'warehouse.inbound.form.boxSkuDetail': 'Boxes & SKU lines',
|
||||
@ -441,6 +444,8 @@ export default {
|
||||
'warehouse.inbound.actions.removeCurrentBox': 'Remove current box',
|
||||
'warehouse.inbound.actions.addSku': 'Add SKU line',
|
||||
'warehouse.inbound.modal.add': 'Add Inbound',
|
||||
'warehouse.inbound.modal.edit': 'Edit Inbound',
|
||||
'warehouse.inbound.modal.view': 'View Inbound',
|
||||
'warehouse.inbound.drawer.detail': 'Inbound Detail',
|
||||
'warehouse.inbound.box': 'Box',
|
||||
'warehouse.inbound.line.skuCode': 'SKU',
|
||||
@ -450,6 +455,9 @@ export default {
|
||||
'warehouse.inbound.form.selectBin': 'Select bin',
|
||||
'warehouse.inbound.actions.delete': 'Delete',
|
||||
'warehouse.inbound.actions.confirmReceive': 'Confirm Receive',
|
||||
'warehouse.inbound.actions.printLabel': 'Print Box Label',
|
||||
'warehouse.inbound.message.printFailed': 'Failed to print box label',
|
||||
'warehouse.inbound.message.printEmpty': 'No box label file available',
|
||||
'warehouse.inbound.modal.confirmReceive': 'Confirm Receive & Putaway',
|
||||
'warehouse.inbound.message.loadFailed': 'Failed to load inbound list',
|
||||
'warehouse.inbound.message.saveSuccess': 'Saved',
|
||||
@ -2533,6 +2541,8 @@ export default {
|
||||
'order.myOrders.drawer.editBasicTitle': 'Edit Order Details',
|
||||
'order.myOrders.drawer.editWeightTitle': 'Edit Weight & Dimensions',
|
||||
'order.myOrders.drawer.message.saveSuccess': 'Saved successfully',
|
||||
'order.myOrders.drawer.message.changeProductSuccess': 'Sales product updated',
|
||||
'order.myOrders.drawer.placeholder.productName': 'Select sales product',
|
||||
'order.myOrders.drawer.message.recalculateSuccess': 'Recalculated successfully',
|
||||
'order.myOrders.drawer.costItem': 'Cost Item',
|
||||
'order.myOrders.drawer.amount': 'Amount',
|
||||
@ -4248,6 +4258,7 @@ export default {
|
||||
'operation.printLabel.labelTemplate.inbound': 'Inbound Label',
|
||||
'operation.printLabel.labelTemplate.outbound': 'Outbound Label',
|
||||
'operation.printLabel.labelTemplate.sku': 'SKU Label',
|
||||
'operation.printLabel.labelTemplate.box': 'Box Mark Label',
|
||||
'operation.printLabel.preview.title': 'Label preview',
|
||||
'operation.printLabel.preview.empty': 'Scan a number to show order info; open Preview when needed',
|
||||
'operation.printLabel.preview.product': 'Product',
|
||||
|
||||
@ -214,6 +214,7 @@ export default {
|
||||
'warehouse.sku.modal.add': '新增 SKU',
|
||||
'warehouse.sku.modal.edit': '编辑 SKU',
|
||||
'warehouse.sku.actions.delete': '删除 SKU',
|
||||
'warehouse.sku.actions.printLabel': '打印标签',
|
||||
'warehouse.sku.message.loadFailed': '加载 SKU 列表失败',
|
||||
'warehouse.sku.message.saveSuccess': '保存成功',
|
||||
'warehouse.sku.message.saveFailed': '保存失败',
|
||||
@ -221,6 +222,8 @@ export default {
|
||||
'warehouse.sku.message.deleteFailed': '删除失败',
|
||||
'warehouse.sku.message.deleteConfirm': '确定删除 {code}?',
|
||||
'warehouse.sku.message.required': '请填写 SKU 编码和名称',
|
||||
'warehouse.sku.message.printFailed': '打印 SKU 标签失败',
|
||||
'warehouse.sku.message.printEmpty': '未获取到 SKU 标签文件',
|
||||
'warehouse.inventory.table.warehouse': '仓库',
|
||||
'warehouse.inventory.table.binName': '库位名称',
|
||||
'warehouse.inventory.table.skuCode': 'SKU 编码',
|
||||
@ -410,7 +413,7 @@ export default {
|
||||
'warehouse.inbound.search.keyword': '单号/唛头',
|
||||
'warehouse.inbound.tip': '预报到仓货物;收货上架后库存会增加。关注状态:预报 → 待入库 → 已完成。',
|
||||
'warehouse.inbound.tip.currentWarehouse': '预报归属当前仓库;可在顶部切换仓库',
|
||||
'warehouse.inbound.tip.markReference': '一般填物流快递单号或主唛头,便于到仓核对',
|
||||
'warehouse.inbound.tip.markReference': '选填;一般填物流快递单号或主唛头,便于到仓核对',
|
||||
'warehouse.inbound.tip.inboundType': '普通入库或退货入库,影响后续统计分类',
|
||||
'warehouse.inbound.tip.boxSkuDetail': '按箱维护 SKU 与计划数量;可加箱或一次加 5 箱',
|
||||
'warehouse.inbound.tip.markBoxNo': '单箱唛头/箱号,选填',
|
||||
@ -420,7 +423,7 @@ export default {
|
||||
'warehouse.inbound.form.markBoxNoPlaceholder': '箱唛/箱号(选填)',
|
||||
'warehouse.inbound.form.sku': 'SKU',
|
||||
'warehouse.inbound.form.plannedQty': '计划数量',
|
||||
'warehouse.inbound.form.markPlaceholder': '一般填写物流快递单号',
|
||||
'warehouse.inbound.form.markPlaceholder': '一般填写物流快递单号(选填)',
|
||||
'warehouse.inbound.form.remarkPlaceholder': '选填',
|
||||
'warehouse.inbound.form.optional': '选填',
|
||||
'warehouse.inbound.form.boxSkuDetail': '箱子与 SKU 明细',
|
||||
@ -442,6 +445,8 @@ export default {
|
||||
'warehouse.inbound.actions.removeCurrentBox': '删除当前箱',
|
||||
'warehouse.inbound.actions.addSku': '选择库内 SKU',
|
||||
'warehouse.inbound.modal.add': '新增入库预报',
|
||||
'warehouse.inbound.modal.edit': '编辑入库预报',
|
||||
'warehouse.inbound.modal.view': '查看入库预报',
|
||||
'warehouse.inbound.drawer.detail': '入库详情',
|
||||
'warehouse.inbound.box': '箱',
|
||||
'warehouse.inbound.line.skuCode': 'SKU',
|
||||
@ -451,6 +456,9 @@ export default {
|
||||
'warehouse.inbound.form.selectBin': '请选择库位',
|
||||
'warehouse.inbound.actions.delete': '删除预报',
|
||||
'warehouse.inbound.actions.confirmReceive': '确认收货',
|
||||
'warehouse.inbound.actions.printLabel': '打印箱唛',
|
||||
'warehouse.inbound.message.printFailed': '打印箱唛失败',
|
||||
'warehouse.inbound.message.printEmpty': '未获取到箱唛文件',
|
||||
'warehouse.inbound.modal.confirmReceive': '确认收货上架',
|
||||
'warehouse.inbound.message.loadFailed': '加载入库预报失败',
|
||||
'warehouse.inbound.message.saveSuccess': '保存成功',
|
||||
@ -2392,6 +2400,8 @@ export default {
|
||||
'order.myOrders.drawer.editBasicTitle': '编辑订单详情',
|
||||
'order.myOrders.drawer.editWeightTitle': '编辑件重尺',
|
||||
'order.myOrders.drawer.message.saveSuccess': '保存成功',
|
||||
'order.myOrders.drawer.message.changeProductSuccess': '销售产品已更新',
|
||||
'order.myOrders.drawer.placeholder.productName': '请选择销售产品',
|
||||
'order.myOrders.drawer.message.recalculateSuccess': '重算成功',
|
||||
'order.myOrders.drawer.costItem': '费用项',
|
||||
'order.myOrders.drawer.amount': '金额',
|
||||
@ -4273,6 +4283,7 @@ export default {
|
||||
'operation.printLabel.labelTemplate.inbound': '入库标签',
|
||||
'operation.printLabel.labelTemplate.outbound': '出库标签',
|
||||
'operation.printLabel.labelTemplate.sku': 'sku标签',
|
||||
'operation.printLabel.labelTemplate.box': '箱唛标签',
|
||||
'operation.printLabel.preview.title': '面单预览',
|
||||
'operation.printLabel.preview.empty': '扫描单号后显示订单信息,需要时可点预览查看面单',
|
||||
'operation.printLabel.preview.product': '销售产品',
|
||||
|
||||
@ -73,13 +73,27 @@ const EMPTY_LABEL_TEMPLATE = {
|
||||
|
||||
const initialTemplate = ref<unknown>(EMPTY_LABEL_TEMPLATE)
|
||||
|
||||
const BARCODE_FIELDS = new Set(['serviceNo', 'trackNo', 'clientNo', 'requestId'])
|
||||
const BARCODE_FIELDS = new Set([
|
||||
'serviceNo',
|
||||
'trackNo',
|
||||
'clientNo',
|
||||
'requestId',
|
||||
'noticeNo',
|
||||
'markReference',
|
||||
'markBoxNo',
|
||||
'boxSeqNo',
|
||||
'barcode',
|
||||
'skuCode',
|
||||
])
|
||||
const LONGTEXT_FIELDS = new Set([
|
||||
'recipientAddressFull',
|
||||
'senderAddressFull',
|
||||
'recipientStreet1',
|
||||
'recipientStreet2',
|
||||
'recipientStreet3',
|
||||
'skuSummary',
|
||||
'declareElement',
|
||||
'remark',
|
||||
])
|
||||
|
||||
const MOCK_BY_FIELD: Record<string, string> = {
|
||||
@ -93,6 +107,40 @@ const MOCK_BY_FIELD: Record<string, string> = {
|
||||
actualWeight: '1.25',
|
||||
salesProductName: '专线-普货',
|
||||
warehouseName: 'SZ-01',
|
||||
noticeNo: 'IN202610220001',
|
||||
markReference: 'SF1234567890',
|
||||
markBoxNo: 'BOX-001',
|
||||
boxSeqNo: '1',
|
||||
barcode: 'BOX-001',
|
||||
boxIndexText: '1/2',
|
||||
skuSummary: 'SKU001 T-Shirt×2\nSKU002 Pants×1',
|
||||
skuCode: 'SKU001',
|
||||
skuName: 'T-Shirt',
|
||||
skuNameEn: 'T-Shirt',
|
||||
skuQty: '2',
|
||||
unitPrice: '9.90',
|
||||
exHsCode: '610910',
|
||||
imHsCode: '610910',
|
||||
declareElement: '棉质 T 恤',
|
||||
weightG: '200',
|
||||
lengthCm: '30',
|
||||
widthCm: '20',
|
||||
heightCm: '15',
|
||||
dimensions: '30×20×15',
|
||||
remark: 'fragile',
|
||||
printTime: '2026-10-22 18:00:00',
|
||||
operatorName: 'preview',
|
||||
pageIndex: '1',
|
||||
pageCount: '2',
|
||||
customerCode: 'AF0A1',
|
||||
customerName: 'Demo Customer',
|
||||
}
|
||||
|
||||
function groupPanelTitle(g: LabelFieldGroup): string {
|
||||
if (g.code === 'inboundBox') return '入库预报'
|
||||
if (g.code === 'warehouseSku') return '库内SKU'
|
||||
if (g.code === 'system') return '系统'
|
||||
return `订单·${g.title}`
|
||||
}
|
||||
|
||||
let hiprintRegistered = false
|
||||
@ -183,12 +231,14 @@ function buildFieldElements(groups: LabelFieldGroup[]) {
|
||||
|
||||
for (const g of normalizeFieldGroups(groups)) {
|
||||
const items: any[] = []
|
||||
const groupKey = g.code || 'default'
|
||||
for (const f of g.fields) {
|
||||
const testData = MOCK_BY_FIELD[f.field] || f.title
|
||||
const isLong = LONGTEXT_FIELDS.has(f.field)
|
||||
const tidBase = `omsLabelFields.${groupKey}.${f.field}`
|
||||
|
||||
items.push({
|
||||
tid: `omsLabelFields.${f.field}`,
|
||||
tid: tidBase,
|
||||
title: f.title,
|
||||
type: isLong ? 'longText' : 'text',
|
||||
options: {
|
||||
@ -204,7 +254,7 @@ function buildFieldElements(groups: LabelFieldGroup[]) {
|
||||
|
||||
if (BARCODE_FIELDS.has(f.field)) {
|
||||
items.push({
|
||||
tid: `omsLabelFields.${f.field}.barcode`,
|
||||
tid: `${tidBase}.barcode`,
|
||||
title: `${f.title}·条码`,
|
||||
type: 'text',
|
||||
options: {
|
||||
@ -218,7 +268,7 @@ function buildFieldElements(groups: LabelFieldGroup[]) {
|
||||
},
|
||||
})
|
||||
items.push({
|
||||
tid: `omsLabelFields.${f.field}.qrcode`,
|
||||
tid: `${tidBase}.qrcode`,
|
||||
title: `${f.title}·二维码`,
|
||||
type: 'text',
|
||||
options: {
|
||||
@ -236,7 +286,7 @@ function buildFieldElements(groups: LabelFieldGroup[]) {
|
||||
|
||||
if (items.length) {
|
||||
if (!hiprint?.PrintElementTypeGroup) continue
|
||||
typeGroups.push(new hiprint.PrintElementTypeGroup(`订单·${g.title}`, items))
|
||||
typeGroups.push(new hiprint.PrintElementTypeGroup(groupPanelTitle(g), items))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -200,6 +200,7 @@ const typeOptions = computed(() => [
|
||||
{ value: 'inbound', label: t('operation.printLabel.labelTemplate.inbound') },
|
||||
{ value: 'outbound', label: t('operation.printLabel.labelTemplate.outbound') },
|
||||
{ value: 'sku', label: t('operation.printLabel.labelTemplate.sku') },
|
||||
{ value: 'box', label: t('operation.printLabel.labelTemplate.box') },
|
||||
])
|
||||
|
||||
function typeLabel(type?: string) {
|
||||
|
||||
@ -143,7 +143,23 @@
|
||||
<div class="detail-field-value">
|
||||
<template v-if="basicEditing && isOrderFieldEditable(fieldKey)">
|
||||
<a-select
|
||||
v-if="fieldKey === 'warehouse'"
|
||||
v-if="fieldKey === 'productName'"
|
||||
v-model="basicEditForm.productCode"
|
||||
allow-search
|
||||
size="small"
|
||||
:loading="salesProductOptionsLoading"
|
||||
:placeholder="$t('order.myOrders.drawer.placeholder.productName')"
|
||||
>
|
||||
<a-option
|
||||
v-for="opt in salesProductOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-else-if="fieldKey === 'warehouse'"
|
||||
v-model="basicEditForm.warehouseId"
|
||||
allow-clear
|
||||
allow-search
|
||||
@ -719,12 +735,14 @@ import useDrawer from '@/components/DrawerPro/useDrawer';
|
||||
import { postGetDetailTimeline } from '@/api/TrackOrder/trackOrder';
|
||||
import {
|
||||
postOrderUpdate,
|
||||
postOrderChangeProduct,
|
||||
postOrderWeightUpdate,
|
||||
postOrderRecalculateReceivable,
|
||||
postOrderRecalculatePayable,
|
||||
getOrderOperationLogList,
|
||||
type OrderOperationLogItem,
|
||||
} from '@/api/order';
|
||||
import { getSalesProductList } from '@/api/channel/salesProduct';
|
||||
import { getDictListAll, type DictDto } from '@/api/system/dict';
|
||||
import { getWarehouseList } from '@/api/system/warehouse';
|
||||
import { getOrderStatusMeta, getOrderStatusTagStyle, resolveOrderStatusCode } from '@/constants/orderStatus';
|
||||
@ -893,6 +911,12 @@ const orderEditFieldDefs: FormFieldDef[] = [
|
||||
|
||||
/** 入库前(草稿/已预报)才允许客户编辑 */
|
||||
const MY_ORDERS_EDITABLE_STATUSES = new Set(['draft', 'predeclared']);
|
||||
const CHANGE_PRODUCT_STATUSES = new Set([
|
||||
'draft',
|
||||
'predeclared',
|
||||
'pendingAllocation',
|
||||
'pendingPayment',
|
||||
]);
|
||||
|
||||
const orderEditMetaMap = Object.fromEntries(
|
||||
orderEditFieldDefs.map((def) => [def.key, def])
|
||||
@ -902,7 +926,16 @@ const SENDER_READONLY_KEYS = new Set(['isDefault']);
|
||||
const SENDER_FORM_KEY_MAP: Record<string, string> = { country: 'countryCode' };
|
||||
const RECIPIENT_FORM_KEY_MAP: Record<string, string> = { country: 'countryCode' };
|
||||
|
||||
const canChangeProduct = computed(() => {
|
||||
const o = detail.value as any;
|
||||
const paid = o?.isPaid ?? o?.IsPaid;
|
||||
if (paid === true || paid === 1 || paid === '1') return false;
|
||||
const code = resolveOrderStatusCode(detail.value);
|
||||
return !!code && CHANGE_PRODUCT_STATUSES.has(code) && MY_ORDERS_EDITABLE_STATUSES.has(code);
|
||||
});
|
||||
|
||||
function isOrderFieldEditable(key: string) {
|
||||
if (key === 'productName') return canChangeProduct.value;
|
||||
return key in orderEditMetaMap;
|
||||
}
|
||||
|
||||
@ -936,12 +969,36 @@ const basicEditForm = reactive({
|
||||
codCurrency: '',
|
||||
declaredCurrency: '',
|
||||
declaredSum: undefined as number | undefined,
|
||||
productCode: '',
|
||||
});
|
||||
const initialProductCode = ref('');
|
||||
|
||||
const currencyOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const currencyOptionsLoading = ref(false);
|
||||
const warehouseOptions = ref<Array<{ value: number; label: string }>>([]);
|
||||
const warehouseOptionsLoading = ref(false);
|
||||
const salesProductOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const salesProductOptionsLoading = ref(false);
|
||||
|
||||
async function ensureSalesProductOptions() {
|
||||
if (salesProductOptions.value.length > 0) return;
|
||||
salesProductOptionsLoading.value = true;
|
||||
try {
|
||||
const res = await getSalesProductList({ page: 1, pageSize: 500, isActive: 2 });
|
||||
salesProductOptions.value = (res.items ?? [])
|
||||
.map((row: any) => {
|
||||
const code = String(row?.productCode ?? row?.ProductCode ?? '').trim();
|
||||
const name = String(row?.productName ?? row?.ProductName ?? '').trim();
|
||||
if (!code) return null;
|
||||
return { value: code, label: name ? `${name} ${code}` : code };
|
||||
})
|
||||
.filter(Boolean) as Array<{ value: string; label: string }>;
|
||||
} catch {
|
||||
salesProductOptions.value = [];
|
||||
} finally {
|
||||
salesProductOptionsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureWarehouseOptions(preferred?: {
|
||||
id?: number;
|
||||
@ -1988,6 +2045,9 @@ function fillBasicEditForm() {
|
||||
declaredSumRaw != null && declaredSumRaw !== '' && Number.isFinite(Number(declaredSumRaw))
|
||||
? Number(declaredSumRaw)
|
||||
: undefined;
|
||||
const productCode = String(o.productCode ?? o.ProductCode ?? '').trim();
|
||||
basicEditForm.productCode = productCode;
|
||||
initialProductCode.value = productCode;
|
||||
|
||||
senderEditForm.senderName = String(sender.value.senderName ?? '');
|
||||
senderEditForm.company = String(sender.value.company ?? '');
|
||||
@ -2051,6 +2111,9 @@ function startBasicEdit() {
|
||||
id: basicEditForm.warehouseId,
|
||||
name: String(detail.value?.warehouse ?? detail.value?.warehouseName ?? ''),
|
||||
});
|
||||
if (canChangeProduct.value) {
|
||||
void ensureSalesProductOptions();
|
||||
}
|
||||
basicEditing.value = true;
|
||||
void focusFirstEditableField('.order-detail-drawer .basic-info');
|
||||
}
|
||||
@ -2082,6 +2145,18 @@ async function handleBasicEditSave() {
|
||||
if (id == null) return;
|
||||
basicSaving.value = true;
|
||||
try {
|
||||
const nextProductCode = String(basicEditForm.productCode ?? '').trim();
|
||||
const prevProductCode = String(initialProductCode.value ?? '').trim();
|
||||
if (
|
||||
canChangeProduct.value
|
||||
&& nextProductCode
|
||||
&& nextProductCode.toLowerCase() !== prevProductCode.toLowerCase()
|
||||
) {
|
||||
const changed = await postOrderChangeProduct({ id, productCode: nextProductCode });
|
||||
applyDetailFromApi(changed);
|
||||
initialProductCode.value = nextProductCode;
|
||||
}
|
||||
|
||||
const result = await postOrderUpdate({
|
||||
id,
|
||||
clientNo: basicEditForm.clientNo,
|
||||
|
||||
@ -134,7 +134,23 @@
|
||||
<div class="detail-field-value">
|
||||
<template v-if="basicEditing && isOrderFieldEditable(fieldKey)">
|
||||
<a-select
|
||||
v-if="fieldKey === 'warehouse'"
|
||||
v-if="fieldKey === 'productName'"
|
||||
v-model="basicEditForm.productCode"
|
||||
allow-search
|
||||
size="small"
|
||||
:loading="salesProductOptionsLoading"
|
||||
:placeholder="$t('order.myOrders.drawer.placeholder.productName')"
|
||||
>
|
||||
<a-option
|
||||
v-for="opt in salesProductOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-else-if="fieldKey === 'warehouse'"
|
||||
v-model="basicEditForm.warehouseId"
|
||||
allow-clear
|
||||
allow-search
|
||||
@ -994,6 +1010,7 @@ import useDrawer from '@/components/DrawerPro/useDrawer';
|
||||
import { postGetDetailTimeline } from '@/api/TrackOrder/trackOrder';
|
||||
import {
|
||||
postOrderUpdate,
|
||||
postOrderChangeProduct,
|
||||
postOrderWeightUpdate,
|
||||
postOrderRecalculateReceivable,
|
||||
postOrderRecalculatePayable,
|
||||
@ -1158,7 +1175,21 @@ const SENDER_READONLY_KEYS = new Set(['isDefault']);
|
||||
const SENDER_FORM_KEY_MAP: Record<string, string> = { country: 'countryCode' };
|
||||
const RECIPIENT_FORM_KEY_MAP: Record<string, string> = { country: 'countryCode' };
|
||||
|
||||
const CHANGE_PRODUCT_STATUSES = new Set([
|
||||
'draft',
|
||||
'predeclared',
|
||||
'pendingAllocation',
|
||||
'pendingPayment',
|
||||
]);
|
||||
|
||||
const canChangeProduct = computed(() => {
|
||||
if (!isOrderUnpaid.value) return false;
|
||||
const code = resolveOrderStatusCode(detail.value);
|
||||
return !!code && CHANGE_PRODUCT_STATUSES.has(code);
|
||||
});
|
||||
|
||||
function isOrderFieldEditable(key: string) {
|
||||
if (key === 'productName') return canChangeProduct.value;
|
||||
return key in orderEditMetaMap;
|
||||
}
|
||||
|
||||
@ -1193,12 +1224,36 @@ const basicEditForm = reactive({
|
||||
codCurrency: '',
|
||||
declaredCurrency: '',
|
||||
declaredSum: undefined as number | undefined,
|
||||
productCode: '',
|
||||
});
|
||||
const initialProductCode = ref('');
|
||||
|
||||
const currencyOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const currencyOptionsLoading = ref(false);
|
||||
const warehouseOptions = ref<Array<{ value: number; label: string }>>([]);
|
||||
const warehouseOptionsLoading = ref(false);
|
||||
const salesProductOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const salesProductOptionsLoading = ref(false);
|
||||
|
||||
async function ensureSalesProductOptions() {
|
||||
if (salesProductOptions.value.length > 0) return;
|
||||
salesProductOptionsLoading.value = true;
|
||||
try {
|
||||
const res = await getSalesProductList({ page: 1, pageSize: 500, isActive: 2 });
|
||||
salesProductOptions.value = (res.items ?? [])
|
||||
.map((row: any) => {
|
||||
const code = String(row?.productCode ?? row?.ProductCode ?? '').trim();
|
||||
const name = String(row?.productName ?? row?.ProductName ?? '').trim();
|
||||
if (!code) return null;
|
||||
return { value: code, label: name ? `${name} ${code}` : code };
|
||||
})
|
||||
.filter(Boolean) as Array<{ value: string; label: string }>;
|
||||
} catch {
|
||||
salesProductOptions.value = [];
|
||||
} finally {
|
||||
salesProductOptionsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureWarehouseOptions(preferred?: {
|
||||
id?: number;
|
||||
@ -2481,6 +2536,9 @@ function fillBasicEditForm() {
|
||||
declaredSumRaw != null && declaredSumRaw !== '' && Number.isFinite(Number(declaredSumRaw))
|
||||
? Number(declaredSumRaw)
|
||||
: undefined;
|
||||
const productCode = String(o.productCode ?? o.ProductCode ?? '').trim();
|
||||
basicEditForm.productCode = productCode;
|
||||
initialProductCode.value = productCode;
|
||||
|
||||
senderEditForm.senderName = String(sender.value.senderName ?? '');
|
||||
senderEditForm.company = String(sender.value.company ?? '');
|
||||
@ -2540,6 +2598,9 @@ function startBasicEdit() {
|
||||
id: basicEditForm.warehouseId,
|
||||
name: String(detail.value?.warehouse ?? detail.value?.warehouseName ?? ''),
|
||||
});
|
||||
if (canChangeProduct.value) {
|
||||
void ensureSalesProductOptions();
|
||||
}
|
||||
basicEditing.value = true;
|
||||
void focusFirstEditableField('.order-detail-drawer .basic-info');
|
||||
}
|
||||
@ -2572,6 +2633,18 @@ async function handleBasicEditSave() {
|
||||
if (id == null) return;
|
||||
basicSaving.value = true;
|
||||
try {
|
||||
const nextProductCode = String(basicEditForm.productCode ?? '').trim();
|
||||
const prevProductCode = String(initialProductCode.value ?? '').trim();
|
||||
if (
|
||||
canChangeProduct.value
|
||||
&& nextProductCode
|
||||
&& nextProductCode.toLowerCase() !== prevProductCode.toLowerCase()
|
||||
) {
|
||||
const changed = await postOrderChangeProduct({ id, productCode: nextProductCode });
|
||||
applyDetailFromApi(changed);
|
||||
initialProductCode.value = nextProductCode;
|
||||
}
|
||||
|
||||
const result = await postOrderUpdate({
|
||||
id,
|
||||
clientNo: basicEditForm.clientNo,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import { IconDelete, IconPlus, IconQuestionCircle } from '@arco-design/web-vue/es/icon'
|
||||
@ -16,11 +16,13 @@ import {
|
||||
getWarehouseInboundList,
|
||||
getWarehouseInboundSummary,
|
||||
getWarehouseSkuList,
|
||||
postWarehouseInboundPrintLabels,
|
||||
saveWarehouseInbound,
|
||||
} from '@/api/warehouse'
|
||||
import { getWarehouseList } from '@/api/system/warehouse'
|
||||
import { $baseMessage } from '@/utils'
|
||||
import { hasPermission } from '@/utils/rbac-permission'
|
||||
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState'
|
||||
import {
|
||||
buildWarehouseNameMap,
|
||||
formatDateTime,
|
||||
@ -35,6 +37,10 @@ import { useCurrentWarehouseStore } from '@/store'
|
||||
|
||||
defineOptions({ name: 'WarehouseInboundNotice' })
|
||||
|
||||
const OrderPrintModal = defineAsyncComponent(
|
||||
() => import('@/components/PrintTemplate/OrderPrintModal.vue'),
|
||||
)
|
||||
|
||||
interface InboundLineForm {
|
||||
skuId?: number
|
||||
skuCode: string
|
||||
@ -78,6 +84,10 @@ const {
|
||||
resolveSelectedCustomerId,
|
||||
} = useWarehouseAdminCustomer()
|
||||
const currentWarehouseStore = useCurrentWarehouseStore()
|
||||
const printConnectionState = useHiprintClientState()
|
||||
const printVisible = ref(false)
|
||||
const printItems = ref<Array<{ id: number | string; label: string; url: string }>>([])
|
||||
const printPreparing = ref(false)
|
||||
|
||||
/** Tab active-key;全部用 'all',请求时再转成后端 receiptStatus */
|
||||
const STATUS_TAB_ALL = 'all'
|
||||
@ -111,11 +121,10 @@ const slidingStatusTabs = computed(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref<any>(null)
|
||||
|
||||
const [registerFormModal, { openModal, closeModal }] = useModal() as any
|
||||
const [registerReceiveModal, { openModal: openReceiveModal, closeModal: closeReceiveModal }] = useModal() as any
|
||||
/** 已完成等不可改状态:同一弹窗只读查看 */
|
||||
const formReadonly = ref(false)
|
||||
const saving = ref(false)
|
||||
const receiveSaving = ref(false)
|
||||
const receiveNotice = ref<any>(null)
|
||||
@ -145,11 +154,49 @@ const boxTabItems = computed(() =>
|
||||
|
||||
const canRemoveBox = computed(() => boxRows.value.length > 1)
|
||||
|
||||
const formModalTitle = computed(() => {
|
||||
if (!formModel.id) return t('warehouse.inbound.modal.add')
|
||||
if (formReadonly.value) return t('warehouse.inbound.modal.view')
|
||||
return t('warehouse.inbound.modal.edit')
|
||||
})
|
||||
|
||||
const formInboundTypeLabel = computed(() =>
|
||||
t(`warehouse.inbound.type.${formModel.inboundType}`, formModel.inboundType || '-'),
|
||||
)
|
||||
|
||||
const modalWarehouseName = computed(() => {
|
||||
const wh = warehouseOptions.value.find((w) => w.value === formModel.warehouseId)
|
||||
return wh?.label || ''
|
||||
})
|
||||
|
||||
function displayOrDash(value: unknown) {
|
||||
const text = String(value ?? '').trim()
|
||||
return text || '-'
|
||||
}
|
||||
|
||||
function formatBoxWeight(box: InboundBoxForm) {
|
||||
if (box.weightG == null || Number.isNaN(Number(box.weightG))) return '-'
|
||||
return `${box.weightG} g`
|
||||
}
|
||||
|
||||
function formatBoxSize(box: InboundBoxForm) {
|
||||
const l = box.lengthCm
|
||||
const w = box.widthCm
|
||||
const h = box.heightCm
|
||||
if (l == null && w == null && h == null) return '-'
|
||||
return `${l ?? '-'} × ${w ?? '-'} × ${h ?? '-'} cm`
|
||||
}
|
||||
|
||||
function resolveSkuLabel(line: InboundLineForm) {
|
||||
if (!line.skuId) return '-'
|
||||
const opt = skuOptions.value.find((s) => s.value === line.skuId)
|
||||
return opt?.label || line.skuCode || `#${line.skuId}`
|
||||
}
|
||||
|
||||
function viewSkuLines(box: InboundBoxForm) {
|
||||
return box.lines.filter((l) => l.skuId)
|
||||
}
|
||||
|
||||
const receiveWarehouseName = computed(() => {
|
||||
const whId = receiveNotice.value?.warehouseId
|
||||
return resolveWarehouseLabel(whId, warehouseNameMap.value)
|
||||
@ -261,6 +308,7 @@ function onSkuChange(line: InboundLineForm, skuId?: number) {
|
||||
watch(
|
||||
boxRows,
|
||||
(boxes) => {
|
||||
if (formReadonly.value) return
|
||||
for (const box of boxes) {
|
||||
const last = box.lines[box.lines.length - 1]
|
||||
if (last?.skuId) {
|
||||
@ -374,18 +422,6 @@ function handleSearch() {
|
||||
loadList()
|
||||
}
|
||||
|
||||
async function showDetail(row: any) {
|
||||
try {
|
||||
detailData.value = await getWarehouseInboundDetail(
|
||||
row._raw?.id || row.id,
|
||||
isAdmin.value ? resolveSelectedCustomerId() : undefined,
|
||||
)
|
||||
detailVisible.value = true
|
||||
} catch (e: any) {
|
||||
$baseMessage(e?.message || t('warehouse.inbound.message.detailFailed'), 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function loadReceiveBinOptions(whId?: number) {
|
||||
if (!whId) {
|
||||
receiveBinOptions.value = []
|
||||
@ -496,9 +532,14 @@ async function submitConfirmReceive() {
|
||||
}
|
||||
}
|
||||
|
||||
function canEditInbound(row: any) {
|
||||
return row?._status === 'forecasted' || row?._status === 'received'
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
if (!hasPermission('Warehouse.InboundNotice.Save')) return
|
||||
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||
formReadonly.value = false
|
||||
formModel.id = 0
|
||||
formModel.warehouseId = currentWarehouseStore.resolveDefaultWarehouseId(warehouseId.value)
|
||||
formModel.markReference = ''
|
||||
@ -512,7 +553,82 @@ function handleAdd() {
|
||||
openModal(true)
|
||||
}
|
||||
|
||||
/** 查看/编辑融合:同一弹窗;预报/待入库可改,已完成只读 */
|
||||
async function openInboundForm(row: any) {
|
||||
const editable =
|
||||
hasPermission('Warehouse.InboundNotice.Save') && canEditInbound(row)
|
||||
if (editable && isAdmin.value && ensureMutationCustomerId() == null) return
|
||||
try {
|
||||
const detail = await getWarehouseInboundDetail(
|
||||
row._raw?.id || row.id,
|
||||
isAdmin.value ? resolveSelectedCustomerId() : undefined,
|
||||
)
|
||||
const normalized = normalizeListRow(detail || {})
|
||||
formReadonly.value = !editable
|
||||
formModel.id = Number(pickField(normalized, 'id', 'Id') || 0)
|
||||
formModel.warehouseId = pickField<number>(normalized, 'warehouseId', 'WarehouseId')
|
||||
formModel.markReference = String(pickField(normalized, 'markReference', 'MarkReference') ?? '')
|
||||
formModel.inboundType = String(pickField(normalized, 'inboundType', 'InboundType') ?? 'normal')
|
||||
formModel.remark = String(pickField(normalized, 'remark', 'Remark') ?? '')
|
||||
|
||||
const boxesRaw = pickField(normalized, 'boxes', 'Boxes')
|
||||
const boxes = Array.isArray(boxesRaw) ? boxesRaw : []
|
||||
if (!boxes.length) {
|
||||
if (editable) resetBoxes()
|
||||
else {
|
||||
boxRows.value = []
|
||||
activeBoxKey.value = ''
|
||||
}
|
||||
} else {
|
||||
boxRows.value = boxes.map((box: any, index: number) => {
|
||||
const b = normalizeListRow(box)
|
||||
const linesRaw = pickField(b, 'lines', 'Lines')
|
||||
const lines = Array.isArray(linesRaw) ? linesRaw : []
|
||||
const mappedLines: InboundLineForm[] = lines.map((line: any) => {
|
||||
const l = normalizeListRow(line)
|
||||
return {
|
||||
skuId: Number(pickField(l, 'skuId', 'SkuId') || 0) || undefined,
|
||||
skuCode: String(pickField(l, 'skuCode', 'SkuCode') ?? ''),
|
||||
plannedQty: Number(pickField(l, 'plannedQty', 'PlannedQty') ?? 1) || 1,
|
||||
}
|
||||
})
|
||||
if (editable && (!mappedLines.length || mappedLines.every((l) => l.skuId))) {
|
||||
mappedLines.push({ skuId: undefined, skuCode: '', plannedQty: 1 })
|
||||
}
|
||||
return {
|
||||
key: `box-open-${pickField(b, 'id', 'Id') || index}-${Date.now()}`,
|
||||
boxSeqNo: String(pickField(b, 'boxSeqNo', 'BoxSeqNo') ?? index + 1),
|
||||
markBoxNo: String(pickField(b, 'markBoxNo', 'MarkBoxNo') ?? ''),
|
||||
weightG: pickField<number>(b, 'weightG', 'WeightG') ?? undefined,
|
||||
lengthCm: pickField<number>(b, 'lengthCm', 'LengthCm') ?? undefined,
|
||||
widthCm: pickField<number>(b, 'widthCm', 'WidthCm') ?? undefined,
|
||||
heightCm: pickField<number>(b, 'heightCm', 'HeightCm') ?? undefined,
|
||||
lines: mappedLines.length
|
||||
? mappedLines
|
||||
: [{ skuId: undefined, skuCode: '', plannedQty: 1 }],
|
||||
}
|
||||
})
|
||||
selectedBoxKeys.value = []
|
||||
activeBoxKey.value = boxRows.value[0]?.key || ''
|
||||
}
|
||||
|
||||
if (!formModel.warehouseId) {
|
||||
$baseMessage(t('warehouse.common.noCurrentWarehouse'), 'warning')
|
||||
return
|
||||
}
|
||||
openModal(true)
|
||||
} catch (e: any) {
|
||||
$baseMessage(e?.message || t('warehouse.inbound.message.detailFailed'), 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function closeFormModal() {
|
||||
formReadonly.value = false
|
||||
closeModal()
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (formReadonly.value) return
|
||||
if (!formModel.warehouseId) {
|
||||
$baseMessage(t('warehouse.inbound.message.required'), 'warning')
|
||||
return
|
||||
@ -552,7 +668,7 @@ async function handleSave() {
|
||||
boxes,
|
||||
}))
|
||||
$baseMessage(t('warehouse.inbound.message.saveSuccess'), 'success')
|
||||
closeModal()
|
||||
closeFormModal()
|
||||
await loadSummary()
|
||||
await loadList()
|
||||
} catch (e: any) {
|
||||
@ -577,8 +693,62 @@ function handleDelete(row: any) {
|
||||
})
|
||||
}
|
||||
|
||||
function isUsablePrintUrl(raw: unknown) {
|
||||
const value = String(raw ?? '').trim()
|
||||
if (!value) return false
|
||||
if (value.includes('获取中')) return false
|
||||
return true
|
||||
}
|
||||
|
||||
async function handlePrintLabel(row: any) {
|
||||
if (!hasPermission('Warehouse.InboundNotice.PrintLabel')) return
|
||||
const id = Number(row?._raw?.id || row?.id || 0)
|
||||
if (!id) return
|
||||
|
||||
printVisible.value = false
|
||||
printItems.value = []
|
||||
printPreparing.value = true
|
||||
try {
|
||||
const customerId = isAdmin.value ? resolveSelectedCustomerId() ?? undefined : undefined
|
||||
const { printUrl, noticeNo, pageCount } = await postWarehouseInboundPrintLabels({
|
||||
id,
|
||||
customerId,
|
||||
})
|
||||
if (!isUsablePrintUrl(printUrl)) {
|
||||
$baseMessage(t('warehouse.inbound.message.printEmpty'), 'warning')
|
||||
return
|
||||
}
|
||||
const label = noticeNo || row.noticeNo || String(id)
|
||||
printItems.value = [
|
||||
{
|
||||
id,
|
||||
label: pageCount > 1 ? `${label}(${pageCount}页)` : label,
|
||||
url: printUrl,
|
||||
},
|
||||
]
|
||||
printVisible.value = true
|
||||
if (!printConnectionState.enabled || !printConnectionState.connected) {
|
||||
$baseMessage('已打开箱唛预览;静默打印需先在顶部连接打印开关', 'info')
|
||||
}
|
||||
} catch (e: any) {
|
||||
$baseMessage(e?.message || t('warehouse.inbound.message.printFailed'), 'error')
|
||||
} finally {
|
||||
printPreparing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function actionRenderer(row: any) {
|
||||
const parts = [renderWarehouseActionBtn('detail', t('warehouse.common.detail'), 'view')]
|
||||
const canEdit = hasPermission('Warehouse.InboundNotice.Save') && canEditInbound(row)
|
||||
const parts = [
|
||||
canEdit
|
||||
? renderWarehouseActionBtn('open', t('warehouse.common.edit'), 'edit')
|
||||
: renderWarehouseActionBtn('open', t('warehouse.common.detail'), 'view'),
|
||||
]
|
||||
if (hasPermission('Warehouse.InboundNotice.PrintLabel')) {
|
||||
parts.push(
|
||||
renderWarehouseActionBtn('printLabel', t('warehouse.inbound.actions.printLabel'), 'print'),
|
||||
)
|
||||
}
|
||||
if (
|
||||
(hasPermission('Warehouse.InboundNotice.ConfirmReceive')
|
||||
|| hasPermission('Warehouse.InboundNotice.Save'))
|
||||
@ -593,7 +763,8 @@ function actionRenderer(row: any) {
|
||||
}
|
||||
|
||||
function handleActionClick(row: any, _i: number, action?: string) {
|
||||
if (action === 'detail') showDetail(row)
|
||||
if (action === 'open') void openInboundForm(row)
|
||||
if (action === 'printLabel') void handlePrintLabel(row)
|
||||
if (action === 'receive') openConfirmReceive(row)
|
||||
if (action === 'delete') handleDelete(row)
|
||||
}
|
||||
@ -676,22 +847,13 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-drawer v-model:visible="detailVisible" :title="t('warehouse.inbound.drawer.detail')" :width="640">
|
||||
<template v-if="detailData">
|
||||
<p>{{ t('warehouse.inbound.table.noticeNo') }}: {{ detailData.noticeNo || detailData.NoticeNo }}</p>
|
||||
<p>{{ t('warehouse.inbound.table.markReference') }}: {{ detailData.markReference || detailData.MarkReference || '-' }}</p>
|
||||
<div v-for="box in (detailData.boxes || detailData.Boxes || [])" :key="box.id || box.Id" class="box-block">
|
||||
<div class="box-title">{{ t('warehouse.inbound.box') }}: {{ box.markBoxNo || box.MarkBoxNo || box.boxSeqNo || box.BoxSeqNo }}</div>
|
||||
<a-table :data="box.lines || box.Lines || []" :pagination="false" size="small">
|
||||
<template #columns>
|
||||
<a-table-column :title="t('warehouse.inbound.line.skuCode')" data-index="skuCode" />
|
||||
<a-table-column :title="t('warehouse.inbound.line.plannedQty')" data-index="plannedQty" />
|
||||
<a-table-column :title="t('warehouse.inbound.line.receivedQty')" data-index="receivedQty" />
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
</a-drawer>
|
||||
<OrderPrintModal
|
||||
v-if="printVisible"
|
||||
v-model:visible="printVisible"
|
||||
mode="single"
|
||||
:items="printItems"
|
||||
:preparing="printPreparing"
|
||||
/>
|
||||
|
||||
<ModalPro
|
||||
:title="t('warehouse.inbound.modal.confirmReceive')"
|
||||
@ -754,11 +916,11 @@ onMounted(async () => {
|
||||
</ModalPro>
|
||||
|
||||
<ModalPro
|
||||
:title="t('warehouse.inbound.modal.add')"
|
||||
:title="formModalTitle"
|
||||
:width="860"
|
||||
:mask-closable="false"
|
||||
@register="registerFormModal"
|
||||
@cancel="closeModal"
|
||||
@cancel="closeFormModal"
|
||||
>
|
||||
<div class="wh-form-modal">
|
||||
<div class="wh-form-info-bar">
|
||||
@ -770,17 +932,33 @@ onMounted(async () => {
|
||||
|| formModel.warehouseId
|
||||
|| '-'
|
||||
}}
|
||||
<a-tooltip :content="t('warehouse.inbound.tip.currentWarehouse')">
|
||||
<a-tooltip v-if="!formReadonly" :content="t('warehouse.inbound.tip.currentWarehouse')">
|
||||
<icon-question-circle class="tip-icon" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-section-title">{{ t('warehouse.modal.section.basic') }}</div>
|
||||
<a-form :model="formModel" layout="vertical" class="inbound-form-basic edit-form">
|
||||
<div v-if="formReadonly" class="inbound-view-basic">
|
||||
<div class="inbound-view-row">
|
||||
<div class="inbound-view-field inbound-view-field--grow">
|
||||
<div class="inbound-view-label">{{ t('warehouse.inbound.form.markReference') }}</div>
|
||||
<div class="wh-form-readonly">{{ displayOrDash(formModel.markReference) }}</div>
|
||||
</div>
|
||||
<div class="inbound-view-field">
|
||||
<div class="inbound-view-label">{{ t('warehouse.inbound.table.inboundType') }}</div>
|
||||
<div class="wh-form-readonly">{{ formInboundTypeLabel }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inbound-view-field">
|
||||
<div class="inbound-view-label">{{ t('warehouse.parcel.form.remark') }}</div>
|
||||
<div class="wh-form-readonly">{{ displayOrDash(formModel.remark) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-form v-else :model="formModel" layout="vertical" class="inbound-form-basic edit-form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="14">
|
||||
<a-form-item>
|
||||
<a-form-item :required="false">
|
||||
<template #label>
|
||||
<span class="label-with-tip">
|
||||
{{ t('warehouse.inbound.form.markReference') }}
|
||||
@ -837,7 +1015,7 @@ onMounted(async () => {
|
||||
<icon-question-circle class="tip-icon" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-space :size="8">
|
||||
<a-space v-if="!formReadonly" :size="8">
|
||||
<a-button size="mini" @click="addBox">
|
||||
<template #icon><IconPlus :size="12" /></template>
|
||||
{{ t('warehouse.inbound.actions.addBox') }}
|
||||
@ -866,7 +1044,21 @@ onMounted(async () => {
|
||||
:key="box.key"
|
||||
class="wh-form-box-panel inbound-box-panel"
|
||||
>
|
||||
<div class="inbound-box-meta">
|
||||
<div v-if="formReadonly" class="inbound-box-meta inbound-box-meta--view">
|
||||
<div class="inbound-box-meta__field inbound-box-meta__field--mark">
|
||||
<span class="inbound-box-meta__label">{{ t('warehouse.inbound.form.markBoxNo') }}</span>
|
||||
<div class="wh-form-readonly">{{ displayOrDash(box.markBoxNo) }}</div>
|
||||
</div>
|
||||
<div class="inbound-box-meta__field inbound-box-meta__field--weight">
|
||||
<span class="inbound-box-meta__label">{{ t('warehouse.inbound.form.boxWeight') }}</span>
|
||||
<div class="wh-form-readonly">{{ formatBoxWeight(box) }}</div>
|
||||
</div>
|
||||
<div class="inbound-box-meta__field inbound-box-meta__field--size">
|
||||
<span class="inbound-box-meta__label">{{ t('warehouse.inbound.form.boxSize') }}</span>
|
||||
<div class="wh-form-readonly">{{ formatBoxSize(box) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="inbound-box-meta">
|
||||
<div class="inbound-box-meta__field inbound-box-meta__field--mark">
|
||||
<span class="inbound-box-meta__label label-with-tip">
|
||||
{{ t('warehouse.inbound.form.markBoxNo') }}
|
||||
@ -925,7 +1117,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inbound-sku-head">
|
||||
<div v-if="!formReadonly" class="inbound-sku-head">
|
||||
<span class="wh-form-hint label-with-tip">
|
||||
{{ t('warehouse.inbound.form.autoAddSkuRow') }}
|
||||
<a-tooltip :content="t('warehouse.inbound.tip.skuLines')">
|
||||
@ -935,6 +1127,32 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
v-if="formReadonly"
|
||||
class="inbound-sku-table"
|
||||
:data="viewSkuLines(box)"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
:bordered="{ cell: true }"
|
||||
:scroll="{ x: '100%', y: 240 }"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column :title="'#'" :width="44">
|
||||
<template #cell="{ rowIndex }">{{ rowIndex + 1 }}</template>
|
||||
</a-table-column>
|
||||
<a-table-column :title="t('warehouse.inbound.form.sku')" :min-width="220">
|
||||
<template #cell="{ record }">
|
||||
<span class="wh-form-readonly">{{ resolveSkuLabel(record) }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column :title="t('warehouse.inbound.line.plannedQty')" :width="120">
|
||||
<template #cell="{ record }">
|
||||
<span class="wh-form-readonly">{{ record.plannedQty }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-table
|
||||
v-else
|
||||
class="inbound-sku-table"
|
||||
:data="box.lines"
|
||||
:pagination="false"
|
||||
@ -991,8 +1209,15 @@ onMounted(async () => {
|
||||
|
||||
<template #footer>
|
||||
<a-space :size="8">
|
||||
<a-button @click="closeModal">{{ t('warehouse.common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="saving" @click="handleSave">{{ t('warehouse.common.save') }}</a-button>
|
||||
<a-button @click="closeFormModal">{{ t('warehouse.common.cancel') }}</a-button>
|
||||
<a-button
|
||||
v-if="!formReadonly"
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
{{ t('warehouse.common.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</ModalPro>
|
||||
@ -1003,16 +1228,40 @@ onMounted(async () => {
|
||||
@import '@/styles/warehouse-list-page.less';
|
||||
@import '@/styles/warehouse-form-modal.less';
|
||||
|
||||
.box-block {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.box-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.wh-form-box-tabs {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.inbound-view-basic {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.inbound-view-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.inbound-view-field {
|
||||
min-width: 140px;
|
||||
|
||||
&--grow {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.inbound-view-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.inbound-box-meta--view {
|
||||
.inbound-box-meta__label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
.receive-box-block {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ const historyLoading = ref(false)
|
||||
const historyTab = ref<'all' | 'inbound' | 'deduct'>('all')
|
||||
const historyRows = ref<any[]>([])
|
||||
const historyPage = ref(1)
|
||||
const historypageSize = ref(50)
|
||||
const historyPageSize = ref(50)
|
||||
const historyTotal = ref(0)
|
||||
|
||||
const [registerRelocateModal, { openModal: openRelocate, closeModal: closeRelocate }] = useModal() as any
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, defineAsyncComponent, onMounted, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import { IconDelete, IconImage, IconPlus, IconQuestionCircle } from '@arco-design/web-vue/es/icon'
|
||||
@ -11,6 +11,7 @@ import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
||||
import {
|
||||
deleteWarehouseSku,
|
||||
getWarehouseSkuList,
|
||||
postWarehouseSkuPrintLabel,
|
||||
saveWarehouseSku,
|
||||
uploadWarehouseSkuImage,
|
||||
} from '@/api/warehouse'
|
||||
@ -19,6 +20,7 @@ import { resolveOmsAssetUrl } from '@/api/oms/common'
|
||||
import { streamFileBlob } from '@/api/fileStorage'
|
||||
import { $baseMessage } from '@/utils'
|
||||
import { hasPermission } from '@/utils/rbac-permission'
|
||||
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState'
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDimensions,
|
||||
@ -35,6 +37,10 @@ import {
|
||||
|
||||
defineOptions({ name: 'WarehouseSku' })
|
||||
|
||||
const OrderPrintModal = defineAsyncComponent(
|
||||
() => import('@/components/PrintTemplate/OrderPrintModal.vue'),
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||
const {
|
||||
@ -43,8 +49,13 @@ const {
|
||||
customerOptions,
|
||||
withCustomerId,
|
||||
ensureMutationCustomerId,
|
||||
resolveSelectedCustomerId,
|
||||
} = useWarehouseAdminCustomer()
|
||||
const currentWarehouseStore = useCurrentWarehouseStore()
|
||||
const printConnectionState = useHiprintClientState()
|
||||
const printVisible = ref(false)
|
||||
const printItems = ref<Array<{ id: number | string; label: string; url: string }>>([])
|
||||
const printPreparing = ref(false)
|
||||
|
||||
const listLoading = ref(false)
|
||||
const tableRows = ref<any[]>([])
|
||||
@ -428,8 +439,56 @@ function handleDelete(row: any) {
|
||||
})
|
||||
}
|
||||
|
||||
function isUsablePrintUrl(raw: unknown) {
|
||||
const value = String(raw ?? '').trim()
|
||||
if (!value) return false
|
||||
if (value.includes('获取中')) return false
|
||||
return true
|
||||
}
|
||||
|
||||
async function handlePrintLabel(row: any) {
|
||||
if (!hasPermission('Warehouse.Sku.PrintLabel')) return
|
||||
const id = Number(row?.id || 0)
|
||||
if (!id) return
|
||||
|
||||
printVisible.value = false
|
||||
printItems.value = []
|
||||
printPreparing.value = true
|
||||
try {
|
||||
const adminCustomerId = isAdmin.value ? resolveSelectedCustomerId() ?? undefined : undefined
|
||||
const { printUrl, skuCode } = await postWarehouseSkuPrintLabel({
|
||||
id,
|
||||
customerId: adminCustomerId,
|
||||
})
|
||||
if (!isUsablePrintUrl(printUrl)) {
|
||||
$baseMessage(t('warehouse.sku.message.printEmpty'), 'warning')
|
||||
return
|
||||
}
|
||||
printItems.value = [
|
||||
{
|
||||
id,
|
||||
label: skuCode || row.skuCode || String(id),
|
||||
url: printUrl,
|
||||
},
|
||||
]
|
||||
printVisible.value = true
|
||||
if (!printConnectionState.enabled || !printConnectionState.connected) {
|
||||
$baseMessage('已打开 SKU 标签预览;静默打印需先在顶部连接打印开关', 'info')
|
||||
}
|
||||
} catch (e: any) {
|
||||
$baseMessage(e?.message || t('warehouse.sku.message.printFailed'), 'error')
|
||||
} finally {
|
||||
printPreparing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function actionRenderer() {
|
||||
const parts: string[] = []
|
||||
if (hasPermission('Warehouse.Sku.PrintLabel')) {
|
||||
parts.push(
|
||||
renderWarehouseActionBtn('printLabel', t('warehouse.sku.actions.printLabel'), 'print'),
|
||||
)
|
||||
}
|
||||
if (hasPermission('Warehouse.Sku.Save')) {
|
||||
parts.push(renderWarehouseActionBtn('edit', t('warehouse.common.edit')))
|
||||
}
|
||||
@ -440,6 +499,7 @@ function actionRenderer() {
|
||||
}
|
||||
|
||||
function handleActionClick(row: any, _idx: number, action?: string) {
|
||||
if (action === 'printLabel') void handlePrintLabel(row)
|
||||
if (action === 'edit') handleEdit(row)
|
||||
if (action === 'delete') handleDelete(row)
|
||||
}
|
||||
@ -514,6 +574,14 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OrderPrintModal
|
||||
v-if="printVisible"
|
||||
v-model:visible="printVisible"
|
||||
mode="single"
|
||||
:items="printItems"
|
||||
:preparing="printPreparing"
|
||||
/>
|
||||
|
||||
<ModalPro
|
||||
:title="modalTitle"
|
||||
:width="720"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user