From 82b22173c08a3b6488898971f0f7ad8b167bd950 Mon Sep 17 00:00:00 2001 From: XuZhiqiang Date: Thu, 4 Jun 2026 15:57:43 +0800 Subject: [PATCH] feat(web-antdv-next): migrate WMS module --- apps/web-antdv-next/src/api/wms/home/index.ts | 66 ++ .../src/api/wms/inventory/history/index.ts | 37 + .../src/api/wms/inventory/index.ts | 42 + .../src/api/wms/md/item/brand/index.ts | 55 ++ .../src/api/wms/md/item/category/index.ts | 52 ++ .../src/api/wms/md/item/index.ts | 61 ++ .../src/api/wms/md/item/sku/index.ts | 37 + .../src/api/wms/md/merchant/index.ts | 73 ++ .../src/api/wms/md/warehouse/index.ts | 57 ++ .../src/api/wms/order/check/detail/index.ts | 23 + .../src/api/wms/order/check/index.ts | 71 ++ .../api/wms/order/movement/detail/index.ts | 23 + .../src/api/wms/order/movement/index.ts | 72 ++ .../src/api/wms/order/receipt/detail/index.ts | 20 + .../src/api/wms/order/receipt/index.ts | 74 ++ .../api/wms/order/shipment/detail/index.ts | 21 + .../src/api/wms/order/shipment/index.ts | 74 ++ .../components/number-range-input/index.ts | 39 + .../number-range-input/number-range-input.vue | 73 ++ .../src/views/wms/home/index.vue | 80 ++ .../wms-home-inventory-chart-options.ts | 116 +++ .../modules/wms-home-inventory-charts.vue | 116 +++ .../modules/wms-home-order-summary-cards.vue | 193 +++++ .../wms-home-order-trend-chart-options.ts | 96 +++ .../modules/wms-home-order-trend-chart.vue | 84 ++ .../views/wms/inventory/components/index.ts | 2 + .../views/wms/inventory/components/select.vue | 210 +++++ .../src/views/wms/inventory/history/data.ts | 159 ++++ .../src/views/wms/inventory/history/index.vue | 104 +++ .../src/views/wms/inventory/index/data.ts | 249 ++++++ .../src/views/wms/inventory/index/index.vue | 190 +++++ .../wms/md/item/brand/components/index.ts | 1 + .../wms/md/item/brand/components/select.vue | 85 ++ .../src/views/wms/md/item/brand/data.ts | 108 +++ .../src/views/wms/md/item/brand/index.vue | 145 ++++ .../views/wms/md/item/brand/modules/form.vue | 86 ++ .../wms/md/item/category/components/index.ts | 2 + .../md/item/category/components/select.vue | 68 ++ .../wms/md/item/category/components/tree.vue | 123 +++ .../src/views/wms/md/item/category/data.ts | 186 +++++ .../src/views/wms/md/item/category/index.vue | 162 ++++ .../wms/md/item/category/modules/form.vue | 98 +++ .../src/views/wms/md/item/data.ts | 261 ++++++ .../src/views/wms/md/item/index.vue | 280 +++++++ .../src/views/wms/md/item/modules/form.vue | 102 +++ .../views/wms/md/item/modules/sku-form.vue | 300 +++++++ .../views/wms/md/item/sku/components/index.ts | 1 + .../wms/md/item/sku/components/select.vue | 224 ++++++ .../views/wms/md/merchant/components/index.ts | 1 + .../wms/md/merchant/components/select.vue | 109 +++ .../src/views/wms/md/merchant/data.ts | 231 ++++++ .../src/views/wms/md/merchant/index.vue | 152 ++++ .../views/wms/md/merchant/modules/form.vue | 89 +++ .../wms/md/warehouse/components/index.ts | 1 + .../wms/md/warehouse/components/select.vue | 85 ++ .../src/views/wms/md/warehouse/data.ts | 139 ++++ .../src/views/wms/md/warehouse/index.vue | 152 ++++ .../views/wms/md/warehouse/modules/form.vue | 92 +++ .../src/views/wms/order/check/data.ts | 488 ++++++++++++ .../src/views/wms/order/check/index.vue | 380 +++++++++ .../views/wms/order/check/modules/detail.vue | 170 ++++ .../views/wms/order/check/modules/form.vue | 741 ++++++++++++++++++ .../views/wms/order/check/modules/print.vue | 330 ++++++++ .../src/views/wms/order/movement/data.ts | 372 +++++++++ .../src/views/wms/order/movement/index.vue | 349 +++++++++ .../wms/order/movement/modules/detail.vue | 126 +++ .../views/wms/order/movement/modules/form.vue | 546 +++++++++++++ .../wms/order/movement/modules/print.vue | 249 ++++++ .../src/views/wms/order/receipt/data.ts | 430 ++++++++++ .../src/views/wms/order/receipt/index.vue | 352 +++++++++ .../wms/order/receipt/modules/detail.vue | 122 +++ .../views/wms/order/receipt/modules/form.vue | 476 +++++++++++ .../views/wms/order/receipt/modules/print.vue | 255 ++++++ .../src/views/wms/order/shipment/data.ts | 440 +++++++++++ .../src/views/wms/order/shipment/index.vue | 352 +++++++++ .../wms/order/shipment/modules/detail.vue | 122 +++ .../views/wms/order/shipment/modules/form.vue | 537 +++++++++++++ .../wms/order/shipment/modules/print.vue | 255 ++++++ .../src/views/wms/utils/format.ts | 161 ++++ .../src/views/wms/utils/order.ts | 12 + 80 files changed, 13117 insertions(+) create mode 100644 apps/web-antdv-next/src/api/wms/home/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/inventory/history/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/inventory/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/md/item/brand/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/md/item/category/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/md/item/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/md/item/sku/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/md/merchant/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/md/warehouse/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/check/detail/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/check/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/movement/detail/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/movement/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/receipt/detail/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/receipt/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/shipment/detail/index.ts create mode 100644 apps/web-antdv-next/src/api/wms/order/shipment/index.ts create mode 100644 apps/web-antdv-next/src/components/number-range-input/index.ts create mode 100644 apps/web-antdv-next/src/components/number-range-input/number-range-input.vue create mode 100644 apps/web-antdv-next/src/views/wms/home/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-chart-options.ts create mode 100644 apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-charts.vue create mode 100644 apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-summary-cards.vue create mode 100644 apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart-options.ts create mode 100644 apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart.vue create mode 100644 apps/web-antdv-next/src/views/wms/inventory/components/index.ts create mode 100644 apps/web-antdv-next/src/views/wms/inventory/components/select.vue create mode 100644 apps/web-antdv-next/src/views/wms/inventory/history/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/inventory/history/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/inventory/index/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/inventory/index/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/brand/components/index.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/item/brand/components/select.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/brand/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/item/brand/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/brand/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/category/components/index.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/item/category/components/select.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/category/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/item/category/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/category/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/item/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/modules/sku-form.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/item/sku/components/index.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/item/sku/components/select.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/merchant/components/index.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/merchant/components/select.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/merchant/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/merchant/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/merchant/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/warehouse/components/index.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/warehouse/components/select.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/warehouse/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/md/warehouse/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/md/warehouse/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/check/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/order/check/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/check/modules/detail.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/check/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/check/modules/print.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/movement/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/order/movement/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/movement/modules/detail.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/movement/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/movement/modules/print.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/receipt/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/order/receipt/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/receipt/modules/detail.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/receipt/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/receipt/modules/print.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/shipment/data.ts create mode 100644 apps/web-antdv-next/src/views/wms/order/shipment/index.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/shipment/modules/detail.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/shipment/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/wms/order/shipment/modules/print.vue create mode 100644 apps/web-antdv-next/src/views/wms/utils/format.ts create mode 100644 apps/web-antdv-next/src/views/wms/utils/order.ts diff --git a/apps/web-antdv-next/src/api/wms/home/index.ts b/apps/web-antdv-next/src/api/wms/home/index.ts new file mode 100644 index 000000000..ae9577201 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/home/index.ts @@ -0,0 +1,66 @@ +import { requestClient } from '#/api/request'; + +export namespace WmsHomeStatisticsApi { + export interface StatisticsReq { + goodsLimit?: number; + warehouseId?: number; + warehouseLimit?: number; + } + + export interface OrderStatus { + count: number; + status: number; + } + + export interface OrderSummary { + statuses: OrderStatus[]; + total: number; + type: number; + } + + export interface OrderTrend { + checkCount: number; + movementCount: number; + receiptCount: number; + shipmentCount: number; + time: number | string; + } + + export interface InventoryRankItem { + id: number; + name: string; + quantity: number; + } + + export interface InventorySummary { + goodsShareList: InventoryRankItem[]; + totalQuantity: number; + warehouseDistributionList: InventoryRankItem[]; + } +} + +export function getOrderSummary(params?: WmsHomeStatisticsApi.StatisticsReq) { + return requestClient.get( + '/wms/home-statistics/order-summary', + { params }, + ); +} + +export function getOrderTrend( + days?: number, + params?: WmsHomeStatisticsApi.StatisticsReq, +) { + return requestClient.get( + '/wms/home-statistics/order-trend', + { params: { ...params, days } }, + ); +} + +export function getInventorySummary( + params?: WmsHomeStatisticsApi.StatisticsReq, +) { + return requestClient.get( + '/wms/home-statistics/inventory-summary', + { params }, + ); +} diff --git a/apps/web-antdv-next/src/api/wms/inventory/history/index.ts b/apps/web-antdv-next/src/api/wms/inventory/history/index.ts new file mode 100644 index 000000000..02f6bccd1 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/inventory/history/index.ts @@ -0,0 +1,37 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsInventoryHistoryApi { + /** WMS 库存记录 */ + export interface InventoryHistory { + id?: number; + itemId?: number; + itemCode?: string; + itemName?: string; + unit?: string; + skuId?: number; + skuCode?: string; + skuName?: string; + warehouseId?: number; + warehouseName?: string; + quantity?: number; + beforeQuantity?: number; + afterQuantity?: number; + price?: number; + totalPrice?: number; + remark?: string; + orderId?: number; + orderNo?: string; + orderType?: number; + createTime?: Date; + } +} + +/** 查询库存记录分页 */ +export function getInventoryHistoryPage(params: PageParam) { + return requestClient.get>( + '/wms/inventory-history/page', + { params }, + ); +} diff --git a/apps/web-antdv-next/src/api/wms/inventory/index.ts b/apps/web-antdv-next/src/api/wms/inventory/index.ts new file mode 100644 index 000000000..90ca4a11d --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/inventory/index.ts @@ -0,0 +1,42 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsInventoryApi { + /** WMS 库存统计 */ + export interface Inventory { + id?: number; + itemId?: number; + itemCode?: string; + itemName?: string; + unit?: string; + skuId?: number; + skuCode?: string; + skuName?: string; + warehouseId?: number; + warehouseName?: string; + quantity?: number; + remark?: string; + createTime?: Date; + } + + /** WMS 库存统计列表请求 */ + export interface InventoryListReq { + warehouseId: number; + } +} + +/** 查询库存统计分页 */ +export function getInventoryPage(params: PageParam) { + return requestClient.get>( + '/wms/inventory/page', + { params }, + ); +} + +/** 查询库存统计列表 */ +export function getInventoryList(params: WmsInventoryApi.InventoryListReq) { + return requestClient.get('/wms/inventory/list', { + params, + }); +} diff --git a/apps/web-antdv-next/src/api/wms/md/item/brand/index.ts b/apps/web-antdv-next/src/api/wms/md/item/brand/index.ts new file mode 100644 index 000000000..a251b05ca --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/md/item/brand/index.ts @@ -0,0 +1,55 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsItemBrandApi { + /** WMS 商品品牌 */ + export interface ItemBrand { + id?: number; + code?: string; + name?: string; + createTime?: Date; + } +} + +/** 查询商品品牌分页 */ +export function getItemBrandPage(params: PageParam) { + return requestClient.get>( + '/wms/item-brand/page', + { params }, + ); +} + +/** 查询商品品牌精简列表 */ +export function getItemBrandSimpleList() { + return requestClient.get( + '/wms/item-brand/simple-list', + ); +} + +/** 查询商品品牌详情 */ +export function getItemBrand(id: number) { + return requestClient.get( + `/wms/item-brand/get?id=${id}`, + ); +} + +/** 新增商品品牌 */ +export function createItemBrand(data: WmsItemBrandApi.ItemBrand) { + return requestClient.post('/wms/item-brand/create', data); +} + +/** 修改商品品牌 */ +export function updateItemBrand(data: WmsItemBrandApi.ItemBrand) { + return requestClient.put('/wms/item-brand/update', data); +} + +/** 删除商品品牌 */ +export function deleteItemBrand(id: number) { + return requestClient.delete(`/wms/item-brand/delete?id=${id}`); +} + +/** 导出商品品牌 */ +export function exportItemBrand(params: any) { + return requestClient.download('/wms/item-brand/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/md/item/category/index.ts b/apps/web-antdv-next/src/api/wms/md/item/category/index.ts new file mode 100644 index 000000000..be356f01e --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/md/item/category/index.ts @@ -0,0 +1,52 @@ +import { requestClient } from '#/api/request'; + +export namespace WmsItemCategoryApi { + /** WMS 商品分类 */ + export interface ItemCategory { + id?: number; + parentId?: number; + code?: string; + name?: string; + sort?: number; + status?: number; + createTime?: Date; + children?: ItemCategory[]; + } +} + +/** 查询商品分类列表 */ +export function getItemCategoryList(params?: any) { + return requestClient.get( + '/wms/item-category/list', + { params }, + ); +} + +/** 查询商品分类精简列表 */ +export function getItemCategorySimpleList() { + return requestClient.get( + '/wms/item-category/simple-list', + ); +} + +/** 查询商品分类详情 */ +export function getItemCategory(id: number) { + return requestClient.get( + `/wms/item-category/get?id=${id}`, + ); +} + +/** 新增商品分类 */ +export function createItemCategory(data: WmsItemCategoryApi.ItemCategory) { + return requestClient.post('/wms/item-category/create', data); +} + +/** 修改商品分类 */ +export function updateItemCategory(data: WmsItemCategoryApi.ItemCategory) { + return requestClient.put('/wms/item-category/update', data); +} + +/** 删除商品分类 */ +export function deleteItemCategory(id: number) { + return requestClient.delete(`/wms/item-category/delete?id=${id}`); +} diff --git a/apps/web-antdv-next/src/api/wms/md/item/index.ts b/apps/web-antdv-next/src/api/wms/md/item/index.ts new file mode 100644 index 000000000..fda44706c --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/md/item/index.ts @@ -0,0 +1,61 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import type { WmsItemSkuApi } from './sku'; + +import { requestClient } from '#/api/request'; + +export namespace WmsItemApi { + /** WMS 商品 */ + export interface Item { + id?: number; + code?: string; + name?: string; + categoryId?: number; + categoryName?: string; + unit?: string; + brandId?: number; + brandName?: string; + remark?: string; + skus?: WmsItemSkuApi.ItemSku[]; + createTime?: Date; + } +} + +/** 查询商品分页 */ +export function getItemPage(params: PageParam) { + return requestClient.get>('/wms/item/page', { + params, + }); +} + +/** 查询商品精简列表 */ +export function getItemSimpleList(params?: any) { + return requestClient.get('/wms/item/simple-list', { + params, + }); +} + +/** 查询商品详情 */ +export function getItem(id: number) { + return requestClient.get(`/wms/item/get?id=${id}`); +} + +/** 新增商品 */ +export function createItem(data: WmsItemApi.Item) { + return requestClient.post('/wms/item/create', data); +} + +/** 修改商品 */ +export function updateItem(data: WmsItemApi.Item) { + return requestClient.put('/wms/item/update', data); +} + +/** 删除商品 */ +export function deleteItem(id: number) { + return requestClient.delete(`/wms/item/delete?id=${id}`); +} + +/** 导出商品 */ +export function exportItem(params: any) { + return requestClient.download('/wms/item/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/md/item/sku/index.ts b/apps/web-antdv-next/src/api/wms/md/item/sku/index.ts new file mode 100644 index 000000000..2296becf1 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/md/item/sku/index.ts @@ -0,0 +1,37 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsItemSkuApi { + /** WMS 商品 SKU */ + export interface ItemSku { + id?: number; + name?: string; + itemId?: number; + itemCode?: string; + itemName?: string; + categoryId?: number; + categoryName?: string; + unit?: string; + brandId?: number; + brandName?: string; + barCode?: string; + code?: string; + length?: number; + width?: number; + height?: number; + grossWeight?: number; + netWeight?: number; + costPrice?: number; + sellingPrice?: number; + createTime?: Date; + } +} + +/** 按 SKU 维度分页(支持商品 / 品牌 / 分类多表联查筛选) */ +export function getItemSkuPage(params: PageParam) { + return requestClient.get>( + '/wms/item-sku/page', + { params }, + ); +} diff --git a/apps/web-antdv-next/src/api/wms/md/merchant/index.ts b/apps/web-antdv-next/src/api/wms/md/merchant/index.ts new file mode 100644 index 000000000..93539f098 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/md/merchant/index.ts @@ -0,0 +1,73 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsMerchantApi { + /** WMS 往来企业 */ + export interface Merchant { + id?: number; + code?: string; + name?: string; + type?: number; + level?: string; + bankName?: string; + bankAccount?: string; + address?: string; + mobile?: string; + telephone?: string; + contact?: string; + email?: string; + remark?: string; + createTime?: Date; + } + + /** WMS 往来企业精简列表请求 */ + export interface MerchantSimpleListReq { + types?: number[]; + } +} + +/** 查询往来企业分页 */ +export function getMerchantPage(params: PageParam) { + return requestClient.get>( + '/wms/merchant/page', + { params }, + ); +} + +/** 查询往来企业精简列表 */ +export function getMerchantSimpleList( + params?: WmsMerchantApi.MerchantSimpleListReq, +) { + return requestClient.get( + '/wms/merchant/simple-list', + { params }, + ); +} + +/** 查询往来企业详情 */ +export function getMerchant(id: number) { + return requestClient.get( + `/wms/merchant/get?id=${id}`, + ); +} + +/** 新增往来企业 */ +export function createMerchant(data: WmsMerchantApi.Merchant) { + return requestClient.post('/wms/merchant/create', data); +} + +/** 修改往来企业 */ +export function updateMerchant(data: WmsMerchantApi.Merchant) { + return requestClient.put('/wms/merchant/update', data); +} + +/** 删除往来企业 */ +export function deleteMerchant(id: number) { + return requestClient.delete(`/wms/merchant/delete?id=${id}`); +} + +/** 导出往来企业 */ +export function exportMerchant(params: any) { + return requestClient.download('/wms/merchant/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/md/warehouse/index.ts b/apps/web-antdv-next/src/api/wms/md/warehouse/index.ts new file mode 100644 index 000000000..2b99f3776 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/md/warehouse/index.ts @@ -0,0 +1,57 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsWarehouseApi { + /** WMS 仓库 */ + export interface Warehouse { + id?: number; + code?: string; + name?: string; + remark?: string; + sort?: number; + createTime?: Date; + } +} + +/** 查询仓库分页 */ +export function getWarehousePage(params: PageParam) { + return requestClient.get>( + '/wms/warehouse/page', + { params }, + ); +} + +/** 查询仓库精简列表 */ +export function getWarehouseSimpleList() { + return requestClient.get( + '/wms/warehouse/simple-list', + ); +} + +/** 查询仓库详情 */ +export function getWarehouse(id: number) { + return requestClient.get( + `/wms/warehouse/get?id=${id}`, + ); +} + +/** 新增仓库 */ +export function createWarehouse(data: WmsWarehouseApi.Warehouse) { + return requestClient.post('/wms/warehouse/create', data); +} + +/** 修改仓库 */ +export function updateWarehouse(data: WmsWarehouseApi.Warehouse) { + return requestClient.put('/wms/warehouse/update', data); +} + +/** 删除仓库 */ +export function deleteWarehouse(id: number) { + return requestClient.delete(`/wms/warehouse/delete?id=${id}`); +} + +/** 导出仓库 */ +export function exportWarehouse(params: any) { + return requestClient.download('/wms/warehouse/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/order/check/detail/index.ts b/apps/web-antdv-next/src/api/wms/order/check/detail/index.ts new file mode 100644 index 000000000..ae34b2154 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/check/detail/index.ts @@ -0,0 +1,23 @@ +export namespace WmsCheckOrderDetailApi { + /** WMS 盘库单明细 */ + export interface CheckOrderDetail { + id?: number; + orderId?: number; + itemId?: number; + itemCode?: string; + itemName?: string; + unit?: string; + skuId?: number; + skuCode?: string; + skuName?: string; + inventoryId?: number; + warehouseId?: number; + warehouseName?: string; + receiptTime?: Date; + quantity?: number; + checkQuantity?: number; + availableQuantity?: number; + price?: number; + createTime?: Date; + } +} diff --git a/apps/web-antdv-next/src/api/wms/order/check/index.ts b/apps/web-antdv-next/src/api/wms/order/check/index.ts new file mode 100644 index 000000000..910c997d3 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/check/index.ts @@ -0,0 +1,71 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import type { WmsCheckOrderDetailApi } from './detail'; + +import { requestClient } from '#/api/request'; + +export namespace WmsCheckOrderApi { + /** WMS 盘库单 */ + export interface CheckOrder { + id?: number; + no?: string; + orderTime?: string; + status?: number; + remark?: string; + warehouseId?: number; + warehouseName?: string; + totalQuantity?: number; + totalPrice?: number; + actualPrice?: number; + details?: WmsCheckOrderDetailApi.CheckOrderDetail[]; + createTime?: Date; + creator?: string; + creatorName?: string; + updateTime?: Date; + updater?: string; + updaterName?: string; + } +} + +export function getCheckOrderPage(params: PageParam) { + return requestClient.get>( + '/wms/check-order/page', + { params }, + ); +} + +export function getCheckOrder(id: number) { + return requestClient.get( + `/wms/check-order/get?id=${id}`, + ); +} + +export function getCheckOrderDetailListByOrderId(orderId: number) { + return requestClient.get( + `/wms/check-order-detail/list-by-order-id?orderId=${orderId}`, + ); +} + +export function createCheckOrder(data: WmsCheckOrderApi.CheckOrder) { + return requestClient.post('/wms/check-order/create', data); +} + +export function updateCheckOrder(data: WmsCheckOrderApi.CheckOrder) { + return requestClient.put('/wms/check-order/update', data); +} + +export function completeCheckOrder(id: number) { + return requestClient.put(`/wms/check-order/complete?id=${id}`); +} + +export function cancelCheckOrder(id: number) { + return requestClient.put(`/wms/check-order/cancel?id=${id}`); +} + +export function deleteCheckOrder(id: number) { + return requestClient.delete(`/wms/check-order/delete?id=${id}`); +} + +export function exportCheckOrder(params: any) { + return requestClient.download('/wms/check-order/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/order/movement/detail/index.ts b/apps/web-antdv-next/src/api/wms/order/movement/detail/index.ts new file mode 100644 index 000000000..25a3aec6a --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/movement/detail/index.ts @@ -0,0 +1,23 @@ +export namespace WmsMovementOrderDetailApi { + /** WMS 移库单明细 */ + export interface MovementOrderDetail { + id?: number; + orderId?: number; + itemId?: number; + itemCode?: string; + itemName?: string; + unit?: string; + skuId?: number; + skuCode?: string; + skuName?: string; + sourceWarehouseId?: number; + sourceWarehouseName?: string; + targetWarehouseId?: number; + targetWarehouseName?: string; + quantity?: number; + availableQuantity?: number; + price?: number; + totalPrice?: number; + createTime?: Date; + } +} diff --git a/apps/web-antdv-next/src/api/wms/order/movement/index.ts b/apps/web-antdv-next/src/api/wms/order/movement/index.ts new file mode 100644 index 000000000..f6b9e3197 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/movement/index.ts @@ -0,0 +1,72 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import type { WmsMovementOrderDetailApi } from './detail'; + +import { requestClient } from '#/api/request'; + +export namespace WmsMovementOrderApi { + /** WMS 移库单 */ + export interface MovementOrder { + id?: number; + no?: string; + orderTime?: string; + status?: number; + remark?: string; + sourceWarehouseId?: number; + sourceWarehouseName?: string; + targetWarehouseId?: number; + targetWarehouseName?: string; + totalQuantity?: number; + totalPrice?: number; + details?: WmsMovementOrderDetailApi.MovementOrderDetail[]; + createTime?: Date; + creator?: string; + creatorName?: string; + updateTime?: Date; + updater?: string; + updaterName?: string; + } +} + +export function getMovementOrderPage(params: PageParam) { + return requestClient.get>( + '/wms/movement-order/page', + { params }, + ); +} + +export function getMovementOrder(id: number) { + return requestClient.get( + `/wms/movement-order/get?id=${id}`, + ); +} + +export function getMovementOrderDetailListByOrderId(orderId: number) { + return requestClient.get( + `/wms/movement-order-detail/list-by-order-id?orderId=${orderId}`, + ); +} + +export function createMovementOrder(data: WmsMovementOrderApi.MovementOrder) { + return requestClient.post('/wms/movement-order/create', data); +} + +export function updateMovementOrder(data: WmsMovementOrderApi.MovementOrder) { + return requestClient.put('/wms/movement-order/update', data); +} + +export function completeMovementOrder(id: number) { + return requestClient.put(`/wms/movement-order/complete?id=${id}`); +} + +export function cancelMovementOrder(id: number) { + return requestClient.put(`/wms/movement-order/cancel?id=${id}`); +} + +export function deleteMovementOrder(id: number) { + return requestClient.delete(`/wms/movement-order/delete?id=${id}`); +} + +export function exportMovementOrder(params: any) { + return requestClient.download('/wms/movement-order/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/order/receipt/detail/index.ts b/apps/web-antdv-next/src/api/wms/order/receipt/detail/index.ts new file mode 100644 index 000000000..0695c224d --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/receipt/detail/index.ts @@ -0,0 +1,20 @@ +export namespace WmsReceiptOrderDetailApi { + /** WMS 入库单明细 */ + export interface ReceiptOrderDetail { + id?: number; + orderId?: number; + itemId?: number; + itemCode?: string; + itemName?: string; + unit?: string; + skuId?: number; + skuCode?: string; + skuName?: string; + warehouseId?: number; + warehouseName?: string; + quantity?: number; + price?: number; + totalPrice?: number; + createTime?: Date; + } +} diff --git a/apps/web-antdv-next/src/api/wms/order/receipt/index.ts b/apps/web-antdv-next/src/api/wms/order/receipt/index.ts new file mode 100644 index 000000000..9c5a08a5e --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/receipt/index.ts @@ -0,0 +1,74 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import type { WmsReceiptOrderDetailApi } from './detail'; + +import { requestClient } from '#/api/request'; + +export namespace WmsReceiptOrderApi { + /** WMS 入库单 */ + export interface ReceiptOrder { + id?: number; + no?: string; + type?: number; + orderTime?: string; + status?: number; + bizOrderNo?: string; + merchantId?: number; + merchantName?: string; + remark?: string; + warehouseId?: number; + warehouseName?: string; + totalQuantity?: number; + totalPrice?: number; + details?: WmsReceiptOrderDetailApi.ReceiptOrderDetail[]; + createTime?: Date; + creator?: string; + creatorName?: string; + updateTime?: Date; + updater?: string; + updaterName?: string; + } +} + +export function getReceiptOrderPage(params: PageParam) { + return requestClient.get>( + '/wms/receipt-order/page', + { params }, + ); +} + +export function getReceiptOrder(id: number) { + return requestClient.get( + `/wms/receipt-order/get?id=${id}`, + ); +} + +export function getReceiptOrderDetailListByOrderId(orderId: number) { + return requestClient.get( + `/wms/receipt-order-detail/list-by-order-id?orderId=${orderId}`, + ); +} + +export function createReceiptOrder(data: WmsReceiptOrderApi.ReceiptOrder) { + return requestClient.post('/wms/receipt-order/create', data); +} + +export function updateReceiptOrder(data: WmsReceiptOrderApi.ReceiptOrder) { + return requestClient.put('/wms/receipt-order/update', data); +} + +export function completeReceiptOrder(id: number) { + return requestClient.put(`/wms/receipt-order/complete?id=${id}`); +} + +export function cancelReceiptOrder(id: number) { + return requestClient.put(`/wms/receipt-order/cancel?id=${id}`); +} + +export function deleteReceiptOrder(id: number) { + return requestClient.delete(`/wms/receipt-order/delete?id=${id}`); +} + +export function exportReceiptOrder(params: any) { + return requestClient.download('/wms/receipt-order/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/api/wms/order/shipment/detail/index.ts b/apps/web-antdv-next/src/api/wms/order/shipment/detail/index.ts new file mode 100644 index 000000000..17e796e18 --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/shipment/detail/index.ts @@ -0,0 +1,21 @@ +export namespace WmsShipmentOrderDetailApi { + /** WMS 出库单明细 */ + export interface ShipmentOrderDetail { + id?: number; + orderId?: number; + itemId?: number; + itemCode?: string; + itemName?: string; + unit?: string; + skuId?: number; + skuCode?: string; + skuName?: string; + warehouseId?: number; + warehouseName?: string; + quantity?: number; + availableQuantity?: number; + price?: number; + totalPrice?: number; + createTime?: Date; + } +} diff --git a/apps/web-antdv-next/src/api/wms/order/shipment/index.ts b/apps/web-antdv-next/src/api/wms/order/shipment/index.ts new file mode 100644 index 000000000..b52dcdaca --- /dev/null +++ b/apps/web-antdv-next/src/api/wms/order/shipment/index.ts @@ -0,0 +1,74 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import type { WmsShipmentOrderDetailApi } from './detail'; + +import { requestClient } from '#/api/request'; + +export namespace WmsShipmentOrderApi { + /** WMS 出库单 */ + export interface ShipmentOrder { + id?: number; + no?: string; + type?: number; + orderTime?: string; + status?: number; + bizOrderNo?: string; + merchantId?: number; + merchantName?: string; + remark?: string; + warehouseId?: number; + warehouseName?: string; + totalQuantity?: number; + totalPrice?: number; + details?: WmsShipmentOrderDetailApi.ShipmentOrderDetail[]; + createTime?: Date; + creator?: string; + creatorName?: string; + updateTime?: Date; + updater?: string; + updaterName?: string; + } +} + +export function getShipmentOrderPage(params: PageParam) { + return requestClient.get>( + '/wms/shipment-order/page', + { params }, + ); +} + +export function getShipmentOrder(id: number) { + return requestClient.get( + `/wms/shipment-order/get?id=${id}`, + ); +} + +export function getShipmentOrderDetailListByOrderId(orderId: number) { + return requestClient.get( + `/wms/shipment-order-detail/list-by-order-id?orderId=${orderId}`, + ); +} + +export function createShipmentOrder(data: WmsShipmentOrderApi.ShipmentOrder) { + return requestClient.post('/wms/shipment-order/create', data); +} + +export function updateShipmentOrder(data: WmsShipmentOrderApi.ShipmentOrder) { + return requestClient.put('/wms/shipment-order/update', data); +} + +export function completeShipmentOrder(id: number) { + return requestClient.put(`/wms/shipment-order/complete?id=${id}`); +} + +export function cancelShipmentOrder(id: number) { + return requestClient.put(`/wms/shipment-order/cancel?id=${id}`); +} + +export function deleteShipmentOrder(id: number) { + return requestClient.delete(`/wms/shipment-order/delete?id=${id}`); +} + +export function exportShipmentOrder(params: any) { + return requestClient.download('/wms/shipment-order/export-excel', { params }); +} diff --git a/apps/web-antdv-next/src/components/number-range-input/index.ts b/apps/web-antdv-next/src/components/number-range-input/index.ts new file mode 100644 index 000000000..f1647e75d --- /dev/null +++ b/apps/web-antdv-next/src/components/number-range-input/index.ts @@ -0,0 +1,39 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { markRaw } from 'vue'; + +import NumberRangeInput from './number-range-input.vue'; + +export { default as NumberRangeInput } from './number-range-input.vue'; + +export type NumberRangeValue = [number | undefined, number | undefined]; + +function splitNumberRange(minFieldName: string, maxFieldName: string) { + return ( + value: NumberRangeValue | undefined, + setValue: (fieldName: string, value: number | undefined) => void, + ) => { + setValue(minFieldName, value?.[0]); + setValue(maxFieldName, value?.[1]); + return undefined; + }; +} + +export function buildNumberRangeSchema( + label: string, + fieldName: string, + minFieldName: string, + maxFieldName: string, + precision: number, +): VbenFormSchema { + return { + component: markRaw(NumberRangeInput), + componentProps: { + min: 0, + precision, + }, + fieldName, + label, + valueFormat: splitNumberRange(minFieldName, maxFieldName), + }; +} diff --git a/apps/web-antdv-next/src/components/number-range-input/number-range-input.vue b/apps/web-antdv-next/src/components/number-range-input/number-range-input.vue new file mode 100644 index 000000000..31c6255ec --- /dev/null +++ b/apps/web-antdv-next/src/components/number-range-input/number-range-input.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/home/index.vue b/apps/web-antdv-next/src/views/wms/home/index.vue new file mode 100644 index 000000000..11dae7153 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/home/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-chart-options.ts b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-chart-options.ts new file mode 100644 index 000000000..bc55e5d2d --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-chart-options.ts @@ -0,0 +1,116 @@ +import type { EChartsOption } from '@vben/plugins/echarts'; + +import type { WmsHomeStatisticsApi } from '#/api/wms/home'; + +import { formatQuantity } from '#/views/wms/utils/format'; + +export interface InventoryChartItem { + name: string; + value: number; +} + +/** 格式化库存数量展示文本 */ +export function formatQuantityText(value?: number) { + return formatQuantity(value || 0) || '0.00'; +} + +/** 转换库存排行接口数据为 ECharts 可消费的 name/value 数据 */ +export function buildInventoryChartItemList( + list: undefined | WmsHomeStatisticsApi.InventoryRankItem[], + emptyName: string, +) { + return (list || []) + .map((item) => ({ + name: item.name || emptyName, + value: Number(item.quantity || 0), + })) + .filter((item) => item.value > 0); +} + +/** 格式化货物占比图例,补充当前商品库存占比 */ +function formatGoodsLegend(name: string, goodsShareList: InventoryChartItem[]) { + const total = goodsShareList.reduce((sum, item) => sum + item.value, 0); + const item = goodsShareList.find((goods) => goods.name === name); + if (!total || !item) { + return name; + } + return `${name} ${((item.value / total) * 100).toFixed(1)}%`; +} + +/** 货物占比图表配置 */ +export function getGoodsShareChartOptions( + goodsShareList: InventoryChartItem[], +): EChartsOption { + return { + color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'], + legend: { + formatter: (name: string) => formatGoodsLegend(name, goodsShareList), + itemHeight: 10, + itemWidth: 10, + orient: 'vertical', + right: 10, + top: 'middle', + type: 'scroll', + }, + series: [ + { + avoidLabelOverlap: true, + center: ['34%', '52%'], + data: goodsShareList, + label: { show: false }, + labelLine: { show: false }, + name: '货物占比', + radius: ['48%', '70%'], + type: 'pie', + }, + ], + tooltip: { + formatter: '{b}
库存:{c} ({d}%)', + trigger: 'item', + }, + }; +} + +/** 库存分布图表配置 */ +export function getWarehouseDistributionChartOptions( + warehouseDistributionList: InventoryChartItem[], +): EChartsOption { + const sortedList = warehouseDistributionList.toReversed(); + return { + color: ['#2f7df6'], + grid: { bottom: 16, containLabel: true, left: 24, right: 40, top: 12 }, + series: [ + { + barMaxWidth: 16, + data: sortedList.map((item) => item.value), + label: { + formatter: ({ value }) => formatQuantityText(Number(value || 0)), + position: 'right', + show: true, + }, + name: '库存', + type: 'bar', + }, + ], + tooltip: { + formatter: (params: unknown) => { + const item = (Array.isArray(params) ? params[0] : params) as { + name?: string; + value?: number; + }; + return `${item.name || '-'}
库存:${formatQuantityText(item.value)}`; + }, + trigger: 'axis', + }, + xAxis: { + splitLine: { lineStyle: { color: '#eef2f7' } }, + type: 'value', + }, + yAxis: { + axisLine: { show: false }, + axisTick: { show: false }, + data: sortedList.map((item) => item.name), + type: 'category', + }, + }; +} diff --git a/apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-charts.vue b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-charts.vue new file mode 100644 index 000000000..58fccb5f0 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-inventory-charts.vue @@ -0,0 +1,116 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-summary-cards.vue b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-summary-cards.vue new file mode 100644 index 000000000..b3c2f2eff --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-summary-cards.vue @@ -0,0 +1,193 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart-options.ts b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart-options.ts new file mode 100644 index 000000000..9e76d9d4f --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart-options.ts @@ -0,0 +1,96 @@ +import type { EChartsOption } from '@vben/plugins/echarts'; + +import type { WmsHomeStatisticsApi } from '#/api/wms/home'; + +import { DICT_TYPE, OrderTypeEnum } from '@vben/constants'; +import { getDictLabel } from '@vben/hooks'; +import { formatDate } from '@vben/utils'; + +interface OrderDefinition { + color: string; + title: string; + trendField: keyof Pick< + WmsHomeStatisticsApi.OrderTrend, + 'checkCount' | 'movementCount' | 'receiptCount' | 'shipmentCount' + >; + type: number; +} + +const orderDefinitions: OrderDefinition[] = [ + { + color: '#2f7df6', + title: getDictLabel( + DICT_TYPE.WMS_ORDER_TYPE, + OrderTypeEnum.RECEIPT, + ).replace(/单$/, ''), + trendField: 'receiptCount', + type: OrderTypeEnum.RECEIPT, + }, + { + color: '#18a058', + title: getDictLabel( + DICT_TYPE.WMS_ORDER_TYPE, + OrderTypeEnum.SHIPMENT, + ).replace(/单$/, ''), + trendField: 'shipmentCount', + type: OrderTypeEnum.SHIPMENT, + }, + { + color: '#f59e0b', + title: getDictLabel( + DICT_TYPE.WMS_ORDER_TYPE, + OrderTypeEnum.MOVEMENT, + ).replace(/单$/, ''), + trendField: 'movementCount', + type: OrderTypeEnum.MOVEMENT, + }, + { + color: '#7c3aed', + title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.CHECK).replace( + /单$/, + '', + ), + trendField: 'checkCount', + type: OrderTypeEnum.CHECK, + }, +]; + +/** 格式化趋势接口返回的时间戳为图表横轴日期 */ +function formatTrendTime(time: number | string) { + const date = new Date(time); + return Number.isNaN(date.getTime()) + ? `${time}` + : (formatDate(date, 'MM-DD') as string); +} + +/** 单据趋势图表配置 */ +export function getOrderTrendChartOptions( + list: WmsHomeStatisticsApi.OrderTrend[], +): EChartsOption { + const labels = list.map((item) => formatTrendTime(item.time)); + return { + color: orderDefinitions.map((item) => item.color), + grid: { bottom: 24, containLabel: true, left: 28, right: 24, top: 48 }, + legend: { itemHeight: 10, itemWidth: 10, top: 6 }, + series: orderDefinitions.map((item) => ({ + barMaxWidth: 18, + data: list.map((row) => Number(row[item.trendField] || 0)), + emphasis: { focus: 'series' }, + name: item.title, + type: 'bar', + })), + tooltip: { axisPointer: { type: 'shadow' }, trigger: 'axis' }, + xAxis: { + axisLine: { lineStyle: { color: '#dcdfe6' } }, + axisTick: { show: false }, + data: labels, + type: 'category', + }, + yAxis: { + minInterval: 1, + name: '单据数', + splitLine: { lineStyle: { color: '#eef2f7' } }, + type: 'value', + }, + }; +} diff --git a/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart.vue b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart.vue new file mode 100644 index 000000000..dcb234460 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/home/modules/wms-home-order-trend-chart.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/inventory/components/index.ts b/apps/web-antdv-next/src/views/wms/inventory/components/index.ts new file mode 100644 index 000000000..a86d9117d --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/inventory/components/index.ts @@ -0,0 +1,2 @@ +export { default as WmsInventorySelect } from './select.vue'; +export type { InventorySelectRow } from './select.vue'; diff --git a/apps/web-antdv-next/src/views/wms/inventory/components/select.vue b/apps/web-antdv-next/src/views/wms/inventory/components/select.vue new file mode 100644 index 000000000..864b832ea --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/inventory/components/select.vue @@ -0,0 +1,210 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/inventory/history/data.ts b/apps/web-antdv-next/src/views/wms/inventory/history/data.ts new file mode 100644 index 000000000..02c33d7fb --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/inventory/history/data.ts @@ -0,0 +1,159 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getWarehouseSimpleList } from '#/api/wms/md/warehouse'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'orderType', + label: '单据类型', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_ORDER_TYPE, 'number'), + placeholder: '请选择单据类型', + }, + }, + { + fieldName: 'orderNo', + label: '单据号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入单据号', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getWarehouseSimpleList, + labelField: 'name', + placeholder: '请选择仓库', + showSearch: true, + valueField: 'id', + }, + }, + { + fieldName: 'itemCode', + label: '商品编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品编号', + }, + }, + { + fieldName: 'itemName', + label: '商品名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品名称', + }, + }, + { + fieldName: 'skuCode', + label: '规格编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格编号', + }, + }, + { + fieldName: 'skuName', + label: '规格名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格名称', + }, + }, + { + fieldName: 'createTime', + label: '操作时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 库存流水列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'orderNo', + title: '单据号', + width: 180, + fixed: 'left', + }, + { + field: 'orderType', + title: '单据类型', + width: 110, + fixed: 'left', + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_ORDER_TYPE }, + }, + }, + { + field: 'itemInfo', + title: '商品信息', + minWidth: 220, + slots: { default: 'itemInfo' }, + }, + { + field: 'skuInfo', + title: '规格信息', + minWidth: 220, + slots: { default: 'skuInfo' }, + }, + { + field: 'warehouseName', + title: '仓库', + minWidth: 160, + }, + { + field: 'beforeQuantity', + title: '操作前', + align: 'right', + minWidth: 110, + slots: { default: 'beforeQuantity' }, + }, + { + field: 'afterQuantity', + title: '操作后', + align: 'right', + minWidth: 110, + slots: { default: 'afterQuantity' }, + }, + { + field: 'quantityPrice', + title: '数量/金额(元)', + minWidth: 180, + slots: { default: 'quantityPrice' }, + }, + { + field: 'createTime', + title: '操作时间', + width: 180, + fixed: 'right', + align: 'center', + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/inventory/history/index.vue b/apps/web-antdv-next/src/views/wms/inventory/history/index.vue new file mode 100644 index 000000000..bdb911c05 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/inventory/history/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/inventory/index/data.ts b/apps/web-antdv-next/src/views/wms/inventory/index/data.ts new file mode 100644 index 000000000..40cfa3acf --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/inventory/index/data.ts @@ -0,0 +1,249 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getWarehouseSimpleList } from '#/api/wms/md/warehouse'; + +export const INVENTORY_DIMENSION = { + ITEM: 'item', + WAREHOUSE: 'warehouse', +} as const; + +export type InventoryDimension = + (typeof INVENTORY_DIMENSION)[keyof typeof INVENTORY_DIMENSION]; + +const dimensionOptions = [ + { label: '仓库', value: INVENTORY_DIMENSION.WAREHOUSE }, + { label: '商品', value: INVENTORY_DIMENSION.ITEM }, +]; + +interface DimensionChangeEvent { + target?: { + value?: InventoryDimension; + }; +} + +type DimensionChangeHandler = ( + dimension: InventoryDimension, +) => Promise | void; + +/** 统一解析 antd 事件对象和原始维度值 */ +function getDimensionChangeValue( + value?: DimensionChangeEvent | InventoryDimension, +): InventoryDimension { + if ( + value === INVENTORY_DIMENSION.ITEM || + value === INVENTORY_DIMENSION.WAREHOUSE + ) { + return value; + } + return value?.target?.value ?? INVENTORY_DIMENSION.WAREHOUSE; +} + +/** 搜索表单 */ +export function useGridFormSchema( + onDimensionChange: DimensionChangeHandler, +): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '统计维度', + component: 'RadioGroup', + defaultValue: INVENTORY_DIMENSION.WAREHOUSE, + componentProps: { + buttonStyle: 'solid', + optionType: 'button', + options: dimensionOptions, + onChange: (value: DimensionChangeEvent | InventoryDimension) => { + void onDimensionChange(getDimensionChangeValue(value)); + }, + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getWarehouseSimpleList, + labelField: 'name', + placeholder: '请选择仓库', + showSearch: true, + valueField: 'id', + }, + }, + { + fieldName: 'itemName', + label: '商品名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品名称', + }, + }, + { + fieldName: 'itemCode', + label: '商品编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品编号', + }, + }, + { + fieldName: 'skuName', + label: '规格名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格名称', + }, + }, + { + fieldName: 'skuCode', + label: '规格编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格编号', + }, + }, + ]; +} + +const warehouseDimensionColumns: VxeTableGridOptions['columns'] = [ + { + field: 'warehouseId', + title: '仓库', + minWidth: 160, + slots: { default: 'warehouseName' }, + }, + { + field: 'warehouseItemId', + title: '商品信息', + minWidth: 240, + slots: { default: 'itemInfo' }, + }, + { + field: 'skuId', + title: '规格信息', + minWidth: 220, + slots: { default: 'skuInfo' }, + }, + { + field: 'quantity', + title: '库存', + align: 'right', + minWidth: 130, + slots: { default: 'quantity' }, + }, +]; + +const itemDimensionColumns: VxeTableGridOptions['columns'] = [ + { + field: 'itemId', + title: '商品信息', + minWidth: 240, + slots: { default: 'itemInfo' }, + }, + { + field: 'skuId', + title: '规格信息', + minWidth: 220, + slots: { default: 'skuInfo' }, + }, + { + field: 'skuWarehouseId', + title: '仓库', + minWidth: 160, + slots: { default: 'warehouseName' }, + }, + { + field: 'quantity', + title: '库存', + align: 'right', + minWidth: 130, + slots: { default: 'quantity' }, + }, +]; + +/** 库存统计列表字段 */ +export function useGridColumns( + dimension: InventoryDimension = INVENTORY_DIMENSION.WAREHOUSE, +): VxeTableGridOptions['columns'] { + return dimension === INVENTORY_DIMENSION.ITEM + ? itemDimensionColumns + : warehouseDimensionColumns; +} + +/** 库存选择弹窗搜索表单 */ +export function useInventorySelectGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'itemName', + label: '商品名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品名称', + }, + }, + { + fieldName: 'itemCode', + label: '商品编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品编号', + }, + }, + { + fieldName: 'skuName', + label: '规格名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格名称', + }, + }, + { + fieldName: 'skuCode', + label: '规格编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格编号', + }, + }, + ]; +} + +/** 库存选择弹窗列表字段 */ +export function useInventorySelectGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 50 }, + { + field: 'itemInfo', + title: '商品信息', + minWidth: 220, + slots: { default: 'itemInfo' }, + }, + { + field: 'skuInfo', + title: '规格信息', + minWidth: 220, + slots: { default: 'skuInfo' }, + }, + { + field: 'warehouseName', + title: '仓库', + minWidth: 180, + }, + { + field: 'availableQuantity', + title: '可用库存', + align: 'right', + width: 130, + slots: { default: 'availableQuantity' }, + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/inventory/index/index.vue b/apps/web-antdv-next/src/views/wms/inventory/index/index.vue new file mode 100644 index 000000000..72773c02f --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/inventory/index/index.vue @@ -0,0 +1,190 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/brand/components/index.ts b/apps/web-antdv-next/src/views/wms/md/item/brand/components/index.ts new file mode 100644 index 000000000..bbff4fe4b --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/brand/components/index.ts @@ -0,0 +1 @@ +export { default as WmsItemBrandSelect } from './select.vue'; diff --git a/apps/web-antdv-next/src/views/wms/md/item/brand/components/select.vue b/apps/web-antdv-next/src/views/wms/md/item/brand/components/select.vue new file mode 100644 index 000000000..139d31dc7 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/brand/components/select.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/brand/data.ts b/apps/web-antdv-next/src/views/wms/md/item/brand/data.ts new file mode 100644 index 000000000..7c189a8cd --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/brand/data.ts @@ -0,0 +1,108 @@ +import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h } from 'vue'; + +import { generateWmsCode } from '@vben/constants'; + +import { Button } from 'antdv-next'; + +import { z } from '#/adapter/form'; + +/** 新增/修改商品品牌的表单 */ +export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'code', + label: '品牌编号', + component: 'Input', + componentProps: { + maxLength: 20, + placeholder: '请输入品牌编号', + }, + rules: z.string().min(1, '品牌编号不能为空').max(20), + suffix: () => { + return h( + Button, + { + type: 'default', + onClick: () => { + formApi?.setFieldValue('code', generateWmsCode('B')); + }, + }, + { default: () => '生成' }, + ); + }, + }, + { + fieldName: 'name', + label: '品牌名称', + component: 'Input', + componentProps: { + maxLength: 30, + placeholder: '请输入品牌名称', + }, + rules: z.string().min(1, '品牌名称不能为空').max(30), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'code', + label: '品牌编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入品牌编号', + }, + }, + { + fieldName: 'name', + label: '品牌名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入品牌名称', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'code', + title: '品牌编号', + width: 160, + }, + { + field: 'name', + title: '品牌名称', + minWidth: 160, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/md/item/brand/index.vue b/apps/web-antdv-next/src/views/wms/md/item/brand/index.vue new file mode 100644 index 000000000..813def2d2 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/brand/index.vue @@ -0,0 +1,145 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/brand/modules/form.vue b/apps/web-antdv-next/src/views/wms/md/item/brand/modules/form.vue new file mode 100644 index 000000000..c58a7961a --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/brand/modules/form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/components/index.ts b/apps/web-antdv-next/src/views/wms/md/item/category/components/index.ts new file mode 100644 index 000000000..c61267e76 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/category/components/index.ts @@ -0,0 +1,2 @@ +export { default as WmsItemCategorySelect } from './select.vue'; +export { default as WmsItemCategoryTree } from './tree.vue'; diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/components/select.vue b/apps/web-antdv-next/src/views/wms/md/item/category/components/select.vue new file mode 100644 index 000000000..b733d7e34 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/category/components/select.vue @@ -0,0 +1,68 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue b/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue new file mode 100644 index 000000000..7954aea91 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/data.ts b/apps/web-antdv-next/src/views/wms/md/item/category/data.ts new file mode 100644 index 000000000..8b904d8c8 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/category/data.ts @@ -0,0 +1,186 @@ +import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { WmsItemCategoryApi } from '#/api/wms/md/item/category'; + +import { h } from 'vue'; + +import { CommonStatusEnum, DICT_TYPE, generateWmsCode } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { handleTree } from '@vben/utils'; + +import { Button } from 'antdv-next'; + +import { z } from '#/adapter/form'; +import { getItemCategorySimpleList } from '#/api/wms/md/item/category'; + +/** 新增/修改商品分类的表单 */ +export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级分类', + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + api: async () => { + const data = await getItemCategorySimpleList(); + return [ + { + id: 0, + name: '顶级分类', + children: handleTree(data), + }, + ]; + }, + childrenField: 'children', + labelField: 'name', + placeholder: '请选择上级分类', + treeDefaultExpandAll: true, + treeNodeFilterProp: 'name', + valueField: 'id', + }, + rules: 'selectRequired', + }, + { + fieldName: 'code', + label: '分类编号', + component: 'Input', + componentProps: { + maxLength: 20, + placeholder: '请输入分类编号', + }, + rules: z.string().min(1, '分类编号不能为空').max(20), + suffix: () => { + return h( + Button, + { + type: 'default', + onClick: () => { + formApi?.setFieldValue('code', generateWmsCode('C')); + }, + }, + { default: () => '生成' }, + ); + }, + }, + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + placeholder: '请输入分类名称', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示排序', + component: 'InputNumber', + componentProps: { + class: '!w-full', + min: 0, + }, + rules: z.number().default(0), + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + optionType: 'button', + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'code', + label: '分类编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入分类编号', + }, + }, + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入分类名称', + }, + }, + { + fieldName: 'status', + label: '分类状态', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择分类状态', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '分类名称', + minWidth: 200, + align: 'left', + treeNode: true, + }, + { + field: 'code', + title: '分类编号', + width: 160, + align: 'center', + }, + { + field: 'sort', + title: '排序', + width: 120, + align: 'center', + }, + { + field: 'status', + title: '状态', + width: 120, + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/index.vue b/apps/web-antdv-next/src/views/wms/md/item/category/index.vue new file mode 100644 index 000000000..d3ad68b10 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/category/index.vue @@ -0,0 +1,162 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/modules/form.vue b/apps/web-antdv-next/src/views/wms/md/item/category/modules/form.vue new file mode 100644 index 000000000..d76f95f36 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/category/modules/form.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/data.ts b/apps/web-antdv-next/src/views/wms/md/item/data.ts new file mode 100644 index 000000000..97474f863 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/data.ts @@ -0,0 +1,261 @@ +import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h, markRaw } from 'vue'; + +import { generateWmsCode } from '@vben/constants'; + +import { Button } from 'antdv-next'; + +import { z } from '#/adapter/form'; +import { getItemBrandSimpleList } from '#/api/wms/md/item/brand'; + +import { WmsItemBrandSelect } from './brand/components'; +import { WmsItemCategorySelect } from './category/components'; + +/** 新增/修改商品的表单 */ +export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '商品名称', + component: 'Input', + componentProps: { + maxLength: 60, + placeholder: '请输入商品名称', + }, + rules: z.string().min(1, '商品名称不能为空').max(60), + }, + { + fieldName: 'categoryId', + label: '商品分类', + component: markRaw(WmsItemCategorySelect), + rules: 'required', + }, + { + fieldName: 'code', + label: '商品编号', + component: 'Input', + componentProps: { + maxLength: 20, + placeholder: '请输入商品编号', + }, + rules: z.string().min(1, '商品编号不能为空').max(20), + suffix: () => { + return h( + Button, + { + type: 'default', + onClick: () => { + formApi?.setFieldValue('code', generateWmsCode('I')); + }, + }, + { default: () => '生成' }, + ); + }, + }, + { + fieldName: 'unit', + label: '商品单位', + component: 'Input', + componentProps: { + maxLength: 20, + placeholder: '请输入单位', + }, + }, + { + fieldName: 'brandId', + label: '商品品牌', + component: markRaw(WmsItemBrandSelect), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + rows: 3, + }, + formItemClass: 'col-span-2', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'code', + label: '商品编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品编号', + }, + }, + { + fieldName: 'name', + label: '商品名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品名称', + }, + }, + { + fieldName: 'brandId', + label: '商品品牌', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getItemBrandSimpleList, + labelField: 'name', + placeholder: '请选择商品品牌', + showSearch: true, + valueField: 'id', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'itemInfo', + title: '商品信息', + minWidth: 220, + slots: { default: 'itemInfo' }, + }, + { + field: 'skuInfo', + title: '规格信息', + minWidth: 180, + slots: { default: 'skuInfo' }, + }, + { + field: 'priceInfo', + title: '金额(元)', + minWidth: 140, + slots: { default: 'priceInfo' }, + }, + { + field: 'weightInfo', + title: '重量(kg)', + minWidth: 140, + slots: { default: 'weightInfo' }, + }, + { + field: 'dimensionInfo', + title: '长宽高(cm)', + minWidth: 180, + align: 'right', + slots: { default: 'dimensionInfo' }, + }, + { + field: 'actions', + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** SKU 选择弹窗搜索表单 */ +export function useSkuSelectGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'itemName', + label: '商品名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品名称', + }, + }, + { + fieldName: 'itemCode', + label: '商品编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入商品编号', + }, + }, + { + fieldName: 'name', + label: '规格名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格名称', + }, + }, + { + fieldName: 'code', + label: '规格编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规格编号', + }, + }, + { + fieldName: 'barCode', + label: '条码', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入条码', + }, + }, + ]; +} + +/** SKU 选择弹窗列表字段 */ +export function useSkuSelectGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 50 }, + { + field: 'itemInfo', + title: '商品信息', + minWidth: 220, + slots: { default: 'itemInfo' }, + }, + { + field: 'skuInfo', + title: '规格信息', + minWidth: 220, + slots: { default: 'skuInfo' }, + }, + { + field: 'priceInfo', + title: '金额(元)', + minWidth: 160, + slots: { default: 'priceInfo' }, + }, + { + field: 'weightInfo', + title: '重量(kg)', + minWidth: 160, + slots: { default: 'weightInfo' }, + }, + { + field: 'dimensionInfo', + title: '长宽高(cm)', + minWidth: 180, + align: 'right', + slots: { default: 'dimensionInfo' }, + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/md/item/index.vue b/apps/web-antdv-next/src/views/wms/md/item/index.vue new file mode 100644 index 000000000..f8af1a2d8 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/index.vue @@ -0,0 +1,280 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/modules/form.vue b/apps/web-antdv-next/src/views/wms/md/item/modules/form.vue new file mode 100644 index 000000000..b485fe9d1 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/modules/form.vue @@ -0,0 +1,102 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/modules/sku-form.vue b/apps/web-antdv-next/src/views/wms/md/item/modules/sku-form.vue new file mode 100644 index 000000000..b119f30ca --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/modules/sku-form.vue @@ -0,0 +1,300 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/item/sku/components/index.ts b/apps/web-antdv-next/src/views/wms/md/item/sku/components/index.ts new file mode 100644 index 000000000..ed8725397 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/sku/components/index.ts @@ -0,0 +1 @@ +export { default as WmsItemSkuSelect } from './select.vue'; diff --git a/apps/web-antdv-next/src/views/wms/md/item/sku/components/select.vue b/apps/web-antdv-next/src/views/wms/md/item/sku/components/select.vue new file mode 100644 index 000000000..d539a778d --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/item/sku/components/select.vue @@ -0,0 +1,224 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/merchant/components/index.ts b/apps/web-antdv-next/src/views/wms/md/merchant/components/index.ts new file mode 100644 index 000000000..b21bf58f3 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/merchant/components/index.ts @@ -0,0 +1 @@ +export { default as WmsMerchantSelect } from './select.vue'; diff --git a/apps/web-antdv-next/src/views/wms/md/merchant/components/select.vue b/apps/web-antdv-next/src/views/wms/md/merchant/components/select.vue new file mode 100644 index 000000000..614e7b46c --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/merchant/components/select.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/merchant/data.ts b/apps/web-antdv-next/src/views/wms/md/merchant/data.ts new file mode 100644 index 000000000..03914f278 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/merchant/data.ts @@ -0,0 +1,231 @@ +import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h } from 'vue'; + +import { DICT_TYPE, generateWmsCode } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { Button } from 'antdv-next'; + +import { z } from '#/adapter/form'; + +/** 新增/修改往来企业的表单 */ +export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'code', + label: '往来企业编号', + component: 'Input', + componentProps: { + maxLength: 20, + placeholder: '请输入往来企业编号', + }, + rules: z.string().min(1, '往来企业编号不能为空').max(20), + suffix: () => { + return h( + Button, + { + type: 'default', + onClick: () => { + formApi?.setFieldValue('code', generateWmsCode('M')); + }, + }, + { default: () => '生成' }, + ); + }, + }, + { + fieldName: 'name', + label: '往来企业名称', + component: 'Input', + componentProps: { + maxLength: 60, + placeholder: '请输入往来企业名称', + }, + rules: z.string().min(1, '往来企业名称不能为空').max(60), + }, + { + fieldName: 'type', + label: '往来企业类型', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_MERCHANT_TYPE, 'number'), + placeholder: '请选择往来企业类型', + }, + rules: 'required', + }, + { + fieldName: 'level', + label: '级别', + component: 'Input', + componentProps: { + maxLength: 10, + placeholder: '请输入级别', + }, + }, + { + fieldName: 'bankName', + label: '开户行', + component: 'Input', + componentProps: { + maxLength: 255, + placeholder: '请输入开户行', + }, + }, + { + fieldName: 'bankAccount', + label: '银行账户', + component: 'Input', + componentProps: { + maxLength: 40, + placeholder: '请输入银行账户', + }, + }, + { + fieldName: 'address', + label: '地址', + component: 'Input', + componentProps: { + maxLength: 200, + placeholder: '请输入地址', + }, + }, + { + fieldName: 'contact', + label: '联系人', + component: 'Input', + componentProps: { + maxLength: 30, + placeholder: '请输入联系人', + }, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + maxLength: 13, + placeholder: '请输入手机号', + }, + }, + { + fieldName: 'telephone', + label: '座机号', + component: 'Input', + componentProps: { + maxLength: 13, + placeholder: '请输入座机号', + }, + }, + { + fieldName: 'email', + label: 'Email', + component: 'Input', + componentProps: { + maxLength: 50, + placeholder: '请输入 Email', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'code', + label: '往来企业编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入往来企业编号', + }, + }, + { + fieldName: 'name', + label: '往来企业名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入往来企业名称', + }, + }, + { + fieldName: 'type', + label: '往来企业类型', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_MERCHANT_TYPE, 'number'), + placeholder: '请选择往来企业类型', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'code', + title: '往来企业编号', + width: 160, + }, + { + field: 'name', + title: '往来企业名称', + minWidth: 160, + }, + { + field: 'type', + title: '往来企业类型', + width: 120, + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_MERCHANT_TYPE }, + }, + }, + { + field: 'level', + title: '级别', + width: 100, + align: 'center', + }, + { + field: 'contact', + title: '联系人', + width: 120, + }, + { + field: 'remark', + title: '备注', + minWidth: 160, + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/md/merchant/index.vue b/apps/web-antdv-next/src/views/wms/md/merchant/index.vue new file mode 100644 index 000000000..c0ed6a214 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/merchant/index.vue @@ -0,0 +1,152 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/merchant/modules/form.vue b/apps/web-antdv-next/src/views/wms/md/merchant/modules/form.vue new file mode 100644 index 000000000..9c99191bc --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/merchant/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/warehouse/components/index.ts b/apps/web-antdv-next/src/views/wms/md/warehouse/components/index.ts new file mode 100644 index 000000000..b612ee62a --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/warehouse/components/index.ts @@ -0,0 +1 @@ +export { default as WmsWarehouseSelect } from './select.vue'; diff --git a/apps/web-antdv-next/src/views/wms/md/warehouse/components/select.vue b/apps/web-antdv-next/src/views/wms/md/warehouse/components/select.vue new file mode 100644 index 000000000..65bae9635 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/warehouse/components/select.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/warehouse/data.ts b/apps/web-antdv-next/src/views/wms/md/warehouse/data.ts new file mode 100644 index 000000000..0654c3d0a --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/warehouse/data.ts @@ -0,0 +1,139 @@ +import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h } from 'vue'; + +import { generateWmsCode } from '@vben/constants'; + +import { Button } from 'antdv-next'; + +import { z } from '#/adapter/form'; + +/** 新增/修改仓库的表单 */ +export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '仓库名称', + component: 'Input', + componentProps: { + maxLength: 50, + placeholder: '请输入仓库名称', + }, + rules: z.string().min(1, '仓库名称不能为空').max(50), + }, + { + fieldName: 'code', + label: '仓库编号', + component: 'Input', + componentProps: { + maxLength: 20, + placeholder: '请输入仓库编号', + }, + rules: z.string().min(1, '仓库编号不能为空').max(20), + suffix: () => { + return h( + Button, + { + type: 'default', + onClick: () => { + formApi?.setFieldValue('code', generateWmsCode('W')); + }, + }, + { default: () => '生成' }, + ); + }, + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + class: '!w-full', + min: 0, + }, + rules: z.number().default(0), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + rows: 3, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '仓库名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入仓库名称', + }, + }, + { + fieldName: 'code', + label: '仓库编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入仓库编号', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '仓库名称', + minWidth: 160, + }, + { + field: 'code', + title: '仓库编号', + minWidth: 140, + }, + { + field: 'remark', + title: '备注', + minWidth: 220, + }, + { + field: 'sort', + title: '排序', + width: 100, + align: 'center', + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/md/warehouse/index.vue b/apps/web-antdv-next/src/views/wms/md/warehouse/index.vue new file mode 100644 index 000000000..20624a340 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/warehouse/index.vue @@ -0,0 +1,152 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/md/warehouse/modules/form.vue b/apps/web-antdv-next/src/views/wms/md/warehouse/modules/form.vue new file mode 100644 index 000000000..7fba91e3b --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/md/warehouse/modules/form.vue @@ -0,0 +1,92 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/check/data.ts b/apps/web-antdv-next/src/views/wms/order/check/data.ts new file mode 100644 index 000000000..0efaec3f9 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/check/data.ts @@ -0,0 +1,488 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; +import type { NumberRangeValue } from '#/components/number-range-input'; + +import { h, markRaw } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDate, formatDateTime } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { DictTag } from '#/components/dict-tag'; +import { + buildNumberRangeSchema, + NumberRangeInput, +} from '#/components/number-range-input'; +import { getRangePickerDefaultProps } from '#/utils'; +import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components'; +import { + formatPrice, + formatQuantity, + formatSumPrice, + formatSumQuantity, + getLossClass, + PRICE_PRECISION, + QUANTITY_PRECISION, + roundPrice, + sumPrice, + sumQuantity, +} from '#/views/wms/utils/format'; + +/** 表单类型 */ +export type FormType = 'create' | 'update'; + +/** 拆分数量/金额区间字段,适配后端 Min/Max 查询参数 */ +function splitNumberRange(minFieldName: string, maxFieldName: string) { + return ( + value: NumberRangeValue | undefined, + setValue: (fieldName: string, value: number | undefined) => void, + ) => { + setValue(minFieldName, value?.[0]); + setValue(maxFieldName, value?.[1]); + return undefined; + }; +} + +/** 构建允许负数的区间搜索项,盘库盈亏数量需要支持盘亏 */ +function buildSignedNumberRangeSchema( + label: string, + fieldName: string, + minFieldName: string, + maxFieldName: string, + precision: number, +): VbenFormSchema { + return { + component: markRaw(NumberRangeInput), + componentProps: { + precision, + }, + fieldName, + label, + valueFormat: splitNumberRange(minFieldName, maxFieldName), + }; +} + +/** 计算单据盈亏金额 */ +export function getOrderDifferencePrice(order: { + actualPrice?: number; + totalPrice?: number; +}) { + return roundPrice( + Number(order.actualPrice || 0) - Number(order.totalPrice || 0), + ); +} + +/** 计算明细盈亏数量 */ +export function getDetailDifferenceQuantity(detail: { + checkQuantity?: number; + quantity?: number; +}) { + return Number(detail.checkQuantity || 0) - Number(detail.quantity || 0); +} + +/** 计算明细实际金额 */ +export function getDetailActualPrice(detail: { + checkQuantity?: number; + price?: number; +}) { + if ( + detail.checkQuantity === undefined || + detail.checkQuantity === null || + detail.price === undefined || + detail.price === null + ) { + return undefined; + } + return roundPrice(Number(detail.checkQuantity) * Number(detail.price)); +} + +/** 计算明细盈亏金额 */ +export function getDetailDifferencePrice(detail: { + checkQuantity?: number; + price?: number; + quantity?: number; +}) { + if (detail.price === undefined || detail.price === null) { + return undefined; + } + return roundPrice(getDetailDifferenceQuantity(detail) * Number(detail.price)); +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入盘库单号', + }, + fieldName: 'no', + label: '盘库单号', + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_ORDER_STATUS, 'number'), + placeholder: '请选择单据状态', + }, + fieldName: 'status', + label: '单据状态', + }, + { + component: markRaw(WmsWarehouseSelect), + fieldName: 'warehouseId', + label: '仓库', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'orderTime', + label: '单据日期', + }, + buildSignedNumberRangeSchema( + '盈亏数量', + 'totalQuantityRange', + 'totalQuantityMin', + 'totalQuantityMax', + QUANTITY_PRECISION, + ), + buildNumberRangeSchema( + '总金额', + 'totalPriceRange', + 'totalPriceMin', + 'totalPriceMax', + PRICE_PRECISION, + ), + buildNumberRangeSchema( + '实际金额', + 'actualPriceRange', + 'actualPriceMin', + 'actualPriceMax', + PRICE_PRECISION, + ), + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择创建用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'creator', + label: '创建用户', + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择更新用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'updater', + label: '更新用户', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'createTime', + label: '创建时间', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'updateTime', + label: '更新时间', + }, + ]; +} + +/** 列表表格列 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + fixed: 'left', + slots: { content: 'expand_content' }, + type: 'expand', + width: 48, + }, + { + field: 'no', + fixed: 'left', + slots: { default: 'no' }, + title: '单号', + width: 210, + }, + { + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_ORDER_STATUS }, + }, + field: 'status', + fixed: 'left', + title: '盘库状态', + width: 110, + }, + { + field: 'warehouseName', + minWidth: 180, + title: '仓库', + }, + { + field: 'quantityAmount', + minWidth: 200, + slots: { default: 'quantityAmount' }, + title: '盈亏/金额(元)', + }, + { + field: 'operateInfo', + minWidth: 280, + slots: { default: 'operateInfo' }, + title: '操作信息', + }, + { + field: 'remark', + minWidth: 160, + title: '备注', + }, + { + field: 'actions', + fixed: 'right', + slots: { default: 'actions' }, + title: '操作', + width: 220, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '盘库单号', + render: (val) => val || '-', + }, + { + field: 'warehouseName', + label: '仓库', + render: (val) => val || '-', + }, + { + field: 'orderTime', + label: '单据日期', + render: (val) => formatDate(val, 'YYYY-MM-DD') || '-', + }, + { + field: 'status', + label: '单据状态', + render: (val) => + val === undefined || val === null + ? '-' + : h(DictTag, { + type: DICT_TYPE.WMS_ORDER_STATUS, + value: val, + }), + }, + { + field: 'totalQuantity', + label: '盈亏数量', + render: (val) => + h('span', { class: getLossClass(val) }, formatQuantity(val) || '-'), + }, + { + field: 'totalPrice', + label: '总金额', + render: (val) => formatPrice(val) || '-', + }, + { + field: 'actualPrice', + label: '实际金额', + render: (val) => formatPrice(val) || '-', + }, + { + field: 'differencePrice', + label: '实际盈亏金额', + render: (_val, data) => { + const differencePrice = getOrderDifferencePrice(data || {}); + return h( + 'span', + { class: getLossClass(differencePrice) }, + formatPrice(differencePrice) || '-', + ); + }, + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'creatorName', + label: '创建人', + render: (val, data) => val || data?.creator || '-', + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'updaterName', + label: '更新人', + render: (val, data) => val || data?.updater || '-', + }, + { + field: 'remark', + label: '备注', + render: (val) => val || '-', + span: 2, + }, + ]; +} + +/** 表单的配置项 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + fieldName: 'id', + }, + { + component: 'Input', + componentProps: { + maxLength: 64, + placeholder: '请输入盘库单号', + }, + fieldName: 'no', + label: '盘库单号', + rules: z.string().min(1, '盘库单号不能为空').max(64), + }, + { + component: markRaw(WmsWarehouseSelect), + componentProps: { + disabled: true, + }, + fieldName: 'warehouseId', + label: '仓库', + rules: 'required', + }, + { + component: 'DatePicker', + componentProps: { + class: 'w-full', + format: 'YYYY-MM-DD', + placeholder: '请选择单据日期', + valueFormat: 'x', + }, + fieldName: 'orderTime', + label: '单据日期', + rules: 'required', + }, + { + component: 'Input', + componentProps: { + disabled: true, + }, + fieldName: 'actualPrice', + label: '实际金额', + }, + { + component: 'Textarea', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + }, + fieldName: 'remark', + formItemClass: 'col-span-2', + label: '备注', + }, + ]; +} + +/** 选择盘库仓库表单的配置项 */ +export function useWarehouseFormSchema( + onWarehouseChange: (warehouse: unknown) => void, +): VbenFormSchema[] { + return [ + { + component: markRaw(WmsWarehouseSelect), + componentProps: { + onChange: onWarehouseChange, + }, + fieldName: 'warehouseId', + label: '仓库', + rules: 'required', + }, + ]; +} + +interface CheckOrderDetailFooterRow { + actualPrice?: number; + checkQuantity?: number; + differencePrice?: number; + differenceQuantity?: number; + quantity?: number; +} +type CheckOrderDetailFooterColumn = Pick< + NonNullable[number]>, + 'field' +>; + +/** 明细表格的合计行 */ +export function getCheckDetailFooter({ + columns, + data, +}: { + columns: CheckOrderDetailFooterColumn[]; + data: CheckOrderDetailFooterRow[]; +}) { + return [ + columns.map((column, index) => { + if (index === 0) { + return '合计'; + } + if (column.field === 'quantity') { + return formatSumQuantity(data, (detail) => detail.quantity); + } + if (column.field === 'checkQuantity') { + return formatSumQuantity(data, (detail) => detail.checkQuantity); + } + if (column.field === 'actualPrice') { + return formatSumPrice(data, (detail) => detail.actualPrice); + } + if (column.field === 'differenceQuantity') { + return formatQuantity( + sumQuantity(data, (detail) => detail.differenceQuantity), + ); + } + if (column.field === 'differencePrice') { + return formatPrice(sumPrice(data, (detail) => detail.differencePrice)); + } + return ''; + }), + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/order/check/index.vue b/apps/web-antdv-next/src/views/wms/order/check/index.vue new file mode 100644 index 000000000..f33acc328 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/check/index.vue @@ -0,0 +1,380 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/check/modules/detail.vue b/apps/web-antdv-next/src/views/wms/order/check/modules/detail.vue new file mode 100644 index 000000000..e53989b84 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/check/modules/detail.vue @@ -0,0 +1,170 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/check/modules/form.vue b/apps/web-antdv-next/src/views/wms/order/check/modules/form.vue new file mode 100644 index 000000000..59ccf30fe --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/check/modules/form.vue @@ -0,0 +1,741 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/check/modules/print.vue b/apps/web-antdv-next/src/views/wms/order/check/modules/print.vue new file mode 100644 index 000000000..f206f994d --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/check/modules/print.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/apps/web-antdv-next/src/views/wms/order/movement/data.ts b/apps/web-antdv-next/src/views/wms/order/movement/data.ts new file mode 100644 index 000000000..2ae4ac646 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/movement/data.ts @@ -0,0 +1,372 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { WmsWarehouseApi } from '#/api/wms/md/warehouse'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h, markRaw } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDate, formatDateTime } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { DictTag } from '#/components/dict-tag'; +import { buildNumberRangeSchema } from '#/components/number-range-input'; +import { getRangePickerDefaultProps } from '#/utils'; +import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components'; +import { + formatPrice, + formatQuantity, + formatSumPrice, + formatSumQuantity, + PRICE_PRECISION, + QUANTITY_PRECISION, +} from '#/views/wms/utils/format'; + +/** 表单类型 */ +export type FormType = 'create' | 'update'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入移库单号', + }, + fieldName: 'no', + label: '移库单号', + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_ORDER_STATUS, 'number'), + placeholder: '请选择单据状态', + }, + fieldName: 'status', + label: '单据状态', + }, + { + component: markRaw(WmsWarehouseSelect), + fieldName: 'sourceWarehouseId', + label: '来源仓库', + }, + { + component: markRaw(WmsWarehouseSelect), + fieldName: 'targetWarehouseId', + label: '目标仓库', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'orderTime', + label: '单据日期', + }, + buildNumberRangeSchema( + '数量', + 'totalQuantityRange', + 'totalQuantityMin', + 'totalQuantityMax', + QUANTITY_PRECISION, + ), + buildNumberRangeSchema( + '总金额', + 'totalPriceRange', + 'totalPriceMin', + 'totalPriceMax', + PRICE_PRECISION, + ), + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择创建用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'creator', + label: '创建用户', + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择更新用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'updater', + label: '更新用户', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'createTime', + label: '创建时间', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'updateTime', + label: '更新时间', + }, + ]; +} + +/** 列表表格列 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + fixed: 'left', + slots: { content: 'expand_content' }, + type: 'expand', + width: 48, + }, + { + field: 'no', + fixed: 'left', + slots: { default: 'no' }, + title: '单号', + width: 210, + }, + { + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_ORDER_STATUS }, + }, + field: 'status', + fixed: 'left', + title: '移库状态', + width: 110, + }, + { + field: 'sourceWarehouseName', + minWidth: 180, + title: '来源仓库', + }, + { + field: 'targetWarehouseName', + minWidth: 180, + title: '目标仓库', + }, + { + field: 'quantityAmount', + minWidth: 180, + slots: { default: 'quantityAmount' }, + title: '总数量/总金额(元)', + }, + { + field: 'operateInfo', + minWidth: 260, + slots: { default: 'operateInfo' }, + title: '操作信息', + }, + { + field: 'remark', + minWidth: 160, + title: '备注', + }, + { + field: 'actions', + fixed: 'right', + slots: { default: 'actions' }, + title: '操作', + width: 220, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '移库单号', + render: (val) => val || '-', + }, + { + field: 'sourceWarehouseName', + label: '来源仓库', + render: (val) => val || '-', + }, + { + field: 'targetWarehouseName', + label: '目标仓库', + render: (val) => val || '-', + }, + { + field: 'status', + label: '单据状态', + render: (val) => + val === undefined || val === null + ? '-' + : h(DictTag, { + type: DICT_TYPE.WMS_ORDER_STATUS, + value: val, + }), + }, + { + field: 'orderTime', + label: '单据日期', + render: (val) => formatDate(val, 'YYYY-MM-DD') || '-', + }, + { + field: 'totalQuantity', + label: '总数量', + render: (val) => formatQuantity(val) || '-', + }, + { + field: 'totalPrice', + label: '总金额', + render: (val) => formatPrice(val) || '-', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'creatorName', + label: '创建人', + render: (val, data) => val || data?.creator || '-', + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'updaterName', + label: '更新人', + render: (val, data) => val || data?.updater || '-', + }, + { + field: 'remark', + label: '备注', + render: (val) => val || '-', + span: 2, + }, + ]; +} + +interface MovementFormSchemaOptions { + onSourceWarehouseChange: (warehouse?: WmsWarehouseApi.Warehouse) => void; + onTargetWarehouseChange: (warehouse?: WmsWarehouseApi.Warehouse) => void; +} + +/** 表单的配置项 */ +export function useFormSchema({ + onSourceWarehouseChange, + onTargetWarehouseChange, +}: MovementFormSchemaOptions): VbenFormSchema[] { + return [ + { + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + fieldName: 'id', + }, + { + component: 'Input', + componentProps: { + maxLength: 64, + placeholder: '请输入移库单号', + }, + fieldName: 'no', + label: '移库单号', + rules: z.string().min(1, '移库单号不能为空').max(64), + }, + { + component: markRaw(WmsWarehouseSelect), + componentProps: { + onChange: onSourceWarehouseChange, + }, + fieldName: 'sourceWarehouseId', + label: '来源仓库', + rules: 'required', + }, + { + component: markRaw(WmsWarehouseSelect), + componentProps: { + onChange: onTargetWarehouseChange, + }, + fieldName: 'targetWarehouseId', + label: '目标仓库', + rules: 'required', + }, + { + component: 'DatePicker', + componentProps: { + class: 'w-full', + format: 'YYYY-MM-DD', + placeholder: '请选择单据日期', + valueFormat: 'x', + }, + fieldName: 'orderTime', + label: '单据日期', + rules: 'required', + }, + { + component: 'Textarea', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + }, + fieldName: 'remark', + formItemClass: 'col-span-2', + label: '备注', + }, + ]; +} + +interface MovementOrderDetailFooterRow { + quantity?: number; + totalPrice?: number; +} +type MovementOrderDetailFooterColumn = Pick< + NonNullable[number]>, + 'field' +>; + +/** 明细表格的合计行 */ +export function getDetailFooter({ + columns, + data, +}: { + columns: MovementOrderDetailFooterColumn[]; + data: MovementOrderDetailFooterRow[]; +}) { + return [ + columns.map((column, index) => { + if (index === 0) { + return '合计'; + } + if (column.field === 'quantity') { + return formatSumQuantity(data, (detail) => detail.quantity); + } + if (column.field === 'totalPrice') { + return formatSumPrice(data, (detail) => detail.totalPrice); + } + return ''; + }), + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/order/movement/index.vue b/apps/web-antdv-next/src/views/wms/order/movement/index.vue new file mode 100644 index 000000000..83c8b14d9 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/movement/index.vue @@ -0,0 +1,349 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/movement/modules/detail.vue b/apps/web-antdv-next/src/views/wms/order/movement/modules/detail.vue new file mode 100644 index 000000000..90a41a2be --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/movement/modules/detail.vue @@ -0,0 +1,126 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/movement/modules/form.vue b/apps/web-antdv-next/src/views/wms/order/movement/modules/form.vue new file mode 100644 index 000000000..a0acbe0ed --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/movement/modules/form.vue @@ -0,0 +1,546 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/movement/modules/print.vue b/apps/web-antdv-next/src/views/wms/order/movement/modules/print.vue new file mode 100644 index 000000000..44c75ada4 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/movement/modules/print.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/apps/web-antdv-next/src/views/wms/order/receipt/data.ts b/apps/web-antdv-next/src/views/wms/order/receipt/data.ts new file mode 100644 index 000000000..eebdadb67 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/receipt/data.ts @@ -0,0 +1,430 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h, markRaw } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDate, formatDateTime } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { DictTag } from '#/components/dict-tag'; +import { buildNumberRangeSchema } from '#/components/number-range-input'; +import { getRangePickerDefaultProps } from '#/utils'; +import { WmsMerchantSelect } from '#/views/wms/md/merchant/components'; +import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components'; +import { + formatPrice, + formatQuantity, + formatSumPrice, + formatSumQuantity, + PRICE_PRECISION, + QUANTITY_PRECISION, +} from '#/views/wms/utils/format'; + +/** 表单类型 */ +export type FormType = 'create' | 'update'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入入库单号', + }, + fieldName: 'no', + label: '入库单号', + }, + { + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入业务单号', + }, + fieldName: 'bizOrderNo', + label: '业务单号', + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_ORDER_STATUS, 'number'), + placeholder: '请选择单据状态', + }, + fieldName: 'status', + label: '单据状态', + }, + { + component: markRaw(WmsWarehouseSelect), + fieldName: 'warehouseId', + label: '仓库', + }, + { + component: markRaw(WmsMerchantSelect), + componentProps: { + placeholder: '请选择供应商', + supplier: true, + }, + fieldName: 'merchantId', + label: '供应商', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'orderTime', + label: '单据日期', + }, + buildNumberRangeSchema( + '数量', + 'totalQuantityRange', + 'totalQuantityMin', + 'totalQuantityMax', + QUANTITY_PRECISION, + ), + buildNumberRangeSchema( + '总金额', + 'totalPriceRange', + 'totalPriceMin', + 'totalPriceMax', + PRICE_PRECISION, + ), + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_RECEIPT_ORDER_TYPE, 'number'), + placeholder: '请选择入库类型', + }, + fieldName: 'type', + label: '入库类型', + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择创建用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'creator', + label: '创建用户', + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择更新用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'updater', + label: '更新用户', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'createTime', + label: '创建时间', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'updateTime', + label: '更新时间', + }, + ]; +} + +/** 列表表格列 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + fixed: 'left', + slots: { content: 'expand_content' }, + type: 'expand', + width: 48, + }, + { + field: 'no', + fixed: 'left', + slots: { default: 'no' }, + title: '单号/业务单号', + width: 260, + }, + { + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_ORDER_STATUS }, + }, + field: 'status', + fixed: 'left', + title: '入库状态', + width: 110, + }, + { + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_RECEIPT_ORDER_TYPE }, + }, + field: 'type', + title: '入库类型', + width: 120, + }, + { + field: 'warehouseName', + minWidth: 180, + title: '仓库', + }, + { + field: 'quantityAmount', + minWidth: 180, + slots: { default: 'quantityAmount' }, + title: '总数量/总金额(元)', + }, + { + field: 'merchantName', + minWidth: 160, + title: '供应商', + }, + { + field: 'operateInfo', + minWidth: 260, + slots: { default: 'operateInfo' }, + title: '操作信息', + }, + { + field: 'remark', + minWidth: 160, + title: '备注', + }, + { + field: 'actions', + fixed: 'right', + slots: { default: 'actions' }, + title: '操作', + width: 220, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '入库单号', + render: (val) => val || '-', + }, + { + field: 'type', + label: '入库类型', + render: (val) => + val === undefined || val === null + ? '-' + : h(DictTag, { + type: DICT_TYPE.WMS_RECEIPT_ORDER_TYPE, + value: val, + }), + }, + { + field: 'warehouseName', + label: '仓库', + render: (val) => val || '-', + }, + { + field: 'status', + label: '单据状态', + render: (val) => + val === undefined || val === null + ? '-' + : h(DictTag, { + type: DICT_TYPE.WMS_ORDER_STATUS, + value: val, + }), + }, + { + field: 'orderTime', + label: '单据日期', + render: (val) => formatDate(val, 'YYYY-MM-DD') || '-', + }, + { + field: 'merchantName', + label: '供应商', + render: (val) => val || '-', + }, + { + field: 'bizOrderNo', + label: '业务单号', + render: (val) => val || '-', + }, + { + field: 'totalQuantity', + label: '总数量', + render: (val) => formatQuantity(val) || '-', + }, + { + field: 'totalPrice', + label: '总金额', + render: (val) => formatPrice(val) || '-', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'creatorName', + label: '创建人', + render: (val, data) => val || data?.creator || '-', + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'updaterName', + label: '更新人', + render: (val, data) => val || data?.updater || '-', + }, + { + field: 'remark', + label: '备注', + render: (val) => val || '-', + span: 2, + }, + ]; +} + +/** 表单的配置项 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + fieldName: 'id', + }, + { + component: 'Input', + componentProps: { + maxLength: 64, + placeholder: '请输入入库单号', + }, + fieldName: 'no', + label: '入库单号', + rules: z.string().min(1, '入库单号不能为空').max(64), + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_RECEIPT_ORDER_TYPE, 'number'), + placeholder: '请选择入库类型', + }, + fieldName: 'type', + label: '入库类型', + rules: 'required', + }, + { + component: markRaw(WmsWarehouseSelect), + fieldName: 'warehouseId', + label: '仓库', + rules: 'required', + }, + { + component: 'DatePicker', + componentProps: { + class: 'w-full', + format: 'YYYY-MM-DD', + placeholder: '请选择单据日期', + valueFormat: 'x', + }, + fieldName: 'orderTime', + label: '单据日期', + rules: 'required', + }, + { + component: markRaw(WmsMerchantSelect), + componentProps: { + placeholder: '请选择供应商', + supplier: true, + }, + fieldName: 'merchantId', + label: '供应商', + }, + { + component: 'Input', + componentProps: { + maxLength: 64, + placeholder: '请输入业务单号', + }, + fieldName: 'bizOrderNo', + label: '业务单号', + }, + { + component: 'Textarea', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + }, + fieldName: 'remark', + formItemClass: 'col-span-2', + label: '备注', + }, + ]; +} + +interface ReceiptOrderDetailFooterRow { + quantity?: number; + totalPrice?: number; +} +type ReceiptOrderDetailFooterColumn = Pick< + NonNullable[number]>, + 'field' +>; + +/** 明细表格的合计行 */ +export function getDetailFooter({ + columns, + data, +}: { + columns: ReceiptOrderDetailFooterColumn[]; + data: ReceiptOrderDetailFooterRow[]; +}) { + return [ + columns.map((column, index) => { + if (index === 0) { + return '合计'; + } + if (column.field === 'quantity') { + return formatSumQuantity(data, (detail) => detail.quantity); + } + if (column.field === 'totalPrice') { + return formatSumPrice(data, (detail) => detail.totalPrice); + } + return ''; + }), + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/order/receipt/index.vue b/apps/web-antdv-next/src/views/wms/order/receipt/index.vue new file mode 100644 index 000000000..c11d4047c --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/receipt/index.vue @@ -0,0 +1,352 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/receipt/modules/detail.vue b/apps/web-antdv-next/src/views/wms/order/receipt/modules/detail.vue new file mode 100644 index 000000000..434e2fe79 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/receipt/modules/detail.vue @@ -0,0 +1,122 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/receipt/modules/form.vue b/apps/web-antdv-next/src/views/wms/order/receipt/modules/form.vue new file mode 100644 index 000000000..e5832cf0d --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/receipt/modules/form.vue @@ -0,0 +1,476 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/receipt/modules/print.vue b/apps/web-antdv-next/src/views/wms/order/receipt/modules/print.vue new file mode 100644 index 000000000..e8e1d323f --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/receipt/modules/print.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/apps/web-antdv-next/src/views/wms/order/shipment/data.ts b/apps/web-antdv-next/src/views/wms/order/shipment/data.ts new file mode 100644 index 000000000..afcc3c980 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/shipment/data.ts @@ -0,0 +1,440 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { WmsWarehouseApi } from '#/api/wms/md/warehouse'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h, markRaw } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDate, formatDateTime } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { DictTag } from '#/components/dict-tag'; +import { buildNumberRangeSchema } from '#/components/number-range-input'; +import { getRangePickerDefaultProps } from '#/utils'; +import { WmsMerchantSelect } from '#/views/wms/md/merchant/components'; +import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components'; +import { + formatPrice, + formatQuantity, + formatSumPrice, + formatSumQuantity, + PRICE_PRECISION, + QUANTITY_PRECISION, +} from '#/views/wms/utils/format'; + +/** 表单类型 */ +export type FormType = 'create' | 'update'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入出库单号', + }, + fieldName: 'no', + label: '出库单号', + }, + { + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入业务单号', + }, + fieldName: 'bizOrderNo', + label: '业务单号', + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_ORDER_STATUS, 'number'), + placeholder: '请选择单据状态', + }, + fieldName: 'status', + label: '单据状态', + }, + { + component: markRaw(WmsWarehouseSelect), + fieldName: 'warehouseId', + label: '仓库', + }, + { + component: markRaw(WmsMerchantSelect), + componentProps: { + customer: true, + placeholder: '请选择客户', + }, + fieldName: 'merchantId', + label: '客户', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'orderTime', + label: '单据日期', + }, + buildNumberRangeSchema( + '数量', + 'totalQuantityRange', + 'totalQuantityMin', + 'totalQuantityMax', + QUANTITY_PRECISION, + ), + buildNumberRangeSchema( + '总金额', + 'totalPriceRange', + 'totalPriceMin', + 'totalPriceMax', + PRICE_PRECISION, + ), + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE, 'number'), + placeholder: '请选择出库类型', + }, + fieldName: 'type', + label: '出库类型', + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择创建用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'creator', + label: '创建用户', + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择更新用户', + showSearch: true, + valueField: 'id', + }, + fieldName: 'updater', + label: '更新用户', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'createTime', + label: '创建时间', + }, + { + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + fieldName: 'updateTime', + label: '更新时间', + }, + ]; +} + +/** 列表表格列 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + fixed: 'left', + slots: { content: 'expand_content' }, + type: 'expand', + width: 48, + }, + { + field: 'no', + fixed: 'left', + slots: { default: 'no' }, + title: '单号/业务单号', + width: 260, + }, + { + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_ORDER_STATUS }, + }, + field: 'status', + fixed: 'left', + title: '出库状态', + width: 110, + }, + { + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE }, + }, + field: 'type', + title: '出库类型', + width: 120, + }, + { + field: 'warehouseName', + minWidth: 180, + title: '仓库', + }, + { + field: 'quantityAmount', + minWidth: 180, + slots: { default: 'quantityAmount' }, + title: '总数量/总金额(元)', + }, + { + field: 'merchantName', + minWidth: 160, + title: '客户', + }, + { + field: 'operateInfo', + minWidth: 260, + slots: { default: 'operateInfo' }, + title: '操作信息', + }, + { + field: 'remark', + minWidth: 160, + title: '备注', + }, + { + field: 'actions', + fixed: 'right', + slots: { default: 'actions' }, + title: '操作', + width: 220, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '出库单号', + render: (val) => val || '-', + }, + { + field: 'type', + label: '出库类型', + render: (val) => + val === undefined || val === null + ? '-' + : h(DictTag, { + type: DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE, + value: val, + }), + }, + { + field: 'warehouseName', + label: '仓库', + render: (val) => val || '-', + }, + { + field: 'status', + label: '单据状态', + render: (val) => + val === undefined || val === null + ? '-' + : h(DictTag, { + type: DICT_TYPE.WMS_ORDER_STATUS, + value: val, + }), + }, + { + field: 'orderTime', + label: '单据日期', + render: (val) => formatDate(val, 'YYYY-MM-DD') || '-', + }, + { + field: 'merchantName', + label: '客户', + render: (val) => val || '-', + }, + { + field: 'bizOrderNo', + label: '业务单号', + render: (val) => val || '-', + }, + { + field: 'totalQuantity', + label: '总数量', + render: (val) => formatQuantity(val) || '-', + }, + { + field: 'totalPrice', + label: '总金额', + render: (val) => formatPrice(val) || '-', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'creatorName', + label: '创建人', + render: (val, data) => val || data?.creator || '-', + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) || '-', + }, + { + field: 'updaterName', + label: '更新人', + render: (val, data) => val || data?.updater || '-', + }, + { + field: 'remark', + label: '备注', + render: (val) => val || '-', + span: 2, + }, + ]; +} + +interface ShipmentFormSchemaOptions { + onWarehouseChange: (warehouse?: WmsWarehouseApi.Warehouse) => void; +} + +/** 表单的配置项 */ +export function useFormSchema({ + onWarehouseChange, +}: ShipmentFormSchemaOptions): VbenFormSchema[] { + return [ + { + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + fieldName: 'id', + }, + { + component: 'Input', + componentProps: { + maxLength: 64, + placeholder: '请输入出库单号', + }, + fieldName: 'no', + label: '出库单号', + rules: z.string().min(1, '出库单号不能为空').max(64), + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE, 'number'), + placeholder: '请选择出库类型', + }, + fieldName: 'type', + label: '出库类型', + rules: 'required', + }, + { + component: markRaw(WmsWarehouseSelect), + componentProps: { + onChange: onWarehouseChange, + }, + fieldName: 'warehouseId', + label: '仓库', + rules: 'required', + }, + { + component: 'DatePicker', + componentProps: { + class: 'w-full', + format: 'YYYY-MM-DD', + placeholder: '请选择单据日期', + valueFormat: 'x', + }, + fieldName: 'orderTime', + label: '单据日期', + rules: 'required', + }, + { + component: markRaw(WmsMerchantSelect), + componentProps: { + customer: true, + placeholder: '请选择客户', + }, + fieldName: 'merchantId', + label: '客户', + }, + { + component: 'Input', + componentProps: { + maxLength: 64, + placeholder: '请输入业务单号', + }, + fieldName: 'bizOrderNo', + label: '业务单号', + }, + { + component: 'Textarea', + componentProps: { + maxLength: 255, + placeholder: '请输入备注', + }, + fieldName: 'remark', + formItemClass: 'col-span-2', + label: '备注', + }, + ]; +} + +interface ShipmentOrderDetailFooterRow { + quantity?: number; + totalPrice?: number; +} +type ShipmentOrderDetailFooterColumn = Pick< + NonNullable[number]>, + 'field' +>; + +/** 明细表格的合计行 */ +export function getDetailFooter({ + columns, + data, +}: { + columns: ShipmentOrderDetailFooterColumn[]; + data: ShipmentOrderDetailFooterRow[]; +}) { + return [ + columns.map((column, index) => { + if (index === 0) { + return '合计'; + } + if (column.field === 'quantity') { + return formatSumQuantity(data, (detail) => detail.quantity); + } + if (column.field === 'totalPrice') { + return formatSumPrice(data, (detail) => detail.totalPrice); + } + return ''; + }), + ]; +} diff --git a/apps/web-antdv-next/src/views/wms/order/shipment/index.vue b/apps/web-antdv-next/src/views/wms/order/shipment/index.vue new file mode 100644 index 000000000..dbefe406b --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/shipment/index.vue @@ -0,0 +1,352 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/shipment/modules/detail.vue b/apps/web-antdv-next/src/views/wms/order/shipment/modules/detail.vue new file mode 100644 index 000000000..9d8251b7c --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/shipment/modules/detail.vue @@ -0,0 +1,122 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/shipment/modules/form.vue b/apps/web-antdv-next/src/views/wms/order/shipment/modules/form.vue new file mode 100644 index 000000000..b0cf6ca38 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/shipment/modules/form.vue @@ -0,0 +1,537 @@ + + + diff --git a/apps/web-antdv-next/src/views/wms/order/shipment/modules/print.vue b/apps/web-antdv-next/src/views/wms/order/shipment/modules/print.vue new file mode 100644 index 000000000..e8faa62a9 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/order/shipment/modules/print.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/apps/web-antdv-next/src/views/wms/utils/format.ts b/apps/web-antdv-next/src/views/wms/utils/format.ts new file mode 100644 index 000000000..8235e9cd7 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/utils/format.ts @@ -0,0 +1,161 @@ +/** + * WMS 展示格式化和表单配置 + */ + +type DecimalValue = null | number | string | undefined; + +/** 数量小数位 */ +export const QUANTITY_PRECISION = 2; +/** 金额小数位 */ +export const PRICE_PRECISION = 2; +/** 重量小数位 */ +export const WEIGHT_PRECISION = 3; +/** 长宽高小数位 */ +export const DIMENSION_PRECISION = 1; + +function isNullOrUndefined(value: unknown) { + return value === null || value === undefined; +} + +function toFiniteDecimal(value: DecimalValue) { + if (isNullOrUndefined(value)) { + return undefined; + } + if (typeof value === 'string' && value.trim() === '') { + return undefined; + } + const decimalValue = typeof value === 'string' ? Number(value) : value; + if (!Number.isFinite(decimalValue)) { + return undefined; + } + return decimalValue; +} + +function sumDecimal(list: T[], getter: (item: T) => DecimalValue) { + let sum = 0; + for (const item of list) { + const decimalValue = toFiniteDecimal(getter(item)); + if (decimalValue !== undefined) { + sum += decimalValue; + } + } + return sum; +} + +/** 格式化数量 */ +export function formatQuantity(value?: null | number | string) { + const decimalValue = toFiniteDecimal(value); + return decimalValue === undefined + ? '' + : decimalValue.toFixed(QUANTITY_PRECISION); +} + +/** 格式化金额 */ +export function formatPrice(value?: null | number | string) { + const decimalValue = toFiniteDecimal(value); + return decimalValue === undefined + ? '' + : decimalValue.toFixed(PRICE_PRECISION); +} + +/** 金额四舍五入 */ +export function roundPrice(value: number) { + return Number.isFinite(value) + ? Number(value.toFixed(PRICE_PRECISION)) + : undefined; +} + +/** 亏损数字样式 */ +export function getLossClass(value?: null | number | string) { + const decimalValue = toFiniteDecimal(value); + return decimalValue !== undefined && decimalValue < 0 ? 'text-red-500' : ''; +} + +/** 数量 * 单价,计算金额 */ +export function multiplyPrice(quantity?: number, price?: number) { + if ( + quantity === undefined || + quantity === null || + price === undefined || + price === null + ) { + return undefined; + } + return roundPrice(Number(quantity) * Number(price)); +} + +/** 金额 / 数量,反算单价 */ +export function dividePrice(totalPrice?: number, quantity?: number) { + if (totalPrice === undefined || totalPrice === null || !quantity) { + return undefined; + } + return roundPrice(Number(totalPrice) / Number(quantity)); +} + +/** 汇总数量 */ +export function sumQuantity(list: T[], getter: (item: T) => DecimalValue) { + return sumDecimal(list, getter); +} + +/** 汇总金额 */ +export function sumPrice(list: T[], getter: (item: T) => DecimalValue) { + return sumDecimal(list, getter); +} + +/** 格式化汇总数量 */ +export function formatSumQuantity( + list: T[], + getter: (item: T) => DecimalValue, +) { + return formatQuantity(sumQuantity(list, getter)); +} + +/** 格式化汇总金额 */ +export function formatSumPrice( + list: T[], + getter: (item: T) => DecimalValue, +) { + return formatPrice(sumPrice(list, getter)); +} + +/** 格式化重量 */ +export function formatWeight(value?: null | number | string) { + const decimalValue = toFiniteDecimal(value); + return decimalValue === undefined + ? '' + : decimalValue.toFixed(WEIGHT_PRECISION); +} + +/** 格式化长宽高 */ +export function formatDimension(value?: null | number | string) { + const decimalValue = toFiniteDecimal(value); + return decimalValue === undefined + ? '' + : decimalValue.toFixed(DIMENSION_PRECISION); +} + +/** 格式化长宽高组合 */ +export function formatDimensionText( + length?: null | number | string, + width?: null | number | string, + height?: null | number | string, +) { + if ( + !isNullOrUndefined(length) && + !isNullOrUndefined(width) && + !isNullOrUndefined(height) + ) { + return [ + formatDimension(length), + formatDimension(width), + formatDimension(height), + ].join(' * '); + } + return [ + isNullOrUndefined(length) ? undefined : `长:${formatDimension(length)}`, + isNullOrUndefined(width) ? undefined : `宽:${formatDimension(width)}`, + isNullOrUndefined(height) ? undefined : `高:${formatDimension(height)}`, + ] + .filter(Boolean) + .join(' '); +} diff --git a/apps/web-antdv-next/src/views/wms/utils/order.ts b/apps/web-antdv-next/src/views/wms/utils/order.ts new file mode 100644 index 000000000..5f31136c3 --- /dev/null +++ b/apps/web-antdv-next/src/views/wms/utils/order.ts @@ -0,0 +1,12 @@ +/** + * WMS 订单工具 + */ + +/** 生成业务单号:前缀 + 月日 + 4 位随机数 */ +export function generateOrderNo(prefix: string) { + const now = new Date(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const randomNo = String(Math.floor(Math.random() * 10_000)).padStart(4, '0'); + return `${prefix}${month}${day}${randomNo}`; +}