完善仓储模块:操作列图标、确认收货与换库位对接、新增库位管理页,管理端支持按客户筛选(不选则查全部)。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
fb1e566a21
commit
f5011edde6
@ -137,6 +137,9 @@ server {
|
|||||||
location = /warehouse/sku {
|
location = /warehouse/sku {
|
||||||
try_files /index.html =200;
|
try_files /index.html =200;
|
||||||
}
|
}
|
||||||
|
location = /warehouse/binLocation {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
location ^~ /warehouse/inboundNotice {
|
location ^~ /warehouse/inboundNotice {
|
||||||
try_files /index.html =200;
|
try_files /index.html =200;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,6 +52,7 @@ server {
|
|||||||
location = /warehouse/parcel { try_files /index.html =200; }
|
location = /warehouse/parcel { try_files /index.html =200; }
|
||||||
location = /warehouse/inventory { try_files /index.html =200; }
|
location = /warehouse/inventory { try_files /index.html =200; }
|
||||||
location = /warehouse/sku { try_files /index.html =200; }
|
location = /warehouse/sku { try_files /index.html =200; }
|
||||||
|
location = /warehouse/binLocation { try_files /index.html =200; }
|
||||||
location ^~ /warehouse/inboundNotice { try_files /index.html =200; }
|
location ^~ /warehouse/inboundNotice { try_files /index.html =200; }
|
||||||
|
|
||||||
location ^~ /channel/salesProducts { try_files /index.html =200; }
|
location ^~ /channel/salesProducts { try_files /index.html =200; }
|
||||||
|
|||||||
94
src/composables/useWarehouseAdminCustomer.ts
Normal file
94
src/composables/useWarehouseAdminCustomer.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { CustomerService } from '@/api/customer'
|
||||||
|
import { extractCustomerId, mapCustomerRow } from '@/api/customer/resolve'
|
||||||
|
import { $baseMessage } from '@/utils'
|
||||||
|
import { useRbacUserStore } from '@/store/modules/rbac-user'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仓储页管理端客户上下文:
|
||||||
|
* - 列表/汇总:未选客户 → 不传 customerId → 查全部
|
||||||
|
* - 写入:管理端须显式选择客户
|
||||||
|
* 客户门户登录时由后端会话强制归属,前端不传 customerId。
|
||||||
|
*/
|
||||||
|
export function useWarehouseAdminCustomer() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const rbacUserStore = useRbacUserStore()
|
||||||
|
|
||||||
|
const isAdmin = computed(() => !rbacUserStore.isCustomerPortal())
|
||||||
|
/** undefined = 不筛选(查全部);仅 >0 才作为筛选条件 */
|
||||||
|
const customerId = ref<number>()
|
||||||
|
const customerOptions = ref<Array<{ label: string; value: number }>>([])
|
||||||
|
const customerLoading = ref(false)
|
||||||
|
|
||||||
|
function resolveSelectedCustomerId(): number | undefined {
|
||||||
|
const raw = customerId.value as number | string | null | undefined
|
||||||
|
if (raw == null || raw === '') return undefined
|
||||||
|
const id = Number(raw)
|
||||||
|
return Number.isFinite(id) && id > 0 ? id : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCustomers() {
|
||||||
|
if (!isAdmin.value) {
|
||||||
|
customerOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customerLoading.value = true
|
||||||
|
try {
|
||||||
|
const { items } = await CustomerService.getList({ page: 1, pageSize: 500 })
|
||||||
|
customerOptions.value = (items || [])
|
||||||
|
.map((row) => mapCustomerRow(row as Record<string, unknown>))
|
||||||
|
.map((c) => {
|
||||||
|
const id = extractCustomerId(c as Record<string, unknown>)
|
||||||
|
if (!id) return null
|
||||||
|
const name = c.customerName || c.customerCode || c.phone || `#${id}`
|
||||||
|
const code = c.customerCode ? ` (${c.customerCode})` : ''
|
||||||
|
return { label: `${name}${code}`, value: id }
|
||||||
|
})
|
||||||
|
.filter((x): x is { label: string; value: number } => !!x)
|
||||||
|
} catch {
|
||||||
|
customerOptions.value = []
|
||||||
|
} finally {
|
||||||
|
customerLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加到 list/save 请求体。
|
||||||
|
* 管理端未选客户时显式去掉 customerId,保证「不选 = 查全部」。
|
||||||
|
*/
|
||||||
|
function withCustomerId<T extends Record<string, unknown>>(body: T): T & { customerId?: number } {
|
||||||
|
const next = { ...body } as T & { customerId?: number }
|
||||||
|
delete next.customerId
|
||||||
|
if (!isAdmin.value) return next
|
||||||
|
const id = resolveSelectedCustomerId()
|
||||||
|
if (id != null) next.customerId = id
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 管理端写操作前校验已选客户;客户门户返回 undefined 由后端会话解析 */
|
||||||
|
function ensureMutationCustomerId(): number | undefined {
|
||||||
|
if (!isAdmin.value) return undefined
|
||||||
|
const id = resolveSelectedCustomerId()
|
||||||
|
if (id == null) {
|
||||||
|
$baseMessage(t('warehouse.common.selectCustomerRequired'), 'warning')
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadCustomers()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
isAdmin,
|
||||||
|
customerId,
|
||||||
|
customerOptions,
|
||||||
|
customerLoading,
|
||||||
|
loadCustomers,
|
||||||
|
withCustomerId,
|
||||||
|
ensureMutationCustomerId,
|
||||||
|
resolveSelectedCustomerId,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -117,6 +117,8 @@ export const PERMISSION_ACTION_LABELS: Record<string, string> = {
|
|||||||
AddAnnouncement: '新增公告',
|
AddAnnouncement: '新增公告',
|
||||||
AddDict: '新增字典',
|
AddDict: '新增字典',
|
||||||
AddWarehouse: '新增仓库',
|
AddWarehouse: '新增仓库',
|
||||||
|
Relocate: '换库位',
|
||||||
|
ConfirmReceive: '确认收货',
|
||||||
AddConfig: '新增配置',
|
AddConfig: '新增配置',
|
||||||
Debug: '调试',
|
Debug: '调试',
|
||||||
Rollback: '回滚到此版本',
|
Rollback: '回滚到此版本',
|
||||||
@ -254,6 +256,11 @@ export const PAGE_BUTTON_PARENT_ALIASES: Record<string, string> = {
|
|||||||
'Api.Test.Query': 'Api.ApiTest',
|
'Api.Test.Query': 'Api.ApiTest',
|
||||||
'Channel.LabelDesign': 'Channel.LabelDesign',
|
'Channel.LabelDesign': 'Channel.LabelDesign',
|
||||||
'Order.BatchJobs.Query': 'Order.BatchJobs',
|
'Order.BatchJobs.Query': 'Order.BatchJobs',
|
||||||
|
'Warehouse.Sku.Query': 'Warehouse.Sku',
|
||||||
|
'Warehouse.Inventory.Query': 'Warehouse.Inventory',
|
||||||
|
'Warehouse.Parcel.Query': 'Warehouse.Parcel',
|
||||||
|
'Warehouse.InboundNotice.Query': 'Warehouse.InboundNotice',
|
||||||
|
'Warehouse.BinLocation.Query': 'Warehouse.BinLocation',
|
||||||
};
|
};
|
||||||
|
|
||||||
type BtnInput = { code: string; name: string };
|
type BtnInput = { code: string; name: string };
|
||||||
@ -510,8 +517,13 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
|||||||
B('Warehouse.InboundNotice', [
|
B('Warehouse.InboundNotice', [
|
||||||
{ code: 'Query', name: '查询' },
|
{ code: 'Query', name: '查询' },
|
||||||
{ code: 'Save', name: '保存' },
|
{ code: 'Save', name: '保存' },
|
||||||
|
{ code: 'ConfirmReceive', name: '确认收货' },
|
||||||
{ code: 'Delete', name: '删除' },
|
{ code: 'Delete', name: '删除' },
|
||||||
]),
|
]),
|
||||||
|
B('Warehouse.BinLocation', [
|
||||||
|
{ code: 'Query', name: '查询' },
|
||||||
|
{ code: 'Save', name: '保存' },
|
||||||
|
]),
|
||||||
|
|
||||||
// ── 渠道 ──
|
// ── 渠道 ──
|
||||||
B('Channel.SalesProducts', [
|
B('Channel.SalesProducts', [
|
||||||
|
|||||||
@ -90,6 +90,7 @@ export default {
|
|||||||
'menu.warehouse.inventory': 'My Inventory',
|
'menu.warehouse.inventory': 'My Inventory',
|
||||||
'menu.warehouse.sku': 'My SKU',
|
'menu.warehouse.sku': 'My SKU',
|
||||||
'menu.warehouse.inboundNotice': 'Inbound Notice',
|
'menu.warehouse.inboundNotice': 'Inbound Notice',
|
||||||
|
'menu.warehouse.binLocation': 'Bin Locations',
|
||||||
'warehouse.common.search': 'Search',
|
'warehouse.common.search': 'Search',
|
||||||
'warehouse.common.add': 'Add',
|
'warehouse.common.add': 'Add',
|
||||||
'warehouse.common.edit': 'Edit',
|
'warehouse.common.edit': 'Edit',
|
||||||
@ -99,6 +100,9 @@ export default {
|
|||||||
'warehouse.common.confirm': 'Confirm',
|
'warehouse.common.confirm': 'Confirm',
|
||||||
'warehouse.common.detail': 'Detail',
|
'warehouse.common.detail': 'Detail',
|
||||||
'warehouse.common.warehouse': 'Warehouse',
|
'warehouse.common.warehouse': 'Warehouse',
|
||||||
|
'warehouse.common.customer': 'Customer',
|
||||||
|
'warehouse.common.selectCustomer': 'All customers',
|
||||||
|
'warehouse.common.selectCustomerRequired': 'Select a customer before create/update',
|
||||||
'warehouse.common.yes': 'Yes',
|
'warehouse.common.yes': 'Yes',
|
||||||
'warehouse.common.no': 'No',
|
'warehouse.common.no': 'No',
|
||||||
'warehouse.sku.table.dimensions': 'Size(cm)',
|
'warehouse.sku.table.dimensions': 'Size(cm)',
|
||||||
@ -152,12 +156,40 @@ export default {
|
|||||||
'warehouse.inventory.form.targetBin': 'Target Bin',
|
'warehouse.inventory.form.targetBin': 'Target Bin',
|
||||||
'warehouse.inventory.form.qty': 'Qty',
|
'warehouse.inventory.form.qty': 'Qty',
|
||||||
'warehouse.inventory.form.selectBin': 'Select bin',
|
'warehouse.inventory.form.selectBin': 'Select bin',
|
||||||
|
'warehouse.inventory.form.selectSourceBin': 'Select source bin with stock',
|
||||||
|
'warehouse.inventory.form.selectTargetBin': 'Select target bin',
|
||||||
'warehouse.inventory.message.loadFailed': 'Failed to load inventory',
|
'warehouse.inventory.message.loadFailed': 'Failed to load inventory',
|
||||||
'warehouse.inventory.message.binDetailFailed': 'Failed to load bin detail',
|
'warehouse.inventory.message.binDetailFailed': 'Failed to load bin detail',
|
||||||
'warehouse.inventory.message.relocateRequired': 'Fill relocate fields',
|
'warehouse.inventory.message.relocateRequired': 'Fill relocate fields',
|
||||||
'warehouse.inventory.message.relocateSuccess': 'Relocated',
|
'warehouse.inventory.message.relocateSuccess': 'Relocated',
|
||||||
'warehouse.inventory.message.relocateFailed': 'Relocate failed',
|
'warehouse.inventory.message.relocateFailed': 'Relocate failed',
|
||||||
'warehouse.inventory.message.sameBin': 'Source and target bin must differ',
|
'warehouse.inventory.message.sameBin': 'Source and target bin must differ',
|
||||||
|
'warehouse.inventory.message.qtyExceed': 'Qty cannot exceed source bin stock',
|
||||||
|
'warehouse.binLocation.table.binCode': 'Bin Code',
|
||||||
|
'warehouse.binLocation.table.binName': 'Bin Name',
|
||||||
|
'warehouse.binLocation.table.zoneCode': 'Zone',
|
||||||
|
'warehouse.binLocation.table.isEnabled': 'Enabled',
|
||||||
|
'warehouse.binLocation.table.remark': 'Remark',
|
||||||
|
'warehouse.binLocation.search.warehouse': 'Search warehouse',
|
||||||
|
'warehouse.binLocation.search.keyword': 'Code / Name',
|
||||||
|
'warehouse.binLocation.filter.isEnabled': 'Enabled',
|
||||||
|
'warehouse.binLocation.actions.refreshWarehouse': 'Refresh warehouses',
|
||||||
|
'warehouse.binLocation.actions.add': 'Add Bin',
|
||||||
|
'warehouse.binLocation.loading.warehouse': 'Loading warehouses...',
|
||||||
|
'warehouse.binLocation.emptySelect': 'Select a warehouse on the left',
|
||||||
|
'warehouse.binLocation.modal.add': 'Add Bin Location',
|
||||||
|
'warehouse.binLocation.modal.edit': 'Edit Bin Location',
|
||||||
|
'warehouse.binLocation.form.binCode': 'Bin Code',
|
||||||
|
'warehouse.binLocation.form.binName': 'Bin Name',
|
||||||
|
'warehouse.binLocation.form.zoneCode': 'Zone Code',
|
||||||
|
'warehouse.binLocation.form.isEnabled': 'Enabled',
|
||||||
|
'warehouse.binLocation.form.remark': 'Remark',
|
||||||
|
'warehouse.binLocation.message.warehouseLoadFailed': 'Failed to load warehouses',
|
||||||
|
'warehouse.binLocation.message.loadFailed': 'Failed to load bins',
|
||||||
|
'warehouse.binLocation.message.selectWarehouse': 'Select a warehouse first',
|
||||||
|
'warehouse.binLocation.message.required': 'Bin code is required',
|
||||||
|
'warehouse.binLocation.message.saveSuccess': 'Saved',
|
||||||
|
'warehouse.binLocation.message.saveFailed': 'Save failed',
|
||||||
'warehouse.parcel.status.all': 'All',
|
'warehouse.parcel.status.all': 'All',
|
||||||
'warehouse.parcel.status.inTransit': 'In Transit',
|
'warehouse.parcel.status.inTransit': 'In Transit',
|
||||||
'warehouse.parcel.status.arrived': 'Arrived',
|
'warehouse.parcel.status.arrived': 'Arrived',
|
||||||
@ -228,7 +260,11 @@ export default {
|
|||||||
'warehouse.inbound.line.skuCode': 'SKU',
|
'warehouse.inbound.line.skuCode': 'SKU',
|
||||||
'warehouse.inbound.line.plannedQty': 'Planned',
|
'warehouse.inbound.line.plannedQty': 'Planned',
|
||||||
'warehouse.inbound.line.receivedQty': 'Received',
|
'warehouse.inbound.line.receivedQty': 'Received',
|
||||||
|
'warehouse.inbound.line.binLocation': 'Putaway Bin',
|
||||||
|
'warehouse.inbound.form.selectBin': 'Select bin',
|
||||||
'warehouse.inbound.actions.delete': 'Delete',
|
'warehouse.inbound.actions.delete': 'Delete',
|
||||||
|
'warehouse.inbound.actions.confirmReceive': 'Confirm Receive',
|
||||||
|
'warehouse.inbound.modal.confirmReceive': 'Confirm Receive & Putaway',
|
||||||
'warehouse.inbound.message.loadFailed': 'Failed to load inbound list',
|
'warehouse.inbound.message.loadFailed': 'Failed to load inbound list',
|
||||||
'warehouse.inbound.message.saveSuccess': 'Saved',
|
'warehouse.inbound.message.saveSuccess': 'Saved',
|
||||||
'warehouse.inbound.message.saveFailed': 'Save failed',
|
'warehouse.inbound.message.saveFailed': 'Save failed',
|
||||||
@ -236,6 +272,10 @@ export default {
|
|||||||
'warehouse.inbound.message.required': 'Select warehouse',
|
'warehouse.inbound.message.required': 'Select warehouse',
|
||||||
'warehouse.inbound.message.boxRequired': 'Add SKU lines to at least one box',
|
'warehouse.inbound.message.boxRequired': 'Add SKU lines to at least one box',
|
||||||
'warehouse.inbound.message.detailFailed': 'Failed to load detail',
|
'warehouse.inbound.message.detailFailed': 'Failed to load detail',
|
||||||
|
'warehouse.inbound.message.receiveBinRequired': 'Bin is required when received qty > 0',
|
||||||
|
'warehouse.inbound.message.receiveQtyInvalid': 'Received qty cannot be negative',
|
||||||
|
'warehouse.inbound.message.receiveSuccess': 'Receive succeeded',
|
||||||
|
'warehouse.inbound.message.receiveFailed': 'Receive failed',
|
||||||
'order.platformStore.platform.manual': 'Manual',
|
'order.platformStore.platform.manual': 'Manual',
|
||||||
'order.platformStore.table.siteCountry': 'Site Country',
|
'order.platformStore.table.siteCountry': 'Site Country',
|
||||||
'order.platformStore.table.autoFulfill': 'Auto Fulfill',
|
'order.platformStore.table.autoFulfill': 'Auto Fulfill',
|
||||||
|
|||||||
@ -90,6 +90,7 @@ export default {
|
|||||||
'menu.warehouse.inventory': '我的库存',
|
'menu.warehouse.inventory': '我的库存',
|
||||||
'menu.warehouse.sku': '我的 SKU',
|
'menu.warehouse.sku': '我的 SKU',
|
||||||
'menu.warehouse.inboundNotice': '入库预报',
|
'menu.warehouse.inboundNotice': '入库预报',
|
||||||
|
'menu.warehouse.binLocation': '库位管理',
|
||||||
'warehouse.common.search': '查询',
|
'warehouse.common.search': '查询',
|
||||||
'warehouse.common.add': '新增',
|
'warehouse.common.add': '新增',
|
||||||
'warehouse.common.edit': '编辑',
|
'warehouse.common.edit': '编辑',
|
||||||
@ -99,6 +100,9 @@ export default {
|
|||||||
'warehouse.common.confirm': '确定',
|
'warehouse.common.confirm': '确定',
|
||||||
'warehouse.common.detail': '详情',
|
'warehouse.common.detail': '详情',
|
||||||
'warehouse.common.warehouse': '仓库',
|
'warehouse.common.warehouse': '仓库',
|
||||||
|
'warehouse.common.customer': '客户',
|
||||||
|
'warehouse.common.selectCustomer': '全部客户',
|
||||||
|
'warehouse.common.selectCustomerRequired': '新增/修改请先选择客户',
|
||||||
'warehouse.common.yes': '是',
|
'warehouse.common.yes': '是',
|
||||||
'warehouse.common.no': '否',
|
'warehouse.common.no': '否',
|
||||||
'warehouse.sku.table.dimensions': '尺寸(cm)',
|
'warehouse.sku.table.dimensions': '尺寸(cm)',
|
||||||
@ -152,12 +156,40 @@ export default {
|
|||||||
'warehouse.inventory.form.targetBin': '目标库位',
|
'warehouse.inventory.form.targetBin': '目标库位',
|
||||||
'warehouse.inventory.form.qty': '数量',
|
'warehouse.inventory.form.qty': '数量',
|
||||||
'warehouse.inventory.form.selectBin': '请选择库位',
|
'warehouse.inventory.form.selectBin': '请选择库位',
|
||||||
|
'warehouse.inventory.form.selectSourceBin': '请选择有库存的源库位',
|
||||||
|
'warehouse.inventory.form.selectTargetBin': '请选择目标库位',
|
||||||
'warehouse.inventory.message.loadFailed': '加载库存失败',
|
'warehouse.inventory.message.loadFailed': '加载库存失败',
|
||||||
'warehouse.inventory.message.binDetailFailed': '加载库位明细失败',
|
'warehouse.inventory.message.binDetailFailed': '加载库位明细失败',
|
||||||
'warehouse.inventory.message.relocateRequired': '请填写换库位信息',
|
'warehouse.inventory.message.relocateRequired': '请填写换库位信息',
|
||||||
'warehouse.inventory.message.relocateSuccess': '换库位成功',
|
'warehouse.inventory.message.relocateSuccess': '换库位成功',
|
||||||
'warehouse.inventory.message.relocateFailed': '换库位失败',
|
'warehouse.inventory.message.relocateFailed': '换库位失败',
|
||||||
'warehouse.inventory.message.sameBin': '源库位与目标库位不能相同',
|
'warehouse.inventory.message.sameBin': '源库位与目标库位不能相同',
|
||||||
|
'warehouse.inventory.message.qtyExceed': '数量不能超过源库位库存',
|
||||||
|
'warehouse.binLocation.table.binCode': '库位编码',
|
||||||
|
'warehouse.binLocation.table.binName': '库位名称',
|
||||||
|
'warehouse.binLocation.table.zoneCode': '库区',
|
||||||
|
'warehouse.binLocation.table.isEnabled': '启用',
|
||||||
|
'warehouse.binLocation.table.remark': '备注',
|
||||||
|
'warehouse.binLocation.search.warehouse': '搜索仓库',
|
||||||
|
'warehouse.binLocation.search.keyword': '编码/名称',
|
||||||
|
'warehouse.binLocation.filter.isEnabled': '启用状态',
|
||||||
|
'warehouse.binLocation.actions.refreshWarehouse': '刷新仓库',
|
||||||
|
'warehouse.binLocation.actions.add': '新增库位',
|
||||||
|
'warehouse.binLocation.loading.warehouse': '正在加载仓库...',
|
||||||
|
'warehouse.binLocation.emptySelect': '请选择左侧仓库',
|
||||||
|
'warehouse.binLocation.modal.add': '新增库位',
|
||||||
|
'warehouse.binLocation.modal.edit': '编辑库位',
|
||||||
|
'warehouse.binLocation.form.binCode': '库位编码',
|
||||||
|
'warehouse.binLocation.form.binName': '库位名称',
|
||||||
|
'warehouse.binLocation.form.zoneCode': '库区编码',
|
||||||
|
'warehouse.binLocation.form.isEnabled': '启用',
|
||||||
|
'warehouse.binLocation.form.remark': '备注',
|
||||||
|
'warehouse.binLocation.message.warehouseLoadFailed': '加载仓库失败',
|
||||||
|
'warehouse.binLocation.message.loadFailed': '加载库位失败',
|
||||||
|
'warehouse.binLocation.message.selectWarehouse': '请先选择仓库',
|
||||||
|
'warehouse.binLocation.message.required': '请填写库位编码',
|
||||||
|
'warehouse.binLocation.message.saveSuccess': '保存成功',
|
||||||
|
'warehouse.binLocation.message.saveFailed': '保存失败',
|
||||||
'warehouse.parcel.status.all': '全部',
|
'warehouse.parcel.status.all': '全部',
|
||||||
'warehouse.parcel.status.inTransit': '在途',
|
'warehouse.parcel.status.inTransit': '在途',
|
||||||
'warehouse.parcel.status.arrived': '已到仓',
|
'warehouse.parcel.status.arrived': '已到仓',
|
||||||
@ -228,7 +260,11 @@ export default {
|
|||||||
'warehouse.inbound.line.skuCode': 'SKU',
|
'warehouse.inbound.line.skuCode': 'SKU',
|
||||||
'warehouse.inbound.line.plannedQty': '计划',
|
'warehouse.inbound.line.plannedQty': '计划',
|
||||||
'warehouse.inbound.line.receivedQty': '实收',
|
'warehouse.inbound.line.receivedQty': '实收',
|
||||||
|
'warehouse.inbound.line.binLocation': '上架库位',
|
||||||
|
'warehouse.inbound.form.selectBin': '请选择库位',
|
||||||
'warehouse.inbound.actions.delete': '删除预报',
|
'warehouse.inbound.actions.delete': '删除预报',
|
||||||
|
'warehouse.inbound.actions.confirmReceive': '确认收货',
|
||||||
|
'warehouse.inbound.modal.confirmReceive': '确认收货上架',
|
||||||
'warehouse.inbound.message.loadFailed': '加载入库预报失败',
|
'warehouse.inbound.message.loadFailed': '加载入库预报失败',
|
||||||
'warehouse.inbound.message.saveSuccess': '保存成功',
|
'warehouse.inbound.message.saveSuccess': '保存成功',
|
||||||
'warehouse.inbound.message.saveFailed': '保存失败',
|
'warehouse.inbound.message.saveFailed': '保存失败',
|
||||||
@ -236,6 +272,10 @@ export default {
|
|||||||
'warehouse.inbound.message.required': '请选择仓库',
|
'warehouse.inbound.message.required': '请选择仓库',
|
||||||
'warehouse.inbound.message.boxRequired': '请至少为一个箱子选择 SKU',
|
'warehouse.inbound.message.boxRequired': '请至少为一个箱子选择 SKU',
|
||||||
'warehouse.inbound.message.detailFailed': '加载详情失败',
|
'warehouse.inbound.message.detailFailed': '加载详情失败',
|
||||||
|
'warehouse.inbound.message.receiveBinRequired': '有实收数量时必须选择上架库位',
|
||||||
|
'warehouse.inbound.message.receiveQtyInvalid': '实收数量不能为负',
|
||||||
|
'warehouse.inbound.message.receiveSuccess': '收货成功',
|
||||||
|
'warehouse.inbound.message.receiveFailed': '收货失败',
|
||||||
'order.platformStore.platform.manual': '手工店铺',
|
'order.platformStore.platform.manual': '手工店铺',
|
||||||
'order.platformStore.table.siteCountry': '站点国家',
|
'order.platformStore.table.siteCountry': '站点国家',
|
||||||
'order.platformStore.table.autoFulfill': '自动发货',
|
'order.platformStore.table.autoFulfill': '自动发货',
|
||||||
|
|||||||
@ -20,6 +20,7 @@ const WAREHOUSE: AppRouteRecordRaw = {
|
|||||||
locale: 'menu.warehouse.parcel',
|
locale: 'menu.warehouse.parcel',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
permission: 'Warehouse.Parcel.Query',
|
permission: 'Warehouse.Parcel.Query',
|
||||||
|
order: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -30,6 +31,7 @@ const WAREHOUSE: AppRouteRecordRaw = {
|
|||||||
locale: 'menu.warehouse.inventory',
|
locale: 'menu.warehouse.inventory',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
permission: 'Warehouse.Inventory.Query',
|
permission: 'Warehouse.Inventory.Query',
|
||||||
|
order: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,6 +42,7 @@ const WAREHOUSE: AppRouteRecordRaw = {
|
|||||||
locale: 'menu.warehouse.sku',
|
locale: 'menu.warehouse.sku',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
permission: 'Warehouse.Sku.Query',
|
permission: 'Warehouse.Sku.Query',
|
||||||
|
order: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -50,6 +53,18 @@ const WAREHOUSE: AppRouteRecordRaw = {
|
|||||||
locale: 'menu.warehouse.inboundNotice',
|
locale: 'menu.warehouse.inboundNotice',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
permission: 'Warehouse.InboundNotice.Query',
|
permission: 'Warehouse.InboundNotice.Query',
|
||||||
|
order: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'binLocation',
|
||||||
|
name: 'WarehouseBinLocation',
|
||||||
|
component: () => import('@/views/warehouse/binLocation/index.vue'),
|
||||||
|
meta: {
|
||||||
|
locale: 'menu.warehouse.binLocation',
|
||||||
|
requiresAuth: true,
|
||||||
|
permission: 'Warehouse.BinLocation.Query',
|
||||||
|
order: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -71,3 +71,37 @@
|
|||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
color: var(--app-primary-color);
|
color: var(--app-primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-group) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-btn) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
transition: color 0.2s ease, background-color 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-btn--view:hover),
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-btn--edit:hover),
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-btn--relocate:hover),
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-btn--receive:hover) {
|
||||||
|
color: var(--app-primary-color);
|
||||||
|
background: var(--app-primary-color-light, #e6f7f7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-list-page-card .list-wrap :deep(.wh-action-btn--delete:hover) {
|
||||||
|
color: rgb(var(--danger-6));
|
||||||
|
background: rgb(var(--danger-1));
|
||||||
|
}
|
||||||
|
|||||||
28
src/utils/warehouseActionRenderer.ts
Normal file
28
src/utils/warehouseActionRenderer.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/** Handsontable 仓储列表操作列:图标按钮 HTML(配合 data-action 点击) */
|
||||||
|
|
||||||
|
const ICON_STYLE = 'width:17px;height:17px;display:block;pointer-events:none'
|
||||||
|
|
||||||
|
export const warehouseActionIcons = {
|
||||||
|
view: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="${ICON_STYLE}"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>`,
|
||||||
|
edit: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="${ICON_STYLE}"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>`,
|
||||||
|
delete: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="${ICON_STYLE}"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>`,
|
||||||
|
relocate: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="${ICON_STYLE}"><path d="M7 7h11l-2.5-2.5"/><path d="M17 17H6l2.5 2.5"/><path d="M7 7v4"/><path d="M17 17v-4"/></svg>`,
|
||||||
|
receive: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="${ICON_STYLE}"><path d="M20 6 9 17l-5-5"/><path d="M21 12v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h11"/></svg>`,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type WarehouseActionVariant = keyof typeof warehouseActionIcons
|
||||||
|
|
||||||
|
export function renderWarehouseActionBtn(
|
||||||
|
action: string,
|
||||||
|
title: string,
|
||||||
|
variant: WarehouseActionVariant = action as WarehouseActionVariant,
|
||||||
|
): string {
|
||||||
|
const icon = warehouseActionIcons[variant] || warehouseActionIcons.view
|
||||||
|
const safeTitle = String(title).replace(/"/g, '"')
|
||||||
|
return `<span class="wh-action-btn wh-action-btn--${variant}" data-action="${action}" title="${safeTitle}" role="button">${icon}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapWarehouseActions(parts: string[]): string {
|
||||||
|
if (!parts.length) return ''
|
||||||
|
return `<div class="wh-action-group">${parts.join('')}</div>`
|
||||||
|
}
|
||||||
449
src/views/warehouse/binLocation/index.vue
Normal file
449
src/views/warehouse/binLocation/index.vue
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||||||
|
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||||||
|
import ModalPro from '@/components/ModalPro/index.vue'
|
||||||
|
import useModal from '@/components/ModalPro/useModal'
|
||||||
|
import CustomLeftMenu from '@/components/CustomLeftMenu/index.vue'
|
||||||
|
import type { CustomLeftMenuTreeNode } from '@/components/CustomLeftMenu/types'
|
||||||
|
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
||||||
|
import { getWarehouseBinLocationList, saveWarehouseBinLocation } from '@/api/warehouse'
|
||||||
|
import { getWarehouseList } from '@/api/system/warehouse'
|
||||||
|
import { $baseMessage } from '@/utils'
|
||||||
|
import { hasPermission } from '@/utils/rbac-permission'
|
||||||
|
import { normalizeListRow, pickField } from '@/utils/warehouseListMapper'
|
||||||
|
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
|
||||||
|
|
||||||
|
defineOptions({ name: 'WarehouseBinLocation' })
|
||||||
|
|
||||||
|
interface WarehouseItem {
|
||||||
|
id: number
|
||||||
|
warehouseName: string
|
||||||
|
warehouseCode?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||||
|
|
||||||
|
const warehouseLoading = ref(false)
|
||||||
|
const warehouseList = ref<WarehouseItem[]>([])
|
||||||
|
const warehouseSearchKeyword = ref('')
|
||||||
|
const selectedWarehouseId = ref<number>()
|
||||||
|
const expandedKeys = ref<string[]>(['category-warehouses'])
|
||||||
|
|
||||||
|
const listLoading = ref(false)
|
||||||
|
const tableRows = ref<any[]>([])
|
||||||
|
const keyword = ref('')
|
||||||
|
const enabledFilter = ref<boolean | undefined>(undefined)
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(30)
|
||||||
|
const totalCount = ref(0)
|
||||||
|
|
||||||
|
const [registerFormModal, { openModal, closeModal }] = useModal() as any
|
||||||
|
const saving = ref(false)
|
||||||
|
const formModel = reactive({
|
||||||
|
id: 0,
|
||||||
|
warehouseId: 0,
|
||||||
|
binCode: '',
|
||||||
|
binName: '',
|
||||||
|
zoneCode: '',
|
||||||
|
isEnabled: true,
|
||||||
|
remark: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedWarehouse = computed(() =>
|
||||||
|
warehouseList.value.find((w) => w.id === selectedWarehouseId.value),
|
||||||
|
)
|
||||||
|
|
||||||
|
const warehouseTreeData = computed<CustomLeftMenuTreeNode[]>(() => {
|
||||||
|
const kw = warehouseSearchKeyword.value.trim().toLowerCase()
|
||||||
|
const filtered = warehouseList.value.filter((w) => {
|
||||||
|
if (!kw) return true
|
||||||
|
return (
|
||||||
|
w.warehouseName.toLowerCase().includes(kw)
|
||||||
|
|| (w.warehouseCode || '').toLowerCase().includes(kw)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'category-warehouses',
|
||||||
|
title: t('warehouse.common.warehouse'),
|
||||||
|
children: filtered.map((w) => ({
|
||||||
|
key: `item-${w.id}`,
|
||||||
|
title: w.warehouseName,
|
||||||
|
isLeaf: true,
|
||||||
|
id: w.id,
|
||||||
|
name: w.warehouseName,
|
||||||
|
code: w.warehouseCode,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedKeys = computed(() =>
|
||||||
|
selectedWarehouseId.value ? [`item-${selectedWarehouseId.value}`] : [],
|
||||||
|
)
|
||||||
|
|
||||||
|
const pagedRows = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * pageSize.value
|
||||||
|
return tableRows.value.slice(start, start + pageSize.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const hotColumns = computed(() => [
|
||||||
|
{ key: 'binCode', label: t('warehouse.binLocation.table.binCode'), width: 140 },
|
||||||
|
{ key: 'binName', label: t('warehouse.binLocation.table.binName'), width: 160 },
|
||||||
|
{ key: 'zoneCode', label: t('warehouse.binLocation.table.zoneCode'), width: 120 },
|
||||||
|
{ key: 'isEnabledLabel', label: t('warehouse.binLocation.table.isEnabled'), width: 90 },
|
||||||
|
{ key: 'remark', label: t('warehouse.binLocation.table.remark'), width: 180 },
|
||||||
|
])
|
||||||
|
|
||||||
|
const modalTitle = computed(() =>
|
||||||
|
formModel.id > 0 ? t('warehouse.binLocation.modal.edit') : t('warehouse.binLocation.modal.add'),
|
||||||
|
)
|
||||||
|
|
||||||
|
function mapRow(raw: Record<string, unknown>) {
|
||||||
|
const row = normalizeListRow(raw)
|
||||||
|
const enabled = Boolean(pickField(row, 'isEnabled', 'IsEnabled'))
|
||||||
|
return {
|
||||||
|
id: pickField<number>(row, 'id', 'Id'),
|
||||||
|
warehouseId: pickField<number>(row, 'warehouseId', 'WarehouseId'),
|
||||||
|
binCode: pickField(row, 'binCode', 'BinCode') ?? '-',
|
||||||
|
binName: pickField(row, 'binName', 'BinName') ?? '',
|
||||||
|
zoneCode: pickField(row, 'zoneCode', 'ZoneCode') ?? '',
|
||||||
|
isEnabled: enabled,
|
||||||
|
isEnabledLabel: enabled ? t('warehouse.common.yes') : t('warehouse.common.no'),
|
||||||
|
remark: pickField(row, 'remark', 'Remark') ?? '',
|
||||||
|
_raw: row,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWarehouses() {
|
||||||
|
warehouseLoading.value = true
|
||||||
|
try {
|
||||||
|
const { items } = await getWarehouseList({ page: 1, pageSize: 500 })
|
||||||
|
warehouseList.value = (items || []).map((w: any) => ({
|
||||||
|
id: Number(w.id || w.Id),
|
||||||
|
warehouseName: String(w.warehouseName || w.WarehouseName || `#${w.id}`),
|
||||||
|
warehouseCode: w.warehouseCode || w.WarehouseCode || '',
|
||||||
|
}))
|
||||||
|
if (!selectedWarehouseId.value && warehouseList.value.length) {
|
||||||
|
selectedWarehouseId.value = warehouseList.value[0].id
|
||||||
|
} else if (
|
||||||
|
selectedWarehouseId.value
|
||||||
|
&& !warehouseList.value.some((w) => w.id === selectedWarehouseId.value)
|
||||||
|
) {
|
||||||
|
selectedWarehouseId.value = warehouseList.value[0]?.id
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
warehouseList.value = []
|
||||||
|
$baseMessage(e?.message || t('warehouse.binLocation.message.warehouseLoadFailed'), 'error')
|
||||||
|
} finally {
|
||||||
|
warehouseLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadList() {
|
||||||
|
if (!selectedWarehouseId.value) {
|
||||||
|
tableRows.value = []
|
||||||
|
totalCount.value = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listLoading.value = true
|
||||||
|
try {
|
||||||
|
const { items } = await getWarehouseBinLocationList({
|
||||||
|
warehouseId: selectedWarehouseId.value,
|
||||||
|
keyword: keyword.value || undefined,
|
||||||
|
isEnabled: enabledFilter.value,
|
||||||
|
})
|
||||||
|
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
||||||
|
totalCount.value = tableRows.value.length
|
||||||
|
const maxPage = Math.max(1, Math.ceil(totalCount.value / pageSize.value) || 1)
|
||||||
|
if (currentPage.value > maxPage) currentPage.value = maxPage
|
||||||
|
} catch (e: any) {
|
||||||
|
tableRows.value = []
|
||||||
|
totalCount.value = 0
|
||||||
|
$baseMessage(e?.message || t('warehouse.binLocation.message.loadFailed'), 'error')
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWarehouseFromKey(key?: string) {
|
||||||
|
if (!key || !key.startsWith('item-')) return
|
||||||
|
const id = Number(key.slice(5))
|
||||||
|
if (!Number.isFinite(id) || id <= 0) return
|
||||||
|
if (selectedWarehouseId.value === id) return
|
||||||
|
selectedWarehouseId.value = id
|
||||||
|
currentPage.value = 1
|
||||||
|
keyword.value = ''
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWarehouseSelect(keys: string[]) {
|
||||||
|
selectWarehouseFromKey(keys[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWarehouseNodeClick(key: string) {
|
||||||
|
selectWarehouseFromKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
currentPage.value = 1
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
currentPage.value = page
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(size: number) {
|
||||||
|
pageSize.value = size
|
||||||
|
currentPage.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formModel.id = 0
|
||||||
|
formModel.warehouseId = selectedWarehouseId.value || 0
|
||||||
|
formModel.binCode = ''
|
||||||
|
formModel.binName = ''
|
||||||
|
formModel.zoneCode = ''
|
||||||
|
formModel.isEnabled = true
|
||||||
|
formModel.remark = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
if (!hasPermission('Warehouse.BinLocation.Save')) return
|
||||||
|
if (!selectedWarehouseId.value) {
|
||||||
|
$baseMessage(t('warehouse.binLocation.message.selectWarehouse'), 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resetForm()
|
||||||
|
openModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(row: any) {
|
||||||
|
if (!hasPermission('Warehouse.BinLocation.Save')) return
|
||||||
|
const raw = row._raw || row
|
||||||
|
formModel.id = Number(pickField(raw, 'id', 'Id') ?? 0)
|
||||||
|
formModel.warehouseId = Number(pickField(raw, 'warehouseId', 'WarehouseId') ?? selectedWarehouseId.value)
|
||||||
|
formModel.binCode = String(pickField(raw, 'binCode', 'BinCode') ?? '')
|
||||||
|
formModel.binName = String(pickField(raw, 'binName', 'BinName') ?? '')
|
||||||
|
formModel.zoneCode = String(pickField(raw, 'zoneCode', 'ZoneCode') ?? '')
|
||||||
|
formModel.isEnabled = Boolean(pickField(raw, 'isEnabled', 'IsEnabled'))
|
||||||
|
formModel.remark = String(pickField(raw, 'remark', 'Remark') ?? '')
|
||||||
|
openModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave() {
|
||||||
|
if (!formModel.warehouseId || !formModel.binCode.trim()) {
|
||||||
|
$baseMessage(t('warehouse.binLocation.message.required'), 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await saveWarehouseBinLocation({
|
||||||
|
id: formModel.id,
|
||||||
|
warehouseId: formModel.warehouseId,
|
||||||
|
binCode: formModel.binCode.trim(),
|
||||||
|
binName: formModel.binName.trim() || undefined,
|
||||||
|
zoneCode: formModel.zoneCode.trim() || undefined,
|
||||||
|
isEnabled: formModel.isEnabled,
|
||||||
|
remark: formModel.remark.trim() || undefined,
|
||||||
|
})
|
||||||
|
$baseMessage(t('warehouse.binLocation.message.saveSuccess'), 'success')
|
||||||
|
closeModal()
|
||||||
|
await loadList()
|
||||||
|
} catch (e: any) {
|
||||||
|
$baseMessage(e?.message || t('warehouse.binLocation.message.saveFailed'), 'error')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function actionRenderer() {
|
||||||
|
if (!hasPermission('Warehouse.BinLocation.Save')) return ''
|
||||||
|
return wrapWarehouseActions([
|
||||||
|
renderWarehouseActionBtn('edit', t('warehouse.common.edit')),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleActionClick(row: any, _idx: number, action?: string) {
|
||||||
|
if (action === 'edit') handleEdit(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadWarehouses()
|
||||||
|
await loadList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rbac-split-layout warehouse-bin-location-page">
|
||||||
|
<CustomLeftMenu
|
||||||
|
class="left-sidebar"
|
||||||
|
:tree-data="warehouseTreeData"
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
v-model:expanded-keys="expandedKeys"
|
||||||
|
v-model:search-keyword="warehouseSearchKeyword"
|
||||||
|
:loading="warehouseLoading"
|
||||||
|
:search-placeholder="t('warehouse.binLocation.search.warehouse')"
|
||||||
|
:refresh-title="t('warehouse.binLocation.actions.refreshWarehouse')"
|
||||||
|
:loading-text="t('warehouse.binLocation.loading.warehouse')"
|
||||||
|
:show-add="false"
|
||||||
|
:show-node-actions="false"
|
||||||
|
:show-method="false"
|
||||||
|
:show-map-count="false"
|
||||||
|
:show-node-code="true"
|
||||||
|
:show-pagination="true"
|
||||||
|
:page-size="30"
|
||||||
|
@search="() => {}"
|
||||||
|
@refresh="loadWarehouses"
|
||||||
|
@select="handleWarehouseSelect"
|
||||||
|
@node-click="handleWarehouseNodeClick"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="right-panel">
|
||||||
|
<div class="warehouse-list-page-card bin-right-card">
|
||||||
|
<div v-if="!selectedWarehouse" class="panel-empty">
|
||||||
|
<p>{{ t('warehouse.binLocation.emptySelect') }}</p>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<a-space wrap :size="8">
|
||||||
|
<span class="selected-wh-name">{{ selectedWarehouse.warehouseName }}</span>
|
||||||
|
<a-input
|
||||||
|
v-model="keyword"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('warehouse.binLocation.search.keyword')"
|
||||||
|
style="width: 200px"
|
||||||
|
@press-enter="handleSearch"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-model="enabledFilter"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('warehouse.binLocation.filter.isEnabled')"
|
||||||
|
style="width: 120px"
|
||||||
|
@change="handleSearch"
|
||||||
|
>
|
||||||
|
<a-option :value="true">{{ t('warehouse.common.yes') }}</a-option>
|
||||||
|
<a-option :value="false">{{ t('warehouse.common.no') }}</a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-button type="primary" @click="handleSearch">{{ t('warehouse.common.search') }}</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="hasPermission('Warehouse.BinLocation.Save')"
|
||||||
|
type="primary"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
|
{{ t('warehouse.binLocation.actions.add') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<PaginationPro
|
||||||
|
:total-count="totalCount"
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:page-sizes="[10, 30, 50, 100]"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref="tableContainerRef" class="list-wrap">
|
||||||
|
<HandsontablePro
|
||||||
|
:columns="hotColumns"
|
||||||
|
:data="pagedRows"
|
||||||
|
:height="tableHeight"
|
||||||
|
:loading="listLoading"
|
||||||
|
:stretch-columns="true"
|
||||||
|
show-action-column
|
||||||
|
:action-column-width="70"
|
||||||
|
:action-renderer="actionRenderer"
|
||||||
|
@action-click="handleActionClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ModalPro
|
||||||
|
:title="modalTitle"
|
||||||
|
:width="560"
|
||||||
|
:mask-closable="false"
|
||||||
|
@register="registerFormModal"
|
||||||
|
@cancel="closeModal"
|
||||||
|
>
|
||||||
|
<div class="wh-form-modal">
|
||||||
|
<div class="wh-form-info-bar">
|
||||||
|
<div>{{ t('warehouse.common.warehouse') }}:{{ selectedWarehouse?.warehouseName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<a-form
|
||||||
|
:model="formModel"
|
||||||
|
layout="horizontal"
|
||||||
|
:label-col-props="{ span: 6 }"
|
||||||
|
:wrapper-col-props="{ span: 16 }"
|
||||||
|
>
|
||||||
|
<a-form-item :label="t('warehouse.binLocation.form.binCode')" required>
|
||||||
|
<a-input v-model="formModel.binCode" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('warehouse.binLocation.form.binName')">
|
||||||
|
<a-input v-model="formModel.binName" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('warehouse.binLocation.form.zoneCode')">
|
||||||
|
<a-input v-model="formModel.zoneCode" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('warehouse.binLocation.form.isEnabled')">
|
||||||
|
<a-switch v-model="formModel.isEnabled" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('warehouse.binLocation.form.remark')">
|
||||||
|
<a-textarea v-model="formModel.remark" :auto-size="{ minRows: 2, maxRows: 4 }" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
<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-space>
|
||||||
|
</template>
|
||||||
|
</ModalPro>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
@import '@/components/RbacSplit/rbac-split-layout.less';
|
||||||
|
@import '@/styles/warehouse-list-page.less';
|
||||||
|
@import '@/styles/warehouse-form-modal.less';
|
||||||
|
|
||||||
|
.warehouse-bin-location-page {
|
||||||
|
padding: 16px;
|
||||||
|
background: #f0f3f4;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bin-right-card {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-empty {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
padding: 48px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-wh-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -8,14 +8,16 @@ import ModalPro from '@/components/ModalPro/index.vue'
|
|||||||
import useModal from '@/components/ModalPro/useModal'
|
import useModal from '@/components/ModalPro/useModal'
|
||||||
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
||||||
import {
|
import {
|
||||||
|
confirmWarehouseInboundReceive,
|
||||||
deleteWarehouseInbound,
|
deleteWarehouseInbound,
|
||||||
|
getWarehouseBinLocationList,
|
||||||
getWarehouseInboundDetail,
|
getWarehouseInboundDetail,
|
||||||
getWarehouseInboundList,
|
getWarehouseInboundList,
|
||||||
getWarehouseInboundSummary,
|
getWarehouseInboundSummary,
|
||||||
|
getWarehouseSkuList,
|
||||||
saveWarehouseInbound,
|
saveWarehouseInbound,
|
||||||
} from '@/api/warehouse'
|
} from '@/api/warehouse'
|
||||||
import { getWarehouseList } from '@/api/system/warehouse'
|
import { getWarehouseList } from '@/api/system/warehouse'
|
||||||
import { getWarehouseSkuList } from '@/api/warehouse'
|
|
||||||
import { $baseMessage } from '@/utils'
|
import { $baseMessage } from '@/utils'
|
||||||
import { hasPermission } from '@/utils/rbac-permission'
|
import { hasPermission } from '@/utils/rbac-permission'
|
||||||
import {
|
import {
|
||||||
@ -25,6 +27,8 @@ import {
|
|||||||
pickField,
|
pickField,
|
||||||
resolveWarehouseLabel,
|
resolveWarehouseLabel,
|
||||||
} from '@/utils/warehouseListMapper'
|
} from '@/utils/warehouseListMapper'
|
||||||
|
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
|
||||||
|
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer'
|
||||||
|
|
||||||
defineOptions({ name: 'WarehouseInboundNotice' })
|
defineOptions({ name: 'WarehouseInboundNotice' })
|
||||||
|
|
||||||
@ -45,8 +49,31 @@ interface InboundBoxForm {
|
|||||||
lines: InboundLineForm[]
|
lines: InboundLineForm[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReceiveLineForm {
|
||||||
|
lineId: number
|
||||||
|
skuCode: string
|
||||||
|
plannedQty: number
|
||||||
|
receivedQty: number
|
||||||
|
binLocationId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReceiveBoxForm {
|
||||||
|
boxId: number
|
||||||
|
markBoxNo: string
|
||||||
|
boxSeqNo: string
|
||||||
|
lines: ReceiveLineForm[]
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||||
|
const {
|
||||||
|
isAdmin,
|
||||||
|
customerId: adminCustomerId,
|
||||||
|
customerOptions,
|
||||||
|
withCustomerId,
|
||||||
|
ensureMutationCustomerId,
|
||||||
|
resolveSelectedCustomerId,
|
||||||
|
} = useWarehouseAdminCustomer()
|
||||||
|
|
||||||
const receiptStatus = ref<string>()
|
const receiptStatus = ref<string>()
|
||||||
const summary = ref({ forecasted: 0, received: 0, completed: 0 })
|
const summary = ref({ forecasted: 0, received: 0, completed: 0 })
|
||||||
@ -74,7 +101,12 @@ const detailVisible = ref(false)
|
|||||||
const detailData = ref<any>(null)
|
const detailData = ref<any>(null)
|
||||||
|
|
||||||
const [registerFormModal, { openModal, closeModal }] = useModal() as any
|
const [registerFormModal, { openModal, closeModal }] = useModal() as any
|
||||||
|
const [registerReceiveModal, { openModal: openReceiveModal, closeModal: closeReceiveModal }] = useModal() as any
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
const receiveSaving = ref(false)
|
||||||
|
const receiveNotice = ref<any>(null)
|
||||||
|
const receiveBoxes = ref<ReceiveBoxForm[]>([])
|
||||||
|
const receiveBinOptions = ref<Array<{ label: string; value: number }>>([])
|
||||||
const formModel = reactive({
|
const formModel = reactive({
|
||||||
id: 0,
|
id: 0,
|
||||||
warehouseId: undefined as number | undefined,
|
warehouseId: undefined as number | undefined,
|
||||||
@ -89,6 +121,11 @@ const modalWarehouseName = computed(() => {
|
|||||||
return wh?.label || ''
|
return wh?.label || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const receiveWarehouseName = computed(() => {
|
||||||
|
const whId = receiveNotice.value?.warehouseId
|
||||||
|
return resolveWarehouseLabel(whId, warehouseNameMap.value)
|
||||||
|
})
|
||||||
|
|
||||||
const boxCount = computed(() => boxRows.value.length)
|
const boxCount = computed(() => boxRows.value.length)
|
||||||
const totalSkuCount = computed(() =>
|
const totalSkuCount = computed(() =>
|
||||||
boxRows.value.reduce((sum, box) => sum + box.lines.filter((l) => l.skuId).length, 0),
|
boxRows.value.reduce((sum, box) => sum + box.lines.filter((l) => l.skuId).length, 0),
|
||||||
@ -207,7 +244,7 @@ async function loadWarehouses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSkus() {
|
async function loadSkus() {
|
||||||
const { items } = await getWarehouseSkuList({ page: 1, pageSize: 500 }).catch(() => ({ items: [] }))
|
const { items } = await getWarehouseSkuList(withCustomerId({ page: 1, pageSize: 500 })).catch(() => ({ items: [] }))
|
||||||
skuOptions.value = (items || []).map((s: any) => ({
|
skuOptions.value = (items || []).map((s: any) => ({
|
||||||
label: `${s.skuCode || s.SkuCode} - ${s.skuName || s.SkuName}`,
|
label: `${s.skuCode || s.SkuCode} - ${s.skuName || s.SkuName}`,
|
||||||
value: Number(s.id || s.Id),
|
value: Number(s.id || s.Id),
|
||||||
@ -217,7 +254,9 @@ async function loadSkus() {
|
|||||||
|
|
||||||
async function loadSummary() {
|
async function loadSummary() {
|
||||||
try {
|
try {
|
||||||
const data = await getWarehouseInboundSummary({ warehouseId: warehouseId.value })
|
const data = await getWarehouseInboundSummary(withCustomerId({
|
||||||
|
warehouseId: warehouseId.value,
|
||||||
|
}))
|
||||||
summary.value = {
|
summary.value = {
|
||||||
forecasted: data?.forecasted ?? data?.Forecasted ?? 0,
|
forecasted: data?.forecasted ?? data?.Forecasted ?? 0,
|
||||||
received: data?.received ?? data?.Received ?? 0,
|
received: data?.received ?? data?.Received ?? 0,
|
||||||
@ -231,13 +270,13 @@ async function loadSummary() {
|
|||||||
async function loadList() {
|
async function loadList() {
|
||||||
listLoading.value = true
|
listLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { items, total } = await getWarehouseInboundList({
|
const { items, total } = await getWarehouseInboundList(withCustomerId({
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
receiptStatus: receiptStatus.value,
|
receiptStatus: receiptStatus.value,
|
||||||
keyword: keyword.value || undefined,
|
keyword: keyword.value || undefined,
|
||||||
warehouseId: warehouseId.value,
|
warehouseId: warehouseId.value,
|
||||||
})
|
}))
|
||||||
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
||||||
totalCount.value = Number(total || 0)
|
totalCount.value = Number(total || 0)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -265,15 +304,129 @@ function handleSearch() {
|
|||||||
|
|
||||||
async function showDetail(row: any) {
|
async function showDetail(row: any) {
|
||||||
try {
|
try {
|
||||||
detailData.value = await getWarehouseInboundDetail(row._raw?.id || row.id)
|
detailData.value = await getWarehouseInboundDetail(
|
||||||
|
row._raw?.id || row.id,
|
||||||
|
isAdmin.value ? resolveSelectedCustomerId() : undefined,
|
||||||
|
)
|
||||||
detailVisible.value = true
|
detailVisible.value = true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
$baseMessage(e?.message || t('warehouse.inbound.message.detailFailed'), 'error')
|
$baseMessage(e?.message || t('warehouse.inbound.message.detailFailed'), 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadReceiveBinOptions(whId?: number) {
|
||||||
|
if (!whId) {
|
||||||
|
receiveBinOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { items } = await getWarehouseBinLocationList({ warehouseId: whId, isEnabled: true })
|
||||||
|
receiveBinOptions.value = (items || []).map((b: any) => ({
|
||||||
|
label: `${b.binCode || b.BinCode}${b.binName || b.BinName ? ` - ${b.binName || b.BinName}` : ''}`,
|
||||||
|
value: Number(b.id || b.Id),
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
receiveBinOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openConfirmReceive(row: any) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
hasPermission('Warehouse.InboundNotice.ConfirmReceive')
|
||||||
|
|| hasPermission('Warehouse.InboundNotice.Save')
|
||||||
|
)
|
||||||
|
|| row._status === 'completed'
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
|
try {
|
||||||
|
const detail = await getWarehouseInboundDetail(
|
||||||
|
row._raw?.id || row.id,
|
||||||
|
isAdmin.value ? resolveSelectedCustomerId() : undefined,
|
||||||
|
)
|
||||||
|
const normalized = normalizeListRow(detail || {})
|
||||||
|
const whId = pickField<number>(normalized, 'warehouseId', 'WarehouseId')
|
||||||
|
const boxesRaw = pickField(normalized, 'boxes', 'Boxes')
|
||||||
|
const boxes = Array.isArray(boxesRaw) ? boxesRaw : []
|
||||||
|
receiveNotice.value = {
|
||||||
|
id: pickField<number>(normalized, 'id', 'Id'),
|
||||||
|
noticeNo: pickField(normalized, 'noticeNo', 'NoticeNo') ?? row.noticeNo,
|
||||||
|
warehouseId: whId,
|
||||||
|
}
|
||||||
|
receiveBoxes.value = boxes.map((box: any) => {
|
||||||
|
const b = normalizeListRow(box)
|
||||||
|
const linesRaw = pickField(b, 'lines', 'Lines')
|
||||||
|
const lines = Array.isArray(linesRaw) ? linesRaw : []
|
||||||
|
return {
|
||||||
|
boxId: Number(pickField(b, 'id', 'Id') || 0),
|
||||||
|
markBoxNo: String(pickField(b, 'markBoxNo', 'MarkBoxNo') ?? ''),
|
||||||
|
boxSeqNo: String(pickField(b, 'boxSeqNo', 'BoxSeqNo') ?? ''),
|
||||||
|
lines: lines.map((line: any) => {
|
||||||
|
const l = normalizeListRow(line)
|
||||||
|
const planned = Number(pickField(l, 'plannedQty', 'PlannedQty') ?? 0)
|
||||||
|
const received = pickField<number>(l, 'receivedQty', 'ReceivedQty')
|
||||||
|
return {
|
||||||
|
lineId: Number(pickField(l, 'id', 'Id') || 0),
|
||||||
|
skuCode: String(pickField(l, 'skuCode', 'SkuCode') ?? '-'),
|
||||||
|
plannedQty: planned,
|
||||||
|
receivedQty: received != null ? Number(received) : planned,
|
||||||
|
binLocationId: pickField<number>(l, 'binLocationId', 'BinLocationId') || undefined,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await loadReceiveBinOptions(whId)
|
||||||
|
openReceiveModal(true)
|
||||||
|
} catch (e: any) {
|
||||||
|
$baseMessage(e?.message || t('warehouse.inbound.message.detailFailed'), 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitConfirmReceive() {
|
||||||
|
const notice = receiveNotice.value
|
||||||
|
if (!notice?.id) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
|
for (const box of receiveBoxes.value) {
|
||||||
|
for (const line of box.lines) {
|
||||||
|
if (line.receivedQty > 0 && !line.binLocationId) {
|
||||||
|
$baseMessage(t('warehouse.inbound.message.receiveBinRequired'), 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (line.receivedQty < 0) {
|
||||||
|
$baseMessage(t('warehouse.inbound.message.receiveQtyInvalid'), 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
receiveSaving.value = true
|
||||||
|
try {
|
||||||
|
await confirmWarehouseInboundReceive(withCustomerId({
|
||||||
|
inboundNoticeId: notice.id,
|
||||||
|
boxes: receiveBoxes.value.map((box) => ({
|
||||||
|
boxId: box.boxId,
|
||||||
|
lines: box.lines.map((line) => ({
|
||||||
|
lineId: line.lineId,
|
||||||
|
receivedQty: Number(line.receivedQty) || 0,
|
||||||
|
binLocationId: line.receivedQty > 0 ? Number(line.binLocationId) : 0,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
$baseMessage(t('warehouse.inbound.message.receiveSuccess'), 'success')
|
||||||
|
closeReceiveModal()
|
||||||
|
await loadSummary()
|
||||||
|
await loadList()
|
||||||
|
} catch (e: any) {
|
||||||
|
$baseMessage(e?.message || t('warehouse.inbound.message.receiveFailed'), 'error')
|
||||||
|
} finally {
|
||||||
|
receiveSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
if (!hasPermission('Warehouse.InboundNotice.Save')) return
|
if (!hasPermission('Warehouse.InboundNotice.Save')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
formModel.id = 0
|
formModel.id = 0
|
||||||
formModel.warehouseId = warehouseId.value
|
formModel.warehouseId = warehouseId.value
|
||||||
formModel.markReference = ''
|
formModel.markReference = ''
|
||||||
@ -288,6 +441,7 @@ async function handleSave() {
|
|||||||
$baseMessage(t('warehouse.inbound.message.required'), 'warning')
|
$baseMessage(t('warehouse.inbound.message.required'), 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
const boxes = boxRows.value
|
const boxes = boxRows.value
|
||||||
.map((box) => ({
|
.map((box) => ({
|
||||||
boxSeqNo: box.boxSeqNo,
|
boxSeqNo: box.boxSeqNo,
|
||||||
@ -313,14 +467,14 @@ async function handleSave() {
|
|||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await saveWarehouseInbound({
|
await saveWarehouseInbound(withCustomerId({
|
||||||
id: formModel.id,
|
id: formModel.id,
|
||||||
warehouseId: formModel.warehouseId,
|
warehouseId: formModel.warehouseId,
|
||||||
markReference: formModel.markReference,
|
markReference: formModel.markReference,
|
||||||
inboundType: formModel.inboundType,
|
inboundType: formModel.inboundType,
|
||||||
remark: formModel.remark,
|
remark: formModel.remark,
|
||||||
boxes,
|
boxes,
|
||||||
})
|
}))
|
||||||
$baseMessage(t('warehouse.inbound.message.saveSuccess'), 'success')
|
$baseMessage(t('warehouse.inbound.message.saveSuccess'), 'success')
|
||||||
closeModal()
|
closeModal()
|
||||||
await loadSummary()
|
await loadSummary()
|
||||||
@ -334,6 +488,7 @@ async function handleSave() {
|
|||||||
|
|
||||||
function handleDelete(row: any) {
|
function handleDelete(row: any) {
|
||||||
if (!hasPermission('Warehouse.InboundNotice.Delete')) return
|
if (!hasPermission('Warehouse.InboundNotice.Delete')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: t('warehouse.inbound.actions.delete'),
|
title: t('warehouse.inbound.actions.delete'),
|
||||||
content: row.noticeNo,
|
content: row.noticeNo,
|
||||||
@ -347,15 +502,23 @@ function handleDelete(row: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function actionRenderer(row: any) {
|
function actionRenderer(row: any) {
|
||||||
const parts = [`<button type="button" class="hot-action-btn" data-action="detail">${t('warehouse.common.detail')}</button>`]
|
const parts = [renderWarehouseActionBtn('detail', t('warehouse.common.detail'), 'view')]
|
||||||
if (hasPermission('Warehouse.InboundNotice.Delete') && row._status === 'forecasted') {
|
if (
|
||||||
parts.push(`<button type="button" class="hot-action-btn" data-action="delete">${t('warehouse.common.delete')}</button>`)
|
(hasPermission('Warehouse.InboundNotice.ConfirmReceive')
|
||||||
|
|| hasPermission('Warehouse.InboundNotice.Save'))
|
||||||
|
&& row._status !== 'completed'
|
||||||
|
) {
|
||||||
|
parts.push(renderWarehouseActionBtn('receive', t('warehouse.inbound.actions.confirmReceive')))
|
||||||
}
|
}
|
||||||
return parts.join('')
|
if (hasPermission('Warehouse.InboundNotice.Delete') && row._status === 'forecasted') {
|
||||||
|
parts.push(renderWarehouseActionBtn('delete', t('warehouse.common.delete')))
|
||||||
|
}
|
||||||
|
return wrapWarehouseActions(parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActionClick(row: any, _i: number, action?: string) {
|
function handleActionClick(row: any, _i: number, action?: string) {
|
||||||
if (action === 'detail') showDetail(row)
|
if (action === 'detail') showDetail(row)
|
||||||
|
if (action === 'receive') openConfirmReceive(row)
|
||||||
if (action === 'delete') handleDelete(row)
|
if (action === 'delete') handleDelete(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,6 +548,16 @@ onMounted(async () => {
|
|||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<a-space wrap :size="8">
|
<a-space wrap :size="8">
|
||||||
|
<a-select
|
||||||
|
v-if="isAdmin"
|
||||||
|
v-model="adminCustomerId"
|
||||||
|
allow-clear
|
||||||
|
allow-search
|
||||||
|
:placeholder="t('warehouse.common.selectCustomer')"
|
||||||
|
:options="customerOptions"
|
||||||
|
style="width: 220px"
|
||||||
|
@change="() => { loadSkus(); loadSummary(); handleSearch() }"
|
||||||
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="warehouseId"
|
v-model="warehouseId"
|
||||||
allow-clear
|
allow-clear
|
||||||
@ -425,6 +598,7 @@ onMounted(async () => {
|
|||||||
:loading="listLoading"
|
:loading="listLoading"
|
||||||
:stretch-columns="true"
|
:stretch-columns="true"
|
||||||
show-action-column
|
show-action-column
|
||||||
|
:action-column-width="120"
|
||||||
:action-renderer="actionRenderer"
|
:action-renderer="actionRenderer"
|
||||||
@action-click="handleActionClick"
|
@action-click="handleActionClick"
|
||||||
/>
|
/>
|
||||||
@ -433,11 +607,11 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<a-drawer v-model:visible="detailVisible" :title="t('warehouse.inbound.drawer.detail')" :width="640">
|
<a-drawer v-model:visible="detailVisible" :title="t('warehouse.inbound.drawer.detail')" :width="640">
|
||||||
<template v-if="detailData">
|
<template v-if="detailData">
|
||||||
<p>{{ t('warehouse.inbound.table.noticeNo') }}: {{ detailData.noticeNo }}</p>
|
<p>{{ t('warehouse.inbound.table.noticeNo') }}: {{ detailData.noticeNo || detailData.NoticeNo }}</p>
|
||||||
<p>{{ t('warehouse.inbound.table.markReference') }}: {{ detailData.markReference || '-' }}</p>
|
<p>{{ t('warehouse.inbound.table.markReference') }}: {{ detailData.markReference || detailData.MarkReference || '-' }}</p>
|
||||||
<div v-for="box in detailData.boxes || []" :key="box.id" class="box-block">
|
<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.boxSeqNo }}</div>
|
<div class="box-title">{{ t('warehouse.inbound.box') }}: {{ box.markBoxNo || box.MarkBoxNo || box.boxSeqNo || box.BoxSeqNo }}</div>
|
||||||
<a-table :data="box.lines || []" :pagination="false" size="small">
|
<a-table :data="box.lines || box.Lines || []" :pagination="false" size="small">
|
||||||
<template #columns>
|
<template #columns>
|
||||||
<a-table-column :title="t('warehouse.inbound.line.skuCode')" data-index="skuCode" />
|
<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.plannedQty')" data-index="plannedQty" />
|
||||||
@ -448,6 +622,61 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
|
|
||||||
|
<ModalPro
|
||||||
|
:title="t('warehouse.inbound.modal.confirmReceive')"
|
||||||
|
:width="860"
|
||||||
|
:mask-closable="false"
|
||||||
|
:body-style="{ maxHeight: '70vh', overflow: 'auto' }"
|
||||||
|
@register="registerReceiveModal"
|
||||||
|
@cancel="closeReceiveModal"
|
||||||
|
>
|
||||||
|
<div class="wh-form-modal">
|
||||||
|
<div v-if="receiveNotice" class="wh-form-info-bar">
|
||||||
|
<div>{{ t('warehouse.inbound.table.noticeNo') }}:{{ receiveNotice.noticeNo }}</div>
|
||||||
|
<div>{{ t('warehouse.common.warehouse') }}:{{ receiveWarehouseName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="box in receiveBoxes" :key="box.boxId" class="receive-box-block">
|
||||||
|
<div class="box-title">
|
||||||
|
{{ t('warehouse.inbound.box') }} {{ box.boxSeqNo }}
|
||||||
|
<span v-if="box.markBoxNo">({{ box.markBoxNo }})</span>
|
||||||
|
</div>
|
||||||
|
<a-table :data="box.lines" :pagination="false" size="small" :bordered="{ cell: true }">
|
||||||
|
<template #columns>
|
||||||
|
<a-table-column :title="t('warehouse.inbound.line.skuCode')" data-index="skuCode" :width="140" />
|
||||||
|
<a-table-column :title="t('warehouse.inbound.line.plannedQty')" data-index="plannedQty" :width="90" />
|
||||||
|
<a-table-column :title="t('warehouse.inbound.line.receivedQty')" :width="130">
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-input-number v-model="record.receivedQty" :min="0" style="width: 100%" />
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column :title="t('warehouse.inbound.line.binLocation')" :width="220">
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-select
|
||||||
|
v-model="record.binLocationId"
|
||||||
|
:options="receiveBinOptions"
|
||||||
|
allow-search
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('warehouse.inbound.form.selectBin')"
|
||||||
|
:disabled="!(record.receivedQty > 0)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button @click="closeReceiveModal">{{ t('warehouse.common.cancel') }}</a-button>
|
||||||
|
<a-button type="primary" :loading="receiveSaving" @click="submitConfirmReceive">
|
||||||
|
{{ t('warehouse.inbound.actions.confirmReceive') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</ModalPro>
|
||||||
|
|
||||||
<ModalPro
|
<ModalPro
|
||||||
:title="t('warehouse.inbound.modal.add')"
|
:title="t('warehouse.inbound.modal.add')"
|
||||||
:width="800"
|
:width="800"
|
||||||
@ -594,4 +823,7 @@ onMounted(async () => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
.receive-box-block {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -21,11 +21,21 @@ import {
|
|||||||
pickField,
|
pickField,
|
||||||
resolveWarehouseLabel,
|
resolveWarehouseLabel,
|
||||||
} from '@/utils/warehouseListMapper'
|
} from '@/utils/warehouseListMapper'
|
||||||
|
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
|
||||||
|
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer'
|
||||||
|
|
||||||
defineOptions({ name: 'WarehouseInventory' })
|
defineOptions({ name: 'WarehouseInventory' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||||
|
const {
|
||||||
|
isAdmin,
|
||||||
|
customerId: adminCustomerId,
|
||||||
|
customerOptions,
|
||||||
|
withCustomerId,
|
||||||
|
ensureMutationCustomerId,
|
||||||
|
resolveSelectedCustomerId,
|
||||||
|
} = useWarehouseAdminCustomer()
|
||||||
|
|
||||||
const listLoading = ref(false)
|
const listLoading = ref(false)
|
||||||
const tableRows = ref<any[]>([])
|
const tableRows = ref<any[]>([])
|
||||||
@ -43,7 +53,8 @@ const activeSku = ref<any>(null)
|
|||||||
|
|
||||||
const [registerRelocateModal, { openModal: openRelocate, closeModal: closeRelocate }] = useModal() as any
|
const [registerRelocateModal, { openModal: openRelocate, closeModal: closeRelocate }] = useModal() as any
|
||||||
const relocateSaving = ref(false)
|
const relocateSaving = ref(false)
|
||||||
const binOptions = ref<Array<{ label: string; value: number }>>([])
|
const sourceBinOptions = ref<Array<{ label: string; value: number; qty: number }>>([])
|
||||||
|
const targetBinOptions = ref<Array<{ label: string; value: number }>>([])
|
||||||
const relocateForm = ref({
|
const relocateForm = ref({
|
||||||
sourceBinLocationId: undefined as number | undefined,
|
sourceBinLocationId: undefined as number | undefined,
|
||||||
targetBinLocationId: undefined as number | undefined,
|
targetBinLocationId: undefined as number | undefined,
|
||||||
@ -56,6 +67,13 @@ const modalWarehouseName = computed(() => {
|
|||||||
return resolveWarehouseLabel(row.warehouseId, warehouseNameMap.value)
|
return resolveWarehouseLabel(row.warehouseId, warehouseNameMap.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const sourceMaxQty = computed(() => {
|
||||||
|
const sourceId = relocateForm.value.sourceBinLocationId
|
||||||
|
if (!sourceId) return activeSku.value?.totalQty || 1
|
||||||
|
const hit = sourceBinOptions.value.find((b) => b.value === sourceId)
|
||||||
|
return hit?.qty || 1
|
||||||
|
})
|
||||||
|
|
||||||
const hotColumns = computed(() => [
|
const hotColumns = computed(() => [
|
||||||
{ key: 'skuCode', label: t('warehouse.inventory.table.skuCode'), width: 140 },
|
{ key: 'skuCode', label: t('warehouse.inventory.table.skuCode'), width: 140 },
|
||||||
{ key: 'skuName', label: t('warehouse.inventory.table.skuName'), width: 200 },
|
{ key: 'skuName', label: t('warehouse.inventory.table.skuName'), width: 200 },
|
||||||
@ -91,31 +109,59 @@ async function loadWarehouses() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadBinOptions(whId?: number) {
|
async function loadTargetBinOptions(whId?: number) {
|
||||||
if (!whId) {
|
if (!whId) {
|
||||||
binOptions.value = []
|
targetBinOptions.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { items } = await getWarehouseBinLocationList({ warehouseId: whId, isEnabled: true })
|
const { items } = await getWarehouseBinLocationList({ warehouseId: whId, isEnabled: true })
|
||||||
binOptions.value = (items || []).map((b: any) => ({
|
targetBinOptions.value = (items || []).map((b: any) => ({
|
||||||
label: `${b.binCode || b.BinCode}${b.binName || b.BinName ? ` - ${b.binName || b.BinName}` : ''}`,
|
label: `${b.binCode || b.BinCode}${b.binName || b.BinName ? ` - ${b.binName || b.BinName}` : ''}`,
|
||||||
value: Number(b.id || b.Id),
|
value: Number(b.id || b.Id),
|
||||||
}))
|
}))
|
||||||
} catch {
|
} catch {
|
||||||
binOptions.value = []
|
targetBinOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSourceBinOptions(skuId?: number, whId?: number) {
|
||||||
|
if (!skuId || !whId) {
|
||||||
|
sourceBinOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = await getWarehouseInventoryBinDetail(
|
||||||
|
skuId,
|
||||||
|
whId,
|
||||||
|
isAdmin.value ? resolveSelectedCustomerId() : undefined,
|
||||||
|
)
|
||||||
|
const items = data?.items ?? data?.Items ?? (Array.isArray(data) ? data : [])
|
||||||
|
sourceBinOptions.value = (items || []).map((item: Record<string, unknown>) => {
|
||||||
|
const normalized = normalizeListRow(item)
|
||||||
|
const qty = Number(pickField(normalized, 'qty', 'Qty') ?? 0)
|
||||||
|
const code = pickField(normalized, 'binCode', 'BinCode') ?? '-'
|
||||||
|
const name = pickField(normalized, 'binName', 'BinName')
|
||||||
|
return {
|
||||||
|
label: `${code}${name ? ` - ${name}` : ''}(${qty})`,
|
||||||
|
value: Number(pickField(normalized, 'binLocationId', 'BinLocationId') ?? 0),
|
||||||
|
qty,
|
||||||
|
}
|
||||||
|
}).filter((b: { value: number; qty: number }) => b.value > 0 && b.qty > 0)
|
||||||
|
} catch {
|
||||||
|
sourceBinOptions.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
listLoading.value = true
|
listLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { items, total } = await getWarehouseInventoryList({
|
const { items, total } = await getWarehouseInventoryList(withCustomerId({
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
keyword: keyword.value || undefined,
|
keyword: keyword.value || undefined,
|
||||||
warehouseId: warehouseId.value,
|
warehouseId: warehouseId.value,
|
||||||
})
|
}))
|
||||||
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
||||||
totalCount.value = Number(total || 0)
|
totalCount.value = Number(total || 0)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -146,11 +192,16 @@ function handleSearch() {
|
|||||||
async function showBinDetail(row: any) {
|
async function showBinDetail(row: any) {
|
||||||
activeSku.value = row
|
activeSku.value = row
|
||||||
try {
|
try {
|
||||||
const data = await getWarehouseInventoryBinDetail(row.skuId, row.warehouseId)
|
const data = await getWarehouseInventoryBinDetail(
|
||||||
|
row.skuId,
|
||||||
|
row.warehouseId,
|
||||||
|
isAdmin.value ? resolveSelectedCustomerId() : undefined,
|
||||||
|
)
|
||||||
const items = data?.items ?? data?.Items ?? (Array.isArray(data) ? data : [])
|
const items = data?.items ?? data?.Items ?? (Array.isArray(data) ? data : [])
|
||||||
binDetailRows.value = (items || []).map((item: Record<string, unknown>) => {
|
binDetailRows.value = (items || []).map((item: Record<string, unknown>) => {
|
||||||
const normalized = normalizeListRow(item)
|
const normalized = normalizeListRow(item)
|
||||||
return {
|
return {
|
||||||
|
binLocationId: pickField<number>(normalized, 'binLocationId', 'BinLocationId'),
|
||||||
binCode: pickField(normalized, 'binCode', 'BinCode') ?? '-',
|
binCode: pickField(normalized, 'binCode', 'BinCode') ?? '-',
|
||||||
binName: pickField(normalized, 'binName', 'BinName') ?? '',
|
binName: pickField(normalized, 'binName', 'BinName') ?? '',
|
||||||
qty: pickField<number>(normalized, 'qty', 'Qty') ?? 0,
|
qty: pickField<number>(normalized, 'qty', 'Qty') ?? 0,
|
||||||
@ -164,12 +215,28 @@ async function showBinDetail(row: any) {
|
|||||||
|
|
||||||
async function openRelocateModal(row: any) {
|
async function openRelocateModal(row: any) {
|
||||||
if (!hasPermission('Warehouse.Inventory.Relocate')) return
|
if (!hasPermission('Warehouse.Inventory.Relocate')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
activeSku.value = row
|
activeSku.value = row
|
||||||
relocateForm.value = { sourceBinLocationId: undefined, targetBinLocationId: undefined, qty: 1 }
|
relocateForm.value = { sourceBinLocationId: undefined, targetBinLocationId: undefined, qty: 1 }
|
||||||
await loadBinOptions(row.warehouseId)
|
await Promise.all([
|
||||||
|
loadSourceBinOptions(row.skuId, row.warehouseId),
|
||||||
|
loadTargetBinOptions(row.warehouseId),
|
||||||
|
])
|
||||||
|
if (sourceBinOptions.value.length === 1) {
|
||||||
|
relocateForm.value.sourceBinLocationId = sourceBinOptions.value[0].value
|
||||||
|
relocateForm.value.qty = Math.min(1, sourceBinOptions.value[0].qty) || 1
|
||||||
|
}
|
||||||
openRelocate(true)
|
openRelocate(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSourceBinChange(binId?: number) {
|
||||||
|
relocateForm.value.sourceBinLocationId = binId
|
||||||
|
const hit = sourceBinOptions.value.find((b) => b.value === binId)
|
||||||
|
if (hit && relocateForm.value.qty > hit.qty) {
|
||||||
|
relocateForm.value.qty = hit.qty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitRelocate() {
|
async function submitRelocate() {
|
||||||
const row = activeSku.value
|
const row = activeSku.value
|
||||||
const f = relocateForm.value
|
const f = relocateForm.value
|
||||||
@ -181,15 +248,19 @@ async function submitRelocate() {
|
|||||||
$baseMessage(t('warehouse.inventory.message.sameBin'), 'warning')
|
$baseMessage(t('warehouse.inventory.message.sameBin'), 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (f.qty > sourceMaxQty.value) {
|
||||||
|
$baseMessage(t('warehouse.inventory.message.qtyExceed'), 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
relocateSaving.value = true
|
relocateSaving.value = true
|
||||||
try {
|
try {
|
||||||
await relocateWarehouseInventory({
|
await relocateWarehouseInventory(withCustomerId({
|
||||||
warehouseId: row.warehouseId,
|
warehouseId: row.warehouseId,
|
||||||
skuId: row.skuId,
|
skuId: row.skuId,
|
||||||
sourceBinLocationId: f.sourceBinLocationId,
|
sourceBinLocationId: f.sourceBinLocationId,
|
||||||
targetBinLocationId: f.targetBinLocationId,
|
targetBinLocationId: f.targetBinLocationId,
|
||||||
qty: f.qty,
|
qty: f.qty,
|
||||||
})
|
}))
|
||||||
$baseMessage(t('warehouse.inventory.message.relocateSuccess'), 'success')
|
$baseMessage(t('warehouse.inventory.message.relocateSuccess'), 'success')
|
||||||
closeRelocate()
|
closeRelocate()
|
||||||
await loadList()
|
await loadList()
|
||||||
@ -202,12 +273,12 @@ async function submitRelocate() {
|
|||||||
|
|
||||||
function actionRenderer() {
|
function actionRenderer() {
|
||||||
const parts: string[] = [
|
const parts: string[] = [
|
||||||
`<button type="button" class="hot-action-btn" data-action="detail">${t('warehouse.inventory.actions.binDetail')}</button>`,
|
renderWarehouseActionBtn('detail', t('warehouse.inventory.actions.binDetail'), 'view'),
|
||||||
]
|
]
|
||||||
if (hasPermission('Warehouse.Inventory.Relocate')) {
|
if (hasPermission('Warehouse.Inventory.Relocate')) {
|
||||||
parts.push(`<button type="button" class="hot-action-btn" data-action="relocate">${t('warehouse.inventory.actions.relocate')}</button>`)
|
parts.push(renderWarehouseActionBtn('relocate', t('warehouse.inventory.actions.relocate')))
|
||||||
}
|
}
|
||||||
return parts.join('')
|
return wrapWarehouseActions(parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActionClick(row: any, _idx: number, action?: string) {
|
function handleActionClick(row: any, _idx: number, action?: string) {
|
||||||
@ -227,6 +298,16 @@ onMounted(async () => {
|
|||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<a-space wrap :size="8">
|
<a-space wrap :size="8">
|
||||||
|
<a-select
|
||||||
|
v-if="isAdmin"
|
||||||
|
v-model="adminCustomerId"
|
||||||
|
allow-clear
|
||||||
|
allow-search
|
||||||
|
:placeholder="t('warehouse.common.selectCustomer')"
|
||||||
|
:options="customerOptions"
|
||||||
|
style="width: 220px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="warehouseId"
|
v-model="warehouseId"
|
||||||
allow-clear
|
allow-clear
|
||||||
@ -263,6 +344,7 @@ onMounted(async () => {
|
|||||||
:loading="listLoading"
|
:loading="listLoading"
|
||||||
:stretch-columns="true"
|
:stretch-columns="true"
|
||||||
show-action-column
|
show-action-column
|
||||||
|
:action-column-width="90"
|
||||||
:action-renderer="actionRenderer"
|
:action-renderer="actionRenderer"
|
||||||
@action-click="handleActionClick"
|
@action-click="handleActionClick"
|
||||||
/>
|
/>
|
||||||
@ -301,21 +383,27 @@ onMounted(async () => {
|
|||||||
<a-form-item :label="t('warehouse.inventory.form.sourceBin')" required>
|
<a-form-item :label="t('warehouse.inventory.form.sourceBin')" required>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="relocateForm.sourceBinLocationId"
|
v-model="relocateForm.sourceBinLocationId"
|
||||||
:options="binOptions"
|
:options="sourceBinOptions"
|
||||||
allow-search
|
allow-search
|
||||||
:placeholder="t('warehouse.inventory.form.selectBin')"
|
:placeholder="t('warehouse.inventory.form.selectSourceBin')"
|
||||||
|
@change="onSourceBinChange"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('warehouse.inventory.form.targetBin')" required>
|
<a-form-item :label="t('warehouse.inventory.form.targetBin')" required>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="relocateForm.targetBinLocationId"
|
v-model="relocateForm.targetBinLocationId"
|
||||||
:options="binOptions"
|
:options="targetBinOptions"
|
||||||
allow-search
|
allow-search
|
||||||
:placeholder="t('warehouse.inventory.form.selectBin')"
|
:placeholder="t('warehouse.inventory.form.selectTargetBin')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('warehouse.inventory.form.qty')" required>
|
<a-form-item :label="t('warehouse.inventory.form.qty')" required>
|
||||||
<a-input-number v-model="relocateForm.qty" :min="1" style="width: 100%" />
|
<a-input-number
|
||||||
|
v-model="relocateForm.qty"
|
||||||
|
:min="1"
|
||||||
|
:max="sourceMaxQty"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import {
|
|||||||
resolveWarehouseLabel,
|
resolveWarehouseLabel,
|
||||||
summarizeParcelItems,
|
summarizeParcelItems,
|
||||||
} from '@/utils/warehouseListMapper'
|
} from '@/utils/warehouseListMapper'
|
||||||
|
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
|
||||||
|
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer'
|
||||||
|
|
||||||
defineOptions({ name: 'WarehouseParcel' })
|
defineOptions({ name: 'WarehouseParcel' })
|
||||||
|
|
||||||
@ -30,6 +32,13 @@ interface ParcelItemRow {
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||||
|
const {
|
||||||
|
isAdmin,
|
||||||
|
customerId: adminCustomerId,
|
||||||
|
customerOptions,
|
||||||
|
withCustomerId,
|
||||||
|
ensureMutationCustomerId,
|
||||||
|
} = useWarehouseAdminCustomer()
|
||||||
|
|
||||||
const lifecycleStatus = ref<string>()
|
const lifecycleStatus = ref<string>()
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
@ -128,13 +137,13 @@ async function loadWarehouses() {
|
|||||||
async function loadList() {
|
async function loadList() {
|
||||||
listLoading.value = true
|
listLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { items, total } = await getWarehouseParcelList({
|
const { items, total } = await getWarehouseParcelList(withCustomerId({
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
lifecycleStatus: lifecycleStatus.value,
|
lifecycleStatus: lifecycleStatus.value,
|
||||||
keyword: keyword.value || undefined,
|
keyword: keyword.value || undefined,
|
||||||
warehouseId: warehouseId.value,
|
warehouseId: warehouseId.value,
|
||||||
})
|
}))
|
||||||
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
||||||
totalCount.value = Number(total || 0)
|
totalCount.value = Number(total || 0)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -164,6 +173,7 @@ function handleSearch() {
|
|||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
if (!hasPermission('Warehouse.Parcel.Save')) return
|
if (!hasPermission('Warehouse.Parcel.Save')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
formModel.id = 0
|
formModel.id = 0
|
||||||
formModel.warehouseId = warehouseId.value
|
formModel.warehouseId = warehouseId.value
|
||||||
formModel.trackingNo = ''
|
formModel.trackingNo = ''
|
||||||
@ -174,11 +184,38 @@ function handleAdd() {
|
|||||||
openModal(true)
|
openModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleEdit(row: any) {
|
||||||
|
if (!hasPermission('Warehouse.Parcel.Save') || row._status === 'stored') return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
|
const raw = row._raw || row
|
||||||
|
formModel.id = Number(pickField(raw, 'id', 'Id') ?? 0)
|
||||||
|
formModel.warehouseId = pickField<number>(raw, 'warehouseId', 'WarehouseId')
|
||||||
|
formModel.trackingNo = String(pickField(raw, 'trackingNo', 'TrackingNo') ?? '')
|
||||||
|
formModel.carrierName = String(pickField(raw, 'carrierName', 'CarrierName') ?? '')
|
||||||
|
formModel.lifecycleStatus = String(pickField(raw, 'lifecycleStatus', 'LifecycleStatus') ?? 'inTransit')
|
||||||
|
formModel.remark = String(pickField(raw, 'remark', 'Remark') ?? '')
|
||||||
|
const items = pickField(raw, 'items', 'Items')
|
||||||
|
if (Array.isArray(items) && items.length) {
|
||||||
|
itemRows.value = items.map((item: any) => ({
|
||||||
|
productName: String(pickField(item, 'productName', 'ProductName') ?? ''),
|
||||||
|
quantity: Number(pickField(item, 'quantity', 'Quantity') ?? 1) || 1,
|
||||||
|
remark: String(pickField(item, 'remark', 'Remark') ?? ''),
|
||||||
|
}))
|
||||||
|
if (!itemRows.value.some((r) => !r.productName.trim())) {
|
||||||
|
itemRows.value = [...itemRows.value, { productName: '', quantity: 1, remark: '' }]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resetItems()
|
||||||
|
}
|
||||||
|
openModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
if (!formModel.warehouseId || !formModel.trackingNo.trim()) {
|
if (!formModel.warehouseId || !formModel.trackingNo.trim()) {
|
||||||
$baseMessage(t('warehouse.parcel.message.required'), 'warning')
|
$baseMessage(t('warehouse.parcel.message.required'), 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
const items = itemRows.value
|
const items = itemRows.value
|
||||||
.filter((row) => row.productName.trim())
|
.filter((row) => row.productName.trim())
|
||||||
.map((row) => ({
|
.map((row) => ({
|
||||||
@ -192,7 +229,7 @@ async function handleSave() {
|
|||||||
}
|
}
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await saveWarehouseParcel({ ...formModel, items })
|
await saveWarehouseParcel(withCustomerId({ ...formModel, items }))
|
||||||
$baseMessage(t('warehouse.parcel.message.saveSuccess'), 'success')
|
$baseMessage(t('warehouse.parcel.message.saveSuccess'), 'success')
|
||||||
closeModal()
|
closeModal()
|
||||||
await loadList()
|
await loadList()
|
||||||
@ -205,6 +242,7 @@ async function handleSave() {
|
|||||||
|
|
||||||
function handleDelete(row: any) {
|
function handleDelete(row: any) {
|
||||||
if (!hasPermission('Warehouse.Parcel.Delete')) return
|
if (!hasPermission('Warehouse.Parcel.Delete')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: t('warehouse.parcel.actions.delete'),
|
title: t('warehouse.parcel.actions.delete'),
|
||||||
content: row.trackingNo,
|
content: row.trackingNo,
|
||||||
@ -219,15 +257,16 @@ function handleDelete(row: any) {
|
|||||||
function actionRenderer(row: any) {
|
function actionRenderer(row: any) {
|
||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
if (hasPermission('Warehouse.Parcel.Save') && row._status !== 'stored') {
|
if (hasPermission('Warehouse.Parcel.Save') && row._status !== 'stored') {
|
||||||
parts.push(`<button type="button" class="hot-action-btn" data-action="edit">${t('warehouse.common.edit')}</button>`)
|
parts.push(renderWarehouseActionBtn('edit', t('warehouse.common.edit')))
|
||||||
}
|
}
|
||||||
if (hasPermission('Warehouse.Parcel.Delete') && row._status !== 'stored') {
|
if (hasPermission('Warehouse.Parcel.Delete') && row._status !== 'stored') {
|
||||||
parts.push(`<button type="button" class="hot-action-btn" data-action="delete">${t('warehouse.common.delete')}</button>`)
|
parts.push(renderWarehouseActionBtn('delete', t('warehouse.common.delete')))
|
||||||
}
|
}
|
||||||
return parts.join('')
|
return wrapWarehouseActions(parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActionClick(row: any, _i: number, action?: string) {
|
function handleActionClick(row: any, _i: number, action?: string) {
|
||||||
|
if (action === 'edit') handleEdit(row)
|
||||||
if (action === 'delete') handleDelete(row)
|
if (action === 'delete') handleDelete(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +289,16 @@ onMounted(async () => {
|
|||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<a-space wrap :size="8">
|
<a-space wrap :size="8">
|
||||||
|
<a-select
|
||||||
|
v-if="isAdmin"
|
||||||
|
v-model="adminCustomerId"
|
||||||
|
allow-clear
|
||||||
|
allow-search
|
||||||
|
:placeholder="t('warehouse.common.selectCustomer')"
|
||||||
|
:options="customerOptions"
|
||||||
|
style="width: 220px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="warehouseId"
|
v-model="warehouseId"
|
||||||
allow-clear
|
allow-clear
|
||||||
@ -289,6 +338,7 @@ onMounted(async () => {
|
|||||||
:loading="listLoading"
|
:loading="listLoading"
|
||||||
:stretch-columns="true"
|
:stretch-columns="true"
|
||||||
show-action-column
|
show-action-column
|
||||||
|
:action-column-width="90"
|
||||||
:action-renderer="actionRenderer"
|
:action-renderer="actionRenderer"
|
||||||
@action-click="handleActionClick"
|
@action-click="handleActionClick"
|
||||||
/>
|
/>
|
||||||
@ -296,7 +346,7 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ModalPro
|
<ModalPro
|
||||||
:title="t('warehouse.parcel.modal.add')"
|
:title="formModel.id ? t('warehouse.common.edit') : t('warehouse.parcel.modal.add')"
|
||||||
:width="720"
|
:width="720"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
:body-style="{ maxHeight: '70vh', overflow: 'auto' }"
|
:body-style="{ maxHeight: '70vh', overflow: 'auto' }"
|
||||||
|
|||||||
@ -17,11 +17,20 @@ import {
|
|||||||
normalizeListRow,
|
normalizeListRow,
|
||||||
pickField,
|
pickField,
|
||||||
} from '@/utils/warehouseListMapper'
|
} from '@/utils/warehouseListMapper'
|
||||||
|
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
|
||||||
|
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer'
|
||||||
|
|
||||||
defineOptions({ name: 'WarehouseSku' })
|
defineOptions({ name: 'WarehouseSku' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||||
|
const {
|
||||||
|
isAdmin,
|
||||||
|
customerId,
|
||||||
|
customerOptions,
|
||||||
|
withCustomerId,
|
||||||
|
ensureMutationCustomerId,
|
||||||
|
} = useWarehouseAdminCustomer()
|
||||||
|
|
||||||
const listLoading = ref(false)
|
const listLoading = ref(false)
|
||||||
const tableRows = ref<any[]>([])
|
const tableRows = ref<any[]>([])
|
||||||
@ -112,9 +121,6 @@ async function loadWarehouses() {
|
|||||||
label: w.warehouseName || w.WarehouseName || `#${w.id}`,
|
label: w.warehouseName || w.WarehouseName || `#${w.id}`,
|
||||||
value: Number(w.id),
|
value: Number(w.id),
|
||||||
}))
|
}))
|
||||||
if (!selectedWarehouseId.value && warehouseOptions.value.length) {
|
|
||||||
selectedWarehouseId.value = warehouseOptions.value[0].value
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
warehouseOptions.value = []
|
warehouseOptions.value = []
|
||||||
}
|
}
|
||||||
@ -123,11 +129,11 @@ async function loadWarehouses() {
|
|||||||
async function loadList() {
|
async function loadList() {
|
||||||
listLoading.value = true
|
listLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { items, total } = await getWarehouseSkuList({
|
const { items, total } = await getWarehouseSkuList(withCustomerId({
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
keyword: keyword.value || undefined,
|
keyword: keyword.value || undefined,
|
||||||
})
|
}))
|
||||||
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
tableRows.value = (items || []).map((item: Record<string, unknown>) => mapRow(item))
|
||||||
totalCount.value = Number(total || 0)
|
totalCount.value = Number(total || 0)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -157,12 +163,14 @@ function handleSearch() {
|
|||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
if (!hasPermission('Warehouse.Sku.Save')) return
|
if (!hasPermission('Warehouse.Sku.Save')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
resetForm()
|
resetForm()
|
||||||
openModal(true)
|
openModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEdit(row: any) {
|
function handleEdit(row: any) {
|
||||||
if (!hasPermission('Warehouse.Sku.Save')) return
|
if (!hasPermission('Warehouse.Sku.Save')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
const raw = row._raw || row
|
const raw = row._raw || row
|
||||||
formModel.id = Number(pickField(raw, 'id', 'Id') ?? 0)
|
formModel.id = Number(pickField(raw, 'id', 'Id') ?? 0)
|
||||||
formModel.skuCode = String(pickField(raw, 'skuCode', 'SkuCode') ?? '')
|
formModel.skuCode = String(pickField(raw, 'skuCode', 'SkuCode') ?? '')
|
||||||
@ -181,9 +189,11 @@ async function handleSave() {
|
|||||||
$baseMessage(t('warehouse.sku.message.required'), 'warning')
|
$baseMessage(t('warehouse.sku.message.required'), 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const mutationCustomerId = isAdmin.value ? ensureMutationCustomerId() : undefined
|
||||||
|
if (isAdmin.value && mutationCustomerId == null) return
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await saveWarehouseSku({ ...formModel })
|
await saveWarehouseSku(withCustomerId({ ...formModel }))
|
||||||
$baseMessage(t('warehouse.sku.message.saveSuccess'), 'success')
|
$baseMessage(t('warehouse.sku.message.saveSuccess'), 'success')
|
||||||
closeModal()
|
closeModal()
|
||||||
await loadList()
|
await loadList()
|
||||||
@ -196,6 +206,7 @@ async function handleSave() {
|
|||||||
|
|
||||||
function handleDelete(row: any) {
|
function handleDelete(row: any) {
|
||||||
if (!hasPermission('Warehouse.Sku.Delete')) return
|
if (!hasPermission('Warehouse.Sku.Delete')) return
|
||||||
|
if (isAdmin.value && ensureMutationCustomerId() == null) return
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: t('warehouse.sku.actions.delete'),
|
title: t('warehouse.sku.actions.delete'),
|
||||||
content: t('warehouse.sku.message.deleteConfirm', { code: row.skuCode }),
|
content: t('warehouse.sku.message.deleteConfirm', { code: row.skuCode }),
|
||||||
@ -214,12 +225,12 @@ function handleDelete(row: any) {
|
|||||||
function actionRenderer() {
|
function actionRenderer() {
|
||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
if (hasPermission('Warehouse.Sku.Save')) {
|
if (hasPermission('Warehouse.Sku.Save')) {
|
||||||
parts.push(`<button type="button" class="hot-action-btn" data-action="edit">${t('warehouse.common.edit')}</button>`)
|
parts.push(renderWarehouseActionBtn('edit', t('warehouse.common.edit')))
|
||||||
}
|
}
|
||||||
if (hasPermission('Warehouse.Sku.Delete')) {
|
if (hasPermission('Warehouse.Sku.Delete')) {
|
||||||
parts.push(`<button type="button" class="hot-action-btn" data-action="delete">${t('warehouse.common.delete')}</button>`)
|
parts.push(renderWarehouseActionBtn('delete', t('warehouse.common.delete')))
|
||||||
}
|
}
|
||||||
return parts.join('')
|
return wrapWarehouseActions(parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActionClick(row: any, _idx: number, action?: string) {
|
function handleActionClick(row: any, _idx: number, action?: string) {
|
||||||
@ -239,6 +250,16 @@ onMounted(async () => {
|
|||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<a-space wrap :size="8">
|
<a-space wrap :size="8">
|
||||||
|
<a-select
|
||||||
|
v-if="isAdmin"
|
||||||
|
v-model="customerId"
|
||||||
|
allow-clear
|
||||||
|
allow-search
|
||||||
|
:placeholder="t('warehouse.common.selectCustomer')"
|
||||||
|
:options="customerOptions"
|
||||||
|
style="width: 220px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="selectedWarehouseId"
|
v-model="selectedWarehouseId"
|
||||||
allow-clear
|
allow-clear
|
||||||
@ -279,6 +300,7 @@ onMounted(async () => {
|
|||||||
:loading="listLoading"
|
:loading="listLoading"
|
||||||
:stretch-columns="true"
|
:stretch-columns="true"
|
||||||
show-action-column
|
show-action-column
|
||||||
|
:action-column-width="90"
|
||||||
:action-renderer="actionRenderer"
|
:action-renderer="actionRenderer"
|
||||||
@action-click="handleActionClick"
|
@action-click="handleActionClick"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -68,7 +68,12 @@ export default defineConfig(({ mode }) => {
|
|||||||
secure: false,
|
secure: false,
|
||||||
bypass(req) {
|
bypass(req) {
|
||||||
const path = req.url?.split('?')[0] ?? '';
|
const path = req.url?.split('?')[0] ?? '';
|
||||||
const warehouseSpaExact = ['/warehouse/parcel', '/warehouse/inventory', '/warehouse/sku'];
|
const warehouseSpaExact = [
|
||||||
|
'/warehouse/parcel',
|
||||||
|
'/warehouse/inventory',
|
||||||
|
'/warehouse/sku',
|
||||||
|
'/warehouse/binLocation',
|
||||||
|
];
|
||||||
if (warehouseSpaExact.some((p) => path === p || path === `${p}/`)) {
|
if (warehouseSpaExact.some((p) => path === p || path === `${p}/`)) {
|
||||||
return '/index.html';
|
return '/index.html';
|
||||||
}
|
}
|
||||||
@ -304,6 +309,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
'/warehouse/inventory',
|
'/warehouse/inventory',
|
||||||
'/warehouse/sku',
|
'/warehouse/sku',
|
||||||
'/warehouse/inboundNotice',
|
'/warehouse/inboundNotice',
|
||||||
|
'/warehouse/binLocation',
|
||||||
// Xhs 相关前端路由(勿用 /xhs 前缀,否则 API 会被误判为 SPA)
|
// Xhs 相关前端路由(勿用 /xhs 前缀,否则 API 会被误判为 SPA)
|
||||||
'/xhs/startService',
|
'/xhs/startService',
|
||||||
'/xhs/analysis',
|
'/xhs/analysis',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user