Commit 6f560892 by zhuzhequan

Merge branch 'release' into dev_ware_in

# Conflicts:
#	src/views/order/podCN/index.vue
#	src/views/warehouse/issueDoc.vue
#	src/views/warehouse/receiptDoc.vue
#	src/views/warehouse/warning.vue
parents 117516e6 e737142b
......@@ -12,6 +12,7 @@
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.7",
"bignumber": "^1.1.0",
"bignumber.js": "^9.3.0",
"dayjs": "^1.11.13",
"echarts": "^6.0.0",
......@@ -3171,6 +3172,14 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/bignumber": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bignumber/-/bignumber-1.1.0.tgz",
"integrity": "sha512-EGqHCKkEAwVwufcEOCYhZQqdVH+7cNCyPZ9yxisYvSjHFB+d9YcGMvorsFpeN5IJpC+lC6K+FHhu8+S4MgJazw==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/bignumber.js": {
"version": "9.3.0",
"resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.0.tgz",
......
......@@ -14,6 +14,7 @@
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.7",
"bignumber": "^1.1.0",
"bignumber.js": "^9.3.0",
"dayjs": "^1.11.13",
"echarts": "^6.0.0",
......
......@@ -5,6 +5,8 @@ import { LogisticBill } from '@/types/api/podMakeOrder'
import { userData } from '@/types/api/user'
import { VersionImageList } from '@/types/api/typesetting'
import { SupplierItem, WarehouseListData } from '@/types'
import { loactionData } from './warehouse'
// 获取物流公司
export function getLogisticsCompanyList() {
......@@ -49,3 +51,38 @@ export function getLogisticCNApi(content: string) {
`factory/podJomallOrder/getOrderByThirdSubOrderNumber?thirdSubOrderNumber=${content}`,
)
}
// 获取仓库列表
export function loadWarehouseListApi() {
return axios.get<never, BaseRespData<WarehouseListData[]>>(
'factoryWarehouseInfo/getAll',
)
}
// 获取用户列表
export function getEmployeeListApi() {
return axios.get<never, BaseRespData<userData[]>>(`/factory/factoryUser/list`)
}
// 获取供应商列表
export function getSupplierListApi() {
return axios.get<never, BaseRespData<SupplierItem[]>>(
`/factory/supplier/list`,
)
}
// 获取币种接口
export function getBaseCurrencyInfoApi() {
return axios.get<never, BaseRespData<never>>(
'factory/supplier/getBaseCurrencyInfo',
)
}
// 获取库位List
export function LocationInfoGetAll(wareHouseId?: string | number) {
return axios.get<never, BaseRespData<loactionData[]>>(
'/factoryWarehouseLocation/getByWareHouse',
{
params: { wareHouseId },
},
)
}
import axios from './axios'
import { BasePaginationData, BaseRespData } from '@/types/api'
import { SearchForm, OutOfStockItem } from '@/types/api/outOfStockStatistics'
import { SearchForm, OutOfStockItem } from '@/types/api/supply/outOfStockStatistics'
export function getOutOfStockStatisticsListApi(
data: SearchForm,
currentPage: number,
......
......@@ -5,7 +5,6 @@ import {
SearchForm,
Tab,
LogListData,
WarehouseListData,
LogisticsData,
ExportParams,
IconfirmSubmit,
......@@ -39,10 +38,6 @@ export function syncReceiverAddress(data: number[]) {
)
}
export function getEmployeeListApi() {
return axios.get(`/factory/factoryUser/list`)
}
// 播种墙配货 扫码放入箱子
export function getPackingCnDataApi(
code: string,
......@@ -419,11 +414,7 @@ export function getLogisticsCalculation(id: number) {
{ params: { id } },
)
}
export function loadWarehouseListApi() {
return axios.get<never, BaseRespData<WarehouseListData[]>>(
'factoryWarehouseInfo/getAll',
)
}
export function refreshMaterialApi(data: {
orderIds?: string
productIds?: string
......
......@@ -6,7 +6,6 @@ import {
Tab,
LogListData,
ProductionClient,
WarehouseListData,
LogisticsData,
ExportParams,
InterceptStateGroupData,
......@@ -310,11 +309,6 @@ export function getLogisticsCalculation(id: number) {
{ params: { id } },
)
}
export function loadWarehouseListApi() {
return axios.get<never, BaseRespData<WarehouseListData[]>>(
'factoryWarehouseInfo/getAll',
)
}
export function refreshMaterialApi(data: {
orderIds?: string
productIds?: string
......@@ -640,11 +634,6 @@ export function getAccountCodeByFactoryIdApi(params: { token: string }) {
export function getLogisticsWayApi() {
return axios.get(`logisticsWay/usableAllList`)
}
export function getEmployeeListApi() {
return axios.get(`/factory/factoryUser/list`)
}
// 打印拣货单item
export function printPickPdfByBatchNumberApi(params: {
batchArrangeNumber: string
......
import axios from '../axios'
import { BasePaginationData, BaseRespData } from '@/types/api'
import {
SearchForm,
TableData,
TreeData,
AddStockingOrderForm,
StockingOrderProduct,
InternalMemoList,
RelatedDocumentList,
LogListData,
} from '@/types/api/supply/stockingOrder'
export function getStockingOrderListApi(
data: SearchForm,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<TableData>>(
'factory/supply/stockingUpManage/list_page',
{ ...data, currentPage, pageSize },
)
}
export function getStockingOrderStatusTreeApi() {
return axios.get<never, BaseRespData<TreeData[]>>(
'factory/supply/stockingUpManage/getStatusTree',
)
}
// 新增备货单
export function addStockingOrderApi(url: string, data: AddStockingOrderForm) {
return axios.post<never, BaseRespData<never>>(url, data)
}
// 根据父SKU查询商品信息
export function getProductBySkuApi(spu: string, supplierId: number) {
return axios.get<never, BaseRespData<StockingOrderProduct[]>>(
'factory/supply/stockingUpManage/getCustomProductItem',
{
params: { productItemSku: spu, supplierId },
},
)
}
export function getStockingOrderDetailByIdApi(id: number) {
return axios.get<never, BaseRespData<AddStockingOrderForm>>(
`factory/supply/stockingUpManage/get?id=${id}`,
)
}
export function getStockingOrderDetailListByIdApi(id: number) {
return axios.get<never, BaseRespData<StockingOrderProduct[]>>(
`factory/supply/stockingUpManage/getManageDetailsList?id=${id}`,
)
}
export function getStockingOrderRelatedDocumentListByIdApi(id: number) {
return axios.get<never, BaseRespData<RelatedDocumentList[]>>(
`factory/supply/stockingUpManage/getWarehouseApplyList?id=${id}`,
)
}
export function getStockingOrderInternalMemoListByIdApi(id: number) {
return axios.get<never, BaseRespData<InternalMemoList[]>>(
`factory/supply/stockingUpManage/getInternalMemoList?id=${id}`,
)
}
export function getStockingOrderLogListByIdApi(id: number) {
return axios.get<never, BaseRespData<LogListData[]>>(
`factory/supply/stockingUpManageLog/getStockingUpManageLog?manageId=${id}`,
)
}
export function submitStockingOrderAuditApi(ids: string) {
return axios.get<never, BaseRespData<void>>(
'factory/supply/stockingUpManage/submission',
{
params: { ids },
},
)
}
export function rejectedStockingOrderApi(params: {
id: number | string
turnDownReason: string
}) {
return axios.get<never, BaseRespData<void>>(
'factory/supply/stockingUpManage/rejected',
{
params,
},
)
}
export function supplierDispatchApi(data: {
manageId: number | string
manageNo: string
warehouseId: number | string
warehouseName: string
expectDeliveryTime: string
currencyCode: string
detailsList: StockingOrderProduct[]
}) {
return axios.post<never, BaseRespData<void>>(
'factory/supply/stockingUpWarehouseApply/add',
data,
)
}
export function stockingCompleteApi(id: number) {
return axios.get<never, BaseRespData<void>>(
`factory/supply/stockingUpManage/delivery?id=${id}`,
)
}
export function deleteStockingOrderApi(id: string | number) {
return axios.get<never, BaseRespData<void>>(
`factory/supply/stockingUpManage/delete`,
{
params: { id },
},
)
}
export function cancelStockingOrderApi(
id: string | number,
cancelReason: string,
) {
return axios.get<never, BaseRespData<void>>(
`factory/supply/stockingUpManage/cancel`,
{
params: { id, cancelReason },
},
)
}
export function addStockingOrderInternalTagApi(memo: string, idList: number[]) {
return axios.post<never, BaseRespData<void>>(
`factory/supply/stockingUpManage/batchAddInternalMemo`,
{
content: memo,
idList,
},
)
}
......@@ -45,12 +45,7 @@ export function getProductInfoBySpuApi(spu: string | number) {
},
)
}
// 获取币种接口
export function getBaseCurrencyInfoApi() {
return axios.get<never, BaseRespData<never>>(
'factory/supplier/getBaseCurrencyInfo',
)
}
//新增
export function addSupplierApi(params: IsupplierType) {
return axios.post<never, BaseRespData<never>>('/factory/supplier/add', params)
......
......@@ -284,14 +284,6 @@ export function warehouseInfoGetAll() {
'/factoryWarehouseInfo/getAll',
)
}
export function LocationInfoGetAll(wareHouseId?: string | number) {
return axios.get<never, BaseRespData<loactionData[]>>(
'/factoryWarehouseLocation/getByWareHouse',
{
params: { wareHouseId },
},
)
}
export function createWarehouseInventoryApi(data: WarehouseWarningData) {
return axios.post<never, BaseRespData<never>>(
......
import axios from '@/api/axios'
import { BasePaginationData, BaseRespData } from '@/types/api'
import { LogListData } from '@/types/api/supply/stockingOrder'
import {
SearchForm,
TableData,
TreeData,
StockingApplyOrderDetailList,
RelatedDocumentList,
StockingApplyOrderDetailData,
LocationDataBySkuData,
} from '@/types/api/warehouse/stockingApplyOrder'
export function getStockingApplyOrderListApi(
data: SearchForm,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<TableData>>(
'factory/supply/stockingUpWarehouseApply/list_page',
{ ...data, currentPage, pageSize },
)
}
export function getStockingApplyOrderTreeApi() {
return axios.get<never, BaseRespData<TreeData[]>>(
'factory/supply/stockingUpWarehouseApply/getStatusTree',
)
}
export function getStockingApplyOrderDetailListByIdApi(id: number) {
return axios.get<never, BaseRespData<StockingApplyOrderDetailList[]>>(
`factory/supply/stockingUpWarehouseApply/getWarehouseApplyList?id=${id}`,
)
}
export function getStockingApplyOrderRelatedDocumentListByIdApi(
warehouseApplyNo: string,
) {
return axios.get<never, BaseRespData<RelatedDocumentList[]>>(
`factory/supply/stockingUpWarehouseApply/getWarehouseInList?warehouseApplyNo=${warehouseApplyNo}`,
)
}
export function getStockingApplyOrderLogListByIdApi(id: number) {
return axios.get<never, BaseRespData<LogListData[]>>(
`factory/supply/stockingUpWarehouseApplyLog/getWarehouseApplyLog?warehouseApplyId=${id}`,
)
}
export function warehouseReceiptApi(id: number) {
return axios.get<never, BaseRespData<void>>(
`factory/supply/stockingUpWarehouseApply/receiving?id=${id}`,
)
}
export function submitWarehousingApi(data: StockingApplyOrderDetailData) {
return axios.post<never, BaseRespData<void>>(
'factory/supply/stockingUpWarehouseApply/submitInventory',
data,
)
}
export function getStockingApplyOrderDetailById(id: number) {
return axios.get<never, BaseRespData<StockingApplyOrderDetailData>>(
`factory/supply/stockingUpWarehouseApply/get?id=${id}`,
)
}
export function getLocationListApi(sku: string, warehouseId: string | number) {
return axios.post<never, BaseRespData<LocationDataBySkuData[]>>(
'factoryWarehouseInventory/getBySkuAndWarehouseId',
{
sku,
warehouseId,
},
)
}
......@@ -3,6 +3,7 @@
<ElTable
ref="tableRef"
:data="paginatedData"
show-overflow-tooltip
border
:stripe="stripe"
header-align="center"
......
......@@ -167,7 +167,14 @@ const router = createRouter({
meta: {
title: '缺货统计',
},
component: () => import('@/views/supply/OutOfStockStatistics.vue')
component: () => import('@/views/supply/OutOfStockStatistics.vue'),
},
{
path: '/supply/stocking-order',
meta: {
title: '备货订单',
},
component: () => import('@/views/supply/stockingOrder/index.vue'),
},
{
path: '/system/delivery-note',
......@@ -296,6 +303,14 @@ const router = createRouter({
component: stockingPlan,
},
{
path: '/warehouse/stocking-application',
meta: {
title: '入库申请单',
},
component: () =>
import('@/views/warehouse/stockingApplicationOrder/index.vue'),
},
{
path: '/warehouse/warning',
meta: {
title: '仓库预警',
......
......@@ -11,11 +11,6 @@ const menu: MenuItem[] = [
id: 1,
label: '概览',
},
// {
// index: '/product',
// id: 11,
// label: '商品',
// },
{
index: '4',
id: 7,
......@@ -74,6 +69,11 @@ const menu: MenuItem[] = [
label: '库存',
children: [
{
index: '/warehouse/stocking-application',
id: 126,
label: '入库申请单',
},
{
index: '/warehouse/warning',
id: 125,
label: '仓库预警',
......@@ -159,8 +159,13 @@ const menu: MenuItem[] = [
id: 1,
},
{
index: '/supply/supplierManagement',
label: '备货订单',
index: '/supply/stocking-order',
id: 2,
},
{
index: '/supply/supplierManagement',
id: 3,
label: '供应商管理',
},
],
......@@ -238,17 +243,5 @@ const menu: MenuItem[] = [
},
],
},
// {
// index: '',
// id: 3,
// label: '生产管理',
// children: [
// {
// index: '/production/complete',
// id: 4,
// label: '生产完成',
// }
// ]
// },
]
export default menu
......@@ -116,4 +116,4 @@ img {
}
.mb-10 {
margin-bottom: 10px;
}
}
\ No newline at end of file
......@@ -32,6 +32,15 @@
color: #409eff !important;
}
}
.el-form-item {
margin-right: 10px !important;
}
:root {
--el-menu-icon-width: 0px;
}
.el-loading-mask.is-fullscreen {
z-index: 5000 !important;
}
.el-loading-mask {
z-index: 1020 !important;
}
\ No newline at end of file
......@@ -55,6 +55,7 @@ export interface SearchForm {
employeeId?: number
blocking?: boolean
outOfStock?: boolean
receiverCountry?: string
}
export interface PodCnOrderListData {
id: number
......
......@@ -194,16 +194,7 @@ export interface PodOrderRes extends ProductList {
note?: Array<{ prop: string | number; value: string | number }>
imgList: cardImages[]
}
export interface WarehouseListData {
code?: string
defaulted?: number
factoryCode?: string
factoryId?: number
id: number
name?: string
remarks?: string
sort?: number
}
export interface LogisticsData {
logisticsWayName: string // 物流名称
warehouseName: string // 发货仓库
......
......@@ -27,11 +27,17 @@ export interface statisticData {
outOfStockSkuNum: number
outOfStockProductNum: number
yesterdayOverTimeShipmentOrderNum: number
notShipmentProductNum: number
overTimeNotShipmentProductNum: number
yesterdayShipmentProductNum: number
}
export interface trendType {
confirmNum: number // 接单数
produceNum: number // 生产数
shipmentNum: number // 发货数
confirmProductNum?: number // 新接数(件)
produceProductNum?: number // 生产数(件)
shipmentProductNum?: number // 发货数(件)
shipmentRateOf24Hour: number // 24小时发货率
shipmentRateOf48Hour: number // 48小时发货率
overtimeShipmentRate: number // 超48小时发货率
......
export interface TreeData {
name: string
code: string
children?: TreeData[]
countQuantity?: number
id?: number
}
export interface SearchForm {
currentPage: number
endDate?: string
pageSize: number
shippingStatusList?: string[]
startDate?: string
status?: string
stockingUpManageNo?: string
stockingUpUserId?: string[]
supplierId?: string[]
warehouseId?: string | number[]
warehouseSku?: string | number[]
warehouseSkuName?: string
remark?: string
}
export interface TableData {
id: number
factoryId?: number
factoryCode?: string
stockingUpManageNo?: string
currencyCode?: string
skuTotal?: number
total?: number
totalPrice?: number
buyStored?: number
storedPrice?: number
buyUnstored?: number
unstoragePrice?: number
shipmentQuantity?: number
rejectsAmount?: number
shipmentFreight?: number
expectDeliveryTime?: string
status?: string
supplierId?: number
supplierName?: string
warehouseId?: number
warehouseName?: string
stockingUpUserId?: number
stockingUpUserName?: string
createUserId?: number
createUserName?: string
createTime?: string
updateTime?: string
remark?: string
dataVersion?: number
shippingStatus?: number
delayDays?: number
lastDeliveryTime?: string
completeTime?: string
auditName?: string
auditTime?: string
}
export interface StockProduct {
skuImage?: string
productName?: string
styleNumber?: string
warehouseSku?: string
stockQuantity?: number
stockUnitPrice?: number
stockAmount?: number
unshippedQuantity?: number
shippedQuantity?: number
inWarehouseQuantity?: number
defectiveQuantity?: number
}
export interface AddStockingOrderForm {
id?: number | string
stockingUpManageNo?: string
supplierId?: number
supplierName?: string
stockingUpUserId?: number
stockingUpUserName?: string
warehouseId?: number
warehouseName?: string
expectDeliveryTime?: string
totalPrice?: string
currencyCode?: string
currencyName?: string
remark?: string
status?: number
submission?: boolean
detailsList?: StockingOrderProduct[]
examineStatus?: number // 审核状态:1通过,0驳回
rejectReason?: string // 驳回原因
}
export interface StockingOrderProduct {
productNo?: string
warehouseSkuId?: number
warehouseSku?: string
warehouseSkuName?: string
warehouseSkuImage?: string
buyAmount?: number | string
price?: number | string
currencyCode?: string
currencyName?: string
totalPrice?: number | string
shipmentQuantity?: number | string
buyStored?: number
// 供应商发货相关字段
remainingQuantity?: number // 剩余待发货数量
currentShipQuantity?: number | string // 本次发货数量
}
export interface RelatedDocumentList {
id: number
factoryId?: number
factoryCode?: string
manageId?: number
manageNo?: string
warehouseApplyNo?: string
warehouseId?: number
warehouseName?: string
skuTotal?: number
total?: number
buyStored?: number
rejectsAmount?: number
storeStatus?: string
expectDeliveryTime?: string
createUserId?: number
createUserName?: string
createTime?: string
updateTime?: string
dataVersion?: number
}
export interface InternalMemoList {
id: number
outId?: number
operatorEmployeeName?: string
operatorTime?: string
content?: string
remark?: string
type?: number
}
export interface LogListData {
id: number
manageId?: number
userId?: number
userName?: string
description?: string
updateTime?: string
}
export interface SearchForm {
warehouseApplyNo?: string
manageNo?: string
warehouseId?: string | number[]
warehouseSku?: string | number[]
warehouseSkuName?: string
currentPage: number
pageSize: number
storeStatus?: string
}
export interface TreeData {
id?: number
code?: string
name?: string
countQuantity?: number
children?: TreeData[]
leaf?: boolean
}
export interface TableData {
id: number
factoryId?: number
factoryCode?: string
manageId?: number
manageNo?: string
warehouseApplyNo: string
warehouseId?: number
warehouseName?: string
skuTotal?: number
total?: number
buyStored?: number
rejectsAmount?: number
storeStatus: string
expectDeliveryTime?: string
createUserId?: number
createUserName?: string
createTime?: string
updateTime?: string
dataVersion?: number
}
export interface StockingApplyOrderDetailList {
id: number
manageId?: number
warehouseApplyId?: number
warehouseApplyNo?: string
warehouseSku?: string
productNo?: string
warehouseSkuName?: string
warehouseSkuImage?: string
shipmentQuantity?: number
buyStored?: number | string
rejectsAmount?: number | string
price?: number
createTime?: string
updateTime?: string
locationId?: number
locationCode?: string | undefined
finallyShipmentQuantity?: number
storedPending?: number
}
export interface RelatedDocumentList {
id: number
dataVersion?: number
createTime?: string
updateTime?: string
factoryId?: number
factoryCode?: string
warehouseId?: number
warehouseName?: string
inNo?: string
skuAmount?: number
total?: number
totalPrice?: number
billStatus?: string
billStatusTxt?: string
makerName?: string
makerUserId?: number
makeTime?: string
checkerUserId?: number
checkerName?: string
source?: string
sourceOn?: string
}
export interface StockingApplyOrderDetailData {
id: number
warehouseApplyNo?: string
warehouseName?: string
warehouseId?: number
checkerUserId?: number
remark?: string
checkerName?: string
detailsList: StockingApplyOrderDetailList[]
}
export interface LocationDataBySkuData {
id: number
factoryId?: number
warehouseId?: number
warehouseName?: string
locationId?: number
locationCode?: string
warehouseSku?: string
customSku?: string
skuName?: string
createTime?: string
updateTime?: string
productItem?: unknown
usableInventory?: number
image?: string
price?: number
currencyCode?: string
currencyName?: string
sumOccupyInventory?: number
}
// 放系统中公共类型
export interface WarehouseListData {
code?: string
defaulted?: number
factoryCode?: string
factoryId?: number
id: number
name?: string
remarks?: string
sort?: number
}
export interface SupplierItem {
id: number
supplierName?: string
contacts?: string
contactsNumber?: string
address?: string
remark?: string
factoryId?: number
updateTime?: string
createTime?: string
supplierProductInfoList?: []
}
export interface CurrencyCodeData {
currencyName?: string
currencyCode?: string
id?: string
}
......@@ -50,11 +50,11 @@
</div>
<div class="card-item">
<div class="card-item-header">
<div class="card-title">未发货订单(订单)</div>
<div class="card-title">未发货订单(件/订单)</div>
<el-tooltip
class="item"
effect="light"
content="目前确认生产后还未转为已发货状态的总订单数"
content="目前确认生产后还未转为已发货状态的总件数/总订单数"
placement="bottom"
>
<div class="card-icon"></div>
......@@ -62,17 +62,21 @@
</div>
<div class="card-item-content-box">
<div v-auto-fit-text class="card-item-content text-blue">
{{ statisticData?.notShipmentOrderNum ?? '-' }}
{{
(statisticData?.notShipmentProductNum ?? '-') +
'/' +
(statisticData?.notShipmentOrderNum ?? '-')
}}
</div>
</div>
</div>
<div class="card-item">
<div class="card-item-header">
<div class="card-title">超时未发订单(订单)</div>
<div class="card-title">超时未发订单(件/订单)</div>
<el-tooltip
class="item"
effect="light"
content="目前确认生产后超过48H还未转为已发货状态的总订单数"
content="目前确认生产后超过48H还未转为已发货状态的总件数/总订单数"
placement="bottom"
>
<div class="card-icon"></div>
......@@ -80,7 +84,11 @@
</div>
<div class="card-item-content-box">
<div v-auto-fit-text class="card-item-content text-red">
{{ statisticData?.overTimeNotShipmentOrderNum ?? '-' }}
{{
(statisticData?.overTimeNotShipmentProductNum ?? '-') +
'/' +
(statisticData?.overTimeNotShipmentOrderNum ?? '-')
}}
</div>
</div>
</div>
......@@ -97,11 +105,11 @@
</template>
<div class="card-item">
<div class="card-item-header">
<div class="card-title">昨日发货数(订单)</div>
<div class="card-title">昨日发货数(件/订单)</div>
<el-tooltip
class="item"
effect="light"
:content="`昨日转为已发货状态的总订单数(${yesterday}北京时间 0-24点数据)`"
:content="`昨日转为已发货状态的总件数/总订单数(${yesterday}北京时间 0-24点数据)`"
placement="bottom"
>
<div class="card-icon"></div>
......@@ -109,7 +117,11 @@
</div>
<div class="card-item-content-box">
<div v-auto-fit-text class="card-item-content text-blue">
{{ statisticData?.yesterdayShipmentOrderNum ?? '-' }}
{{
(statisticData?.yesterdayShipmentProductNum ?? '-') +
'/' +
(statisticData?.yesterdayShipmentOrderNum ?? '-')
}}
</div>
<div class="card-item-content-text">
较前天
......@@ -121,7 +133,11 @@
style="display: flex; margin-left: 10px"
>
<div class="text-green">
{{ statisticData?.compareYesterdayShipmentOrderNum }}
{{
(statisticData?.compareYesterdayShipmentOrderNum).toFixed(
0,
) + '单'
}}
</div>
<div class="up-icon-green"></div>
</div>
......@@ -130,7 +146,7 @@
{{
Math.abs(
statisticData?.compareYesterdayShipmentOrderNum || 0,
)
).toFixed(0) + '单'
}}
</div>
<div class="down-icon-red"></div>
......@@ -541,27 +557,51 @@
</div>
</div>
</div>
<div class="chart-wrapper" style="width: 100%">
<el-radio-group
v-model="chartTimes1"
size="small"
class="chart-controls"
@change="getchartTimes($event, 'ORDER_TREND')"
>
<el-radio-button
v-for="time in timeOptions"
:key="time.type"
:value="time.type"
:label="time.name"
<div class="charts-row" style="width: 100%">
<div class="chart-wrapper chart-half">
<el-radio-group
v-model="chartTimes4"
size="small"
class="chart-controls"
@change="getchartTimes($event, 'PRODUCT_TREND')"
>
<el-radio-button
v-for="time in timeOptions"
:key="time.type"
:value="time.type"
:label="time.name"
>
</el-radio-button>
</el-radio-group>
<div
ref="orderChart4"
v-loading="orderChart4Loading"
class="chart-container"
style="width: 100%"
/>
</div>
<div class="chart-wrapper chart-half">
<el-radio-group
v-model="chartTimes1"
size="small"
class="chart-controls"
@change="getchartTimes($event, 'ORDER_TREND')"
>
</el-radio-button>
</el-radio-group>
<div
ref="orderChart1"
v-loading="orderChart1Loading"
class="chart-container"
style="width: 100%"
/>
<el-radio-button
v-for="time in timeOptions"
:key="time.type"
:value="time.type"
:label="time.name"
>
</el-radio-button>
</el-radio-group>
<div
ref="orderChart1"
v-loading="orderChart1Loading"
class="chart-container"
style="width: 100%"
/>
</div>
</div>
<div class="charts-row">
<div class="chart-wrapper chart-half">
......@@ -662,6 +702,7 @@ const statisticData = ref<StatisticData | null>(null)
const orderChart1Loading = ref<boolean>(true)
const orderChart2Loading = ref<boolean>(true)
const orderChart3Loading = ref<boolean>(true)
const orderChart4Loading = ref<boolean>(true)
const timeOptions = ref<{ type: string; name: string }[]>([
{ type: '0', name: '日数据' },
......@@ -672,14 +713,16 @@ const timeOptions = ref<{ type: string; name: string }[]>([
const chartTimes1 = ref<string>('0')
const chartTimes2 = ref<string>('0')
const chartTimes3 = ref<string>('0')
const chartTimes4 = ref<string>('0')
// 图表容器引用
const orderChart1 = ref<HTMLDivElement | null>(null)
const orderChart2 = ref<HTMLDivElement | null>(null)
const orderChart3 = ref<HTMLDivElement | null>(null)
const orderChart4 = ref<HTMLDivElement | null>(null)
// 图表实例数组
const chartInstances: (echarts.ECharts | null)[] = [null, null, null]
const chartInstances: (echarts.ECharts | null)[] = [null, null, null, null]
type ChartSeriesItem = {
name: string
......@@ -750,6 +793,29 @@ const chartConfig3 = ref<ChartConfig>({
],
})
const chartConfig4 = ref<ChartConfig>({
title: '订单趋势统计(件)',
legend: ['新接数', '生产数', '发货数'],
xAxisData: [''],
series: [
{
name: '新接数',
data: [0],
color: '#30d2cb',
},
{
name: '生产数',
data: [0],
color: '#9e95ff',
},
{
name: '发货数',
data: [0],
color: '#6bb3fd',
},
],
})
const overtimeTooltipFormatter = (params: TooltipParam[]) =>
params
.map((item) => {
......@@ -1045,11 +1111,35 @@ const getStatisticDataApi = async () => {
.catch((error) => {
console.error('获取超时发货趋势数据失败:', error)
})
// 订单趋势统计(件)
trendApi({
timeUnit: 0,
trendType: 'PRODUCT_TREND',
})
.then((res) => {
if (res.code === 200) {
const isEmpty = checkProductNumTrendDataEmpty(res.data)
// 无论数据是否为空,都要更新 x 轴数据
updateProductNumTrendChart(res.data, 0)
updateChart(
chartInstances[3],
chartConfig4,
isEmpty,
false,
chartTimes4.value,
)
orderChart4Loading.value = !(res.data && res.data.length > 0)
}
})
.catch((error) => {
console.error('获取订单趋势(件)数据失败:', error)
})
}
onMounted(async () => {
await nextTick()
// 订单趋势统计
// 订单趋势统计(单)
if (orderChart1.value) {
chartInstances[0] = echarts.init(orderChart1.value)
chartInstances[0]?.setOption(createChartOption(chartConfig1))
......@@ -1066,6 +1156,11 @@ onMounted(async () => {
createChartOption(chartConfig3, true, overtimeTooltipFormatter),
)
}
// 订单趋势统计(件)
if (orderChart4.value) {
chartInstances[3] = echarts.init(orderChart4.value)
chartInstances[3]?.setOption(createChartOption(chartConfig4))
}
// 请求成功后立即更新对应的图表
getStatisticDataApi()
// 监听窗口大小变化,自动调整图表大小
......@@ -1081,6 +1176,8 @@ const getchartTimes = async (v: string, type: string) => {
orderChart2Loading.value = true
} else if (type === 'OVERTIME_SHIPMENT_TREND') {
orderChart3Loading.value = true
} else if (type === 'PRODUCT_TREND') {
orderChart4Loading.value = true
}
const { data } = await trendApi({
......@@ -1111,6 +1208,18 @@ const getchartTimes = async (v: string, type: string) => {
chartTimes2.value,
)
orderChart2Loading.value = !(data && data.length > 0)
} else if (type == 'PRODUCT_TREND') {
const isEmpty = checkProductNumTrendDataEmpty(data)
// 无论数据是否为空,都要更新 x 轴数据
updateProductNumTrendChart(data, v)
updateChart(
chartInstances[3],
chartConfig4,
isEmpty,
false,
chartTimes4.value,
)
orderChart4Loading.value = !(data && data.length > 0)
} else {
const isEmpty = checkOvertimeTrendDataEmpty(data)
// 无论数据是否为空,都要更新 x 轴数据
......@@ -1416,6 +1525,40 @@ const updateOvertimeTrendChart = (data: trendType[], type: number | string) => {
)
}
// 检查订单趋势(件)数据是否为空
const checkProductNumTrendDataEmpty = (data: trendType[]): boolean => {
const confirmProductNums = data.map((el) => el.confirmProductNum ?? 0)
const producedProductNums = data.map((el) => el.produceProductNum ?? 0)
const shipmentProductNums = data.map((el) => el.shipmentProductNum ?? 0)
const hasData =
confirmProductNums.some((item) => item !== 0) ||
producedProductNums.some((item) => item !== 0) ||
shipmentProductNums.some((item) => item !== 0)
return !hasData
}
// 更新订单趋势(件)图表数据
const updateProductNumTrendChart = (
data: trendType[],
type: number | string,
) => {
const confirmProductNums = data.map((el) => el.confirmProductNum ?? 0)
const producedProductNums = data.map((el) => el.produceProductNum ?? 0)
const shipmentProductNums = data.map((el) => el.shipmentProductNum ?? 0)
const startTimes = data.map((el) => el.startTime)
const timerange = data.map((el) => `${el.startTime}——${el.endTime}`)
if (type == 0) {
chartConfig4.value.xAxisData = startTimes
} else {
chartConfig4.value.xAxisData = timerange
}
chartConfig4.value.series[0].data = confirmProductNums
chartConfig4.value.series[1].data = producedProductNums
chartConfig4.value.series[2].data = shipmentProductNums
}
const handleResize = () => {
chartInstances.forEach((instance) => {
instance?.resize()
......
......@@ -86,10 +86,10 @@ import {
getTiktokCarrier,
} from '@/api/logistics'
import type { FormItemRule } from 'element-plus'
import { WarehouseListData } from '@/types/api/podUsOrder'
import { WarehouseListData } from '@/types'
import { ISeachFormConfig } from '@/types/searchType'
import { TableColumn } from '@/components/VxeTable'
import { loadWarehouseListApi } from '@/api/podUsOrder'
import { loadWarehouseListApi } from '@/api/common'
import type {
LogisticsMethod,
platformObj,
......
......@@ -757,19 +757,6 @@ const handleSelectionChange = (val: PodUsOrderListData[]) => {
selection.value = val
}
// 修改行样式方法
const getRowStyle = ({ row }: { row: PodUsOrderListData }) => {
// 如果行被选中,设置背景色为 #fdf6ec
if (selection.value.some((item) => item.id === row.id)) {
return {
backgroundColor: '#fdf6ec',
}
}
return {
backgroundColor: '',
}
}
// 获取行类名方法
const getRowClassName = ({ row }: { row: PodUsOrderListData }) => {
return selection.value.some((item) => item.id === row.id)
......@@ -948,6 +935,7 @@ onMounted(() => {
<el-radio-button value="single">单面</el-radio-button>
<el-radio-button value="multiple">多面</el-radio-button>
<el-radio-button value="normal">普品</el-radio-button>
<el-radio-button value="custom_normal">胚衣</el-radio-button>
</el-radio-group>
</ElFormItem>
<ElFormItem label="数量">
......@@ -1156,7 +1144,6 @@ onMounted(() => {
:serial-numberable="true"
:selectionable="true"
:paginated-data="tableData"
:row-style="getRowStyle"
:row-class-name="getRowClassName"
@row-click="rowClick"
@selection-change="handleSelectionChange"
......@@ -1385,11 +1372,22 @@ onMounted(() => {
}
}
// 确保选中行的背景色在 hover 时也保持
// 设置选中行的背景色
:deep(.el-table__body) {
.el-table__row:hover {
td {
background-color: #e1ebf5 !important;
}
}
.el-table__row.row-selected {
td {
background-color: #faecd8 !important;
}
}
// 确保选中行的背景色在 hover 时也保持
.el-table__row.row-selected:hover {
td {
background-color: #fdf6ec !important;
background-color: #faecd8 !important;
}
}
}
......
......@@ -772,20 +772,6 @@ const handleSelectionChange = (val: PodCnOrderListData[]) => {
selection.value = val
}
// 修改行样式方法
const getRowStyle = ({ row }: { row: PodCnOrderListData }) => {
// 如果行被选中,设置背景色为 #fdf6ec
if (selection.value.some((item) => item.id === row.id)) {
return {
backgroundColor: '#fdf6ec',
}
}
return {
backgroundColor: '',
}
}
// 获取行类名方法
const getRowClassName = ({ row }: { row: PodCnOrderListData }) => {
return selection.value.some((item) => item.id === row.id)
......@@ -988,6 +974,7 @@ onMounted(() => {
<el-radio-button value="single">单面</el-radio-button>
<el-radio-button value="multiple">多面</el-radio-button>
<el-radio-button value="normal">普品</el-radio-button>
<el-radio-button value="custom_normal">胚衣</el-radio-button>
</el-radio-group>
</ElFormItem>
<ElFormItem label="数量">
......@@ -1246,7 +1233,6 @@ onMounted(() => {
:serial-numberable="true"
:selectionable="true"
:paginated-data="tableData"
:row-style="getRowStyle"
:row-class-name="getRowClassName"
@row-click="rowClick"
@selection-change="handleSelectionChange"
......@@ -1482,11 +1468,22 @@ onMounted(() => {
}
}
// 确保选中行的背景色在 hover 时也保持
// 设置选中行的背景色
:deep(.el-table__body) {
.el-table__row:hover {
td {
background-color: #e1ebf5 !important;
}
}
.el-table__row.row-selected {
td {
background-color: #faecd8 !important;
}
}
// 确保选中行的背景色在 hover 时也保持
.el-table__row.row-selected:hover {
td {
background-color: #fdf6ec !important;
background-color: #faecd8 !important;
}
}
}
......
......@@ -282,7 +282,7 @@ import {
} from '@/api/podCnOrder'
const { getCLodop } = useLodop()
import useOrderStore from '@/store/cnOrder'
import { WarehouseListData } from '@/types/api/podUsOrder'
import { WarehouseListData } from '@/types/index'
const orderStore = useOrderStore()
const boxList = computed(() => orderStore.podBoxList)
const podOrderDetailsColumns = computed(() => [
......
......@@ -279,7 +279,7 @@ import {
import useUserStore from '@/store/user'
import { Check, Refresh } from '@element-plus/icons-vue'
import socket from '@/utils/cnWebsocket'
import { WarehouseListData } from '@/types/api/podUsOrder'
import { WarehouseListData } from '@/types/index'
import { ElMessage } from 'element-plus'
import { filePath } from '@/api/axios.ts'
......
......@@ -237,6 +237,7 @@
<el-radio-button label="single">单面</el-radio-button>
<el-radio-button label="multiple">多面</el-radio-button>
<el-radio-button label="normal">普品</el-radio-button>
<el-radio-button value="custom_normal">胚衣</el-radio-button>
</el-radio-group>
</ElFormItem>
<ElFormItem v-if="status !== 'BATCH_DOWNLOAD'" label="数量">
......@@ -379,6 +380,34 @@
style="width: 150px"
></ElInput>
</ElFormItem>
<ElFormItem
v-if="
![
'TO_BE_ARRANGE',
'PICKING',
'TO_BE_REPLENISHMENT',
'IN_PRODUCTION',
].includes(status)
"
label="收件国家"
>
<ElSelect
v-model="searchForm.receiverCountry"
placeholder="收件国家"
clearable
:teleported="false"
style="width: 150px"
filterable
@change="changeReplaceShipment"
>
<ElOption
v-for="item in receiverCountryList"
:key="item.countryCode"
:value="item.countryCode"
:label="item.nameCn + '(' + item.countryCode + ')'"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="是否代发">
<ElSelect
v-model="searchForm.replaceShipment"
......@@ -2880,7 +2909,6 @@ import {
printPrintOrderApi,
stockOutCheckApi,
toBePickingApi,
loadWarehouseListApi,
getLogisticsCalculation,
refreshProductInformationApi,
composingDesignImages,
......@@ -2918,10 +2946,11 @@ import {
printNormalPdf,
printNormalProducePdf,
changeLogisticsApi,
getEmployeeListApi,
allErpCodeListApi,
updateProductOutOfStockApi,
} from '@/api/podCnOrder'
import { loadWarehouseListApi, getEmployeeListApi } from '@/api/common'
// import { logisticsCompanyAllCodelist } from '@/api/logistics.ts'
import { BaseRespData } from '@/types/api'
......@@ -2940,7 +2969,6 @@ import {
ProductionClient,
SearchForm,
Tab,
WarehouseListData,
cardImages,
AddressInfo,
LogisticsData,
......@@ -2948,6 +2976,7 @@ import {
CraftListData,
ExportParams,
} from '@/types/api/podCnOrder'
import { WarehouseListData } from '@/types'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { showConfirm } from '@/utils/ui'
......@@ -2974,6 +3003,7 @@ import {
type RouteLocationNormalized,
} from 'vue-router'
import UpdateCustomDeclarationInfoDialog from './components/UpdateCustomDeclarationInfoDialog.vue'
import { userData } from '@/types/api/user.ts'
declare global {
interface Window {
......@@ -3049,7 +3079,7 @@ const sourceList = [
},
]
const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL']
const employeeList = ref<{ account: string; id: number }[]>([])
const employeeList = ref<userData[]>([])
const exportData = () => {
exportVisible.value = true
......@@ -3170,6 +3200,7 @@ const [searchForm, resetSearchForm] = useValue<SearchForm>({
batchArrangeNumber: '',
craftCode: [],
thirdStockSku: '',
receiverCountry: '',
})
const shipmentArea = ref(0)
const userMarkList = ref<string[]>([])
......@@ -5543,15 +5574,8 @@ const getRowStyle = ({ row }: { row: PodCnOrderListData }) => {
color: '#67c23a',
}
}
// 如果行被选中,设置背景色为 #fdf6ec
if (selection.value.some((item) => item.id === row.id)) {
return {
backgroundColor: '#fdf6ec',
}
}
return {
backgroundColor: '',
}
// 选中状态通过 CSS 类名控制,这里不再处理
return {}
}
// 获取行类名方法
......@@ -5646,6 +5670,17 @@ const getlogisticsCompanyAllCodelist = async () => {
}
}
const receiverCountryList = ref<{ countryCode: string; nameCn: string }[]>([])
const getReceiverCountryList = async () => {
try {
const res = await getAllCountryApi()
if (res.code !== 200) return
receiverCountryList.value = res.data
} catch (e) {
console.error(e)
}
}
function tooltipContent(arr: { name: string }[]) {
return arr.map((tag) => tag.name).join('、')
}
......@@ -5746,6 +5781,7 @@ onMounted(() => {
getCustomTagList()
loadCraftList()
getlogisticsCompanyAllCodelist()
getReceiverCountryList()
// 每60秒更新一次当前时间
timer = window.setInterval(() => {
currentTime.value = new Date()
......@@ -6549,11 +6585,22 @@ const onUpdateCustomsDeclarationInfo = () => {
}
}
// 确保选中行的背景色在 hover 时也保持
// 设置选中行的背景色
:deep(.el-table__body) {
.el-table__row:hover {
td {
background-color: #e1ebf5 !important;
}
}
.el-table__row.row-selected {
td {
background-color: #faecd8 !important;
}
}
// 确保选中行的背景色在 hover 时也保持
.el-table__row.row-selected:hover {
td {
background-color: #fdf6ec !important;
background-color: #faecd8 !important;
}
}
}
......
......@@ -267,7 +267,7 @@ import {
import useUserStore from '@/store/user'
import { Check, Refresh } from '@element-plus/icons-vue'
import socket from '@/utils/websocket'
import { WarehouseListData } from '@/types/api/podUsOrder'
import { WarehouseListData } from '@/types/index'
import { filePath } from '@/api/axios.ts'
import { ElButton, ElIcon } from 'element-plus'
......
......@@ -227,6 +227,7 @@
<el-radio-button label="single">单面</el-radio-button>
<el-radio-button label="multiple">多面</el-radio-button>
<el-radio-button label="normal">普品</el-radio-button>
<el-radio-button value="custom_normal">胚衣</el-radio-button>
</el-radio-group>
</ElFormItem>
<ElFormItem v-if="status !== 'BATCH_DOWNLOAD'" label="数量">
......@@ -3161,7 +3162,7 @@
</template>
<script setup lang="tsx">
import LogisticsWaySelect from '../../logistics/components/LogisticsWaySelect.tsx'
import { getUserMarkList } from '@/api/common'
import { getUserMarkList, loadWarehouseListApi } from '@/api/common'
// import { AnyObject } from '@/types/api/warehouse'
import uploadBox from '@/components/uploadBox.vue'
import { convertToChinaTime } from '@/utils/index'
......@@ -3194,7 +3195,6 @@ import {
printPrintOrderApi,
stockOutCheckApi,
toBePickingApi,
loadWarehouseListApi,
getLogisticsCalculation,
refreshMaterialApi,
getTrackingNumberApi,
......@@ -3240,7 +3240,6 @@ import {
getLogisticsWayApi,
printNormalPickPdfApi,
updatePRNDownloadStatus,
getEmployeeListApi,
updateProductOutOfStockApi,
} from '@/api/podUsOrder'
import { BaseRespData } from '@/types/api'
......@@ -3248,7 +3247,7 @@ import { BaseRespData } from '@/types/api'
import UpdateAddress from './components/updateAddress.vue'
import { getAllCountryApi } from '@/api/logistics.ts'
import { getEmployeeListApi } from '@/api/common'
import TableView from '@/components/TableView.vue'
import {
LogListData,
......@@ -3257,7 +3256,6 @@ import {
ProductionClient,
SearchForm,
Tab,
WarehouseListData,
cardImages,
AddressInfo,
LogisticsData,
......@@ -3293,6 +3291,7 @@ import platformJson from '../../../json/platform.json'
import { getToken } from '@/api/axios'
import usePermissionBtnStore from '@/store/permission'
import ReissueOrderComponent from './components/ReissueOrder.vue'
import { WarehouseListData } from '@/types'
const permissionBtns = usePermissionBtnStore()
......@@ -3302,6 +3301,7 @@ import {
type RouteLocationNormalized,
} from 'vue-router'
import PrintWarehouseSkuTag from '@/views/order/components/printWarehouseSkuTag.vue'
import { userData } from '@/types/api/user.ts'
declare global {
interface Window {
......@@ -3339,7 +3339,7 @@ const printWarehouseSkuDialogRef = ref()
const isAuto = ref(true)
const countryList = ref([])
const logisticsWayList = ref<{ name: string; id: number }[]>([])
const employeeList = ref<{ account: string; id: number }[]>([])
const employeeList = ref<userData[]>([])
const currentRow = ref<AddressInfo>({
receiverName: '',
receiverPhone: '',
......@@ -6176,15 +6176,8 @@ const getRowStyle = ({ row }: { row: PodUsOrderListData }) => {
color: '#67c23a',
}
}
// 如果行被选中,设置背景色为 #fdf6ec
if (selection.value.some((item) => item.id === row.id)) {
return {
backgroundColor: '#fdf6ec',
}
}
return {
backgroundColor: '',
}
// 选中状态通过 CSS 类名控制,这里不再处理
return {}
}
// 获取行类名方法
......@@ -7327,11 +7320,22 @@ useRouter().beforeEach((to, from, next) => {
}
}
// 确保选中行的背景色在 hover 时也保持
// 设置选中行的背景色
:deep(.el-table__body) {
.el-table__row:hover {
td {
background-color: #e1ebf5 !important;
}
}
.el-table__row.row-selected {
td {
background-color: #faecd8 !important;
}
}
// 确保选中行的背景色在 hover 时也保持
.el-table__row.row-selected:hover {
td {
background-color: #fdf6ec !important;
background-color: #faecd8 !important;
}
}
}
......
......@@ -107,9 +107,9 @@
import { computed, ref, onMounted } from 'vue'
import TableView from '@/components/TableView.vue'
import usePageList from '@/utils/hooks/usePageList'
import { loadWarehouseListApi } from '@/api/podCnOrder'
import type { WarehouseListData } from '@/types/api/podCnOrder'
import { SearchForm, OutOfStockItem } from '@/types/api/outOfStockStatistics'
import { loadWarehouseListApi } from '@/api/common'
import { WarehouseListData } from '@/types'
import { SearchForm, OutOfStockItem } from '@/types/api/supply/outOfStockStatistics'
import {
getOutOfStockStatisticsListApi,
exportOutOfStockStatisticsListApi,
......
<template>
<ElDialog
v-model="visible"
:title="
editOrderType === 'add'
? '新增备货单'
: editOrderType === 'edit'
? '编辑备货单'
: '审核备货单'
"
width="1400px"
top="15vh"
:close-on-click-modal="false"
destroy-on-close
>
<div class="add-stocking-order">
<div class="form-section">
<ElForm
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
inline
>
<div class="form-row">
<ElFormItem label="备货单号" class="form-item">
<ElInput
v-model="formData.stockingUpManageNo"
disabled
placeholder="系统自动生成"
style="width: 100%"
/>
</ElFormItem>
<ElFormItem label="供应商" prop="supplierId" class="form-item">
<ElSelect
v-model="formData.supplierId"
placeholder="请选择"
filterable
clearable
:disabled="editOrderType === 'audit'"
style="width: 100%"
@change="handleSupplierChange"
>
<ElOption
v-for="item in supplierList"
:key="item.id"
:label="item.supplierName"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
label="备货员"
prop="stockingUpUserId"
class="form-item"
>
<ElSelect
v-model="formData.stockingUpUserId"
placeholder="请选择"
filterable
:disabled="editOrderType === 'audit'"
clearable
style="width: 100%"
>
<ElOption
v-for="item in stockKeeperList"
:key="item.id"
:label="item.account"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="备货仓库" prop="warehouseId" class="form-item">
<ElSelect
v-model="formData.warehouseId"
placeholder="请选择"
filterable
:disabled="editOrderType === 'audit'"
clearable
style="width: 100%"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
</div>
<div class="form-row">
<ElFormItem label="期望交货日期" class="form-item">
<ElDatePicker
v-model="formData.expectDeliveryTime"
type="datetime"
:disabled="editOrderType === 'audit'"
placeholder="请选择"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
style="width: 100%"
/>
</ElFormItem>
<ElFormItem label="备货总额" class="form-item">
<ElInput
v-model="totalAmount"
disabled
placeholder="0"
style="width: 100%"
/>
</ElFormItem>
<ElFormItem label="币种" class="form-item">
<ElSelect
v-model="formData.currencyCode"
placeholder="请选择"
filterable
clearable
disabled
style="width: 100%"
>
<ElOption
v-for="item in currencyList"
:key="item.id"
:label="`${item.currencyName}(${item.currencyCode})`"
:value="item.currencyCode"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="备注" class="form-item">
<ElInput
v-model="formData.remark"
placeholder="请输入备注"
clearable
:disabled="editOrderType === 'audit'"
style="width: 100%"
/>
</ElFormItem>
</div>
</ElForm>
</div>
<div class="table-section">
<TableView
:paginated-data="formData.detailsList"
:serial-numberable="true"
:columns="tableColumns"
:selectionable="true"
@selection-change="handleSelectionChange"
>
<template #buyAmount="{ row }">
<ElInput
v-model="row.buyAmount"
clearable
:disabled="editOrderType === 'audit'"
size="small"
style="width: 80px"
@blur="validateField(row, 'buyAmount')"
/>
<div v-if="row._buyAmountError" class="field-error">
{{ row._buyAmountError }}
</div>
</template>
<template #price="{ row }">
<ElInput
v-model="row.price"
clearable
:disabled="editOrderType === 'audit'"
size="small"
style="width: 80px"
:class="{ 'is-error': row._priceError }"
@blur="validateField(row, 'price')"
/>
<div v-if="row._priceError" class="field-error">
{{ row._priceError }}
</div>
</template>
</TableView>
</div>
<div
v-if="editOrderType === 'add' || editOrderType === 'edit'"
class="add-sku-section"
>
<span class="label">添加SKU</span>
<ElInput
v-model="productSku"
placeholder="商品父SKU"
clearable
style="width: 200px"
@keyup.enter="handleSearchSku"
/>
<ElPopover
ref="popoverRef"
v-model:visible="popoverVisible"
placement="top-start"
:width="900"
trigger="manual"
popper-class="sku-search-popover"
>
<template #reference>
<ElButton type="primary" @click="handleSearchSku">查询</ElButton>
</template>
<div class="popover-table">
<TableView
ref="searchTableRef"
:paginated-data="productList"
:columns="searchTableColumns"
:serial-numberable="true"
>
<template #operation="{ row }">
<ElButton
type="success"
circle
size="small"
@click="handleAddSingleProduct(row)"
>
<ElIcon><Plus /></ElIcon>
</ElButton>
</template>
</TableView>
</div>
</ElPopover>
<ElButton type="danger" @click="handleDeleteSelected">删除</ElButton>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton size="large" @click="handleCancel">取消</ElButton>
<ElButton
v-if="editOrderType === 'add' || editOrderType === 'edit'"
size="large"
type="primary"
@click="handleSave(false)"
>保存</ElButton
>
<ElButton
v-if="editOrderType === 'add' || editOrderType === 'edit'"
size="large"
type="success"
@click="handleSaveAndSubmit"
>
保存并提交审核
</ElButton>
<ElButton
v-if="editOrderType === 'audit'"
size="large"
type="success"
@click="handleSave(false)"
>审核通过</ElButton
>
<ElButton
v-if="editOrderType === 'audit'"
size="large"
type="danger"
@click="handleReject"
>审核驳回</ElButton
>
</div>
</template>
</ElDialog>
</template>
<script setup lang="tsx">
import { ref, computed } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type {
AddStockingOrderForm,
StockingOrderProduct,
TableData,
} from '@/types/api/supply/stockingOrder'
import type { CurrencyCodeData, SupplierItem, WarehouseListData } from '@/types'
import type { userData } from '@/types/api/user'
import {
addStockingOrderApi,
getProductBySkuApi,
getStockingOrderDetailByIdApi,
rejectedStockingOrderApi,
} from '@/api/supplier/stockingOrder'
import { BigNumber } from 'bignumber.js'
const calcRowTotalPrice = (row: StockingOrderProduct): string => {
if (!row.buyAmount || !row.price) return ''
row.totalPrice = new BigNumber(row.buyAmount ?? 0)
.multipliedBy(row.price ?? 0)
.toString()
return row.totalPrice
}
const props = defineProps<{
supplierList: SupplierItem[]
stockKeeperList: userData[]
warehouseList: WarehouseListData[]
currencyList: CurrencyCodeData[]
}>()
const emit = defineEmits<{
(e: 'refresh'): void
}>()
const visible = ref(false)
const editOrderType = ref<string>('add')
const editId = ref<number | undefined>(undefined)
const formRef = ref<FormInstance>()
const formData = ref<AddStockingOrderForm>({
stockingUpManageNo: '',
supplierId: undefined,
stockingUpUserId: undefined,
warehouseId: undefined,
totalPrice: '',
expectDeliveryTime: '',
currencyCode: '',
remark: '',
detailsList: [],
})
type StockingOrderProductWithError = StockingOrderProduct & {
_buyAmountError?: string
_priceError?: string
}
const validateField = (
row: StockingOrderProductWithError,
field: 'buyAmount' | 'price',
) => {
const value = row[field]
const strValue = value === 0 || value === '0' ? '0' : String(value ?? '')
const fieldLabel = field === 'buyAmount' ? '备货数量' : '备货单价'
if (!strValue) {
if (field === 'buyAmount') row._buyAmountError = '必填项'
else row._priceError = '必填项'
} else if (field === 'buyAmount') {
// 备货数量只能输入正整数
if (!/^[1-9]\d*$/.test(strValue)) {
row._buyAmountError = '请输入正整数'
} else {
row._buyAmountError = ''
}
} else if (field === 'price') {
if (!/^\d+(\.\d+)?$/.test(strValue)) {
row._priceError = '请输入有效数字'
} else if (Number(strValue) === 0) {
row._priceError = `${fieldLabel}不能为0`
} else {
row._priceError = ''
}
}
}
const validateDetailsList = (): boolean => {
if (!formData.value.detailsList?.length) {
return true
}
let isValid = true
formData.value.detailsList.forEach((row) => {
const extendedRow = row as StockingOrderProductWithError
validateField(extendedRow, 'buyAmount')
validateField(extendedRow, 'price')
if (extendedRow._buyAmountError || extendedRow._priceError) {
isValid = false
}
})
return isValid
}
const formRules: FormRules = {
supplierId: [{ required: true, message: '请选择供应商', trigger: 'change' }],
stockingUpUserId: [
{ required: true, message: '请选择备货员', trigger: 'change' },
],
warehouseId: [
{ required: true, message: '请选择备货仓库', trigger: 'change' },
],
}
const selectedProducts = ref<StockingOrderProduct[]>([])
const productSku = ref('')
const popoverVisible = ref(false)
const tableColumns = [
{
label: 'SKU图片',
prop: 'skuImage',
width: 180,
align: 'center',
render: (item: StockingOrderProduct) => (
<el-image
preview-teleported
src={item.warehouseSkuImage}
preview-src-list={[item.warehouseSkuImage]}
z-index={9999}
style="width: 60px;height: 60px;text-align: center;"
/>
),
},
{ label: '商品名称', prop: 'warehouseSkuName', minWidth: 150 },
{ label: '款号', prop: 'productNo', align: 'center', width: 120 },
{ label: '库存SKU', prop: 'warehouseSku', align: 'center', width: 180 },
{
label: '备货数量',
prop: 'buyAmount',
slot: 'buyAmount',
width: 120,
align: 'center',
},
{
label: '备货单价',
prop: 'price',
slot: 'price',
width: 120,
align: 'center',
},
{
label: '备货金额',
prop: 'totalPrice',
width: 120,
align: 'center',
render: (item: StockingOrderProduct) => calcRowTotalPrice(item),
},
]
const searchTableColumns = computed(() => {
return [
{
label: 'SKU图片',
prop: 'skuImage',
slot: 'skuImage',
width: 120,
align: 'center',
render: (item: StockingOrderProduct) => (
<el-image
preview-teleported
src={item.warehouseSkuImage}
preview-src-list={[item.warehouseSkuImage]}
z-index={9999}
style="width: 60px;height: 60px;text-align: center;"
/>
),
},
{ label: '商品名称', prop: 'warehouseSkuName', minWidth: 150 },
{ label: '库存SKU', prop: 'warehouseSku', align: 'center', minWidth: 180 },
{ label: '款号', prop: 'productNo', align: 'center', width: 100 },
{ label: '币种', prop: 'currencyCode', align: 'center', width: 80 },
{ label: '成本价', prop: 'price', align: 'center', width: 80 },
{
label: '操作',
prop: 'operation',
slot: 'operation',
width: 70,
align: 'center',
fixed: 'right',
},
]
})
const productList = ref<StockingOrderProduct[]>([])
const totalAmount = computed(() => {
return formData.value.detailsList?.reduce((sum, cur) => {
return new BigNumber(sum || 0)
.plus(new BigNumber(cur.totalPrice || 0).toNumber())
.toString()
}, new BigNumber(0).toString())
})
const handleSelectionChange = (selection: StockingOrderProduct[]) => {
selectedProducts.value = selection
}
const handleSearchSku = async () => {
if (!productSku.value) {
ElMessage.warning('请输入商品父SKU')
popoverVisible.value = false
return
}
if (!formData.value.supplierId) {
ElMessage.warning('请先选择供应商')
popoverVisible.value = false
return
}
try {
const res = await getProductBySkuApi(
productSku.value,
formData.value.supplierId,
)
if (res.code !== 200) {
ElMessage.warning(res.message)
popoverVisible.value = false
return
}
productList.value = res.data || []
if (productList.value.length === 0) {
ElMessage.warning('未查询到商品数据')
popoverVisible.value = false
} else {
popoverVisible.value = true
}
} catch (error) {
console.error(error)
popoverVisible.value = false
}
}
const handleAddSingleProduct = (row: StockingOrderProduct) => {
if (
formData.value.detailsList?.some(
(item) => item.warehouseSku === row.warehouseSku,
)
) {
ElMessage.warning('商品已添加')
return
}
if (
formData.value.currencyCode &&
row.currencyCode !== formData.value.currencyCode
) {
ElMessage.warning('商品币种与主单币种不一致,添加失败!')
return
}
if (!formData.value.currencyCode && row.currencyCode) {
formData.value.currencyCode = row.currencyCode
}
formData.value.detailsList?.push(row)
const index = productList.value.findIndex(
(item: StockingOrderProduct) => item.warehouseSku === row.warehouseSku,
)
if (index !== -1) {
productList.value.splice(index, 1)
}
if (productList.value.length === 0) {
popoverVisible.value = false
}
}
const handleDeleteSelected = () => {
if (selectedProducts.value.length === 0) {
ElMessage.warning('请先选择要删除的商品')
return
}
const selectedSkus = selectedProducts.value.map((item) => item.warehouseSku)
formData.value.detailsList = formData.value.detailsList?.filter(
(item) => !selectedSkus.includes(item.warehouseSku),
)
selectedProducts.value = []
// 如果商品列表为空,清空币种
if (!formData.value.detailsList?.length) {
formData.value.currencyCode = ''
}
}
const handleCancel = () => {
visible.value = false
}
const handleSave = async (isSubmit: boolean = false) => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch (e) {
return
}
if (!formData.value.detailsList?.length) {
ElMessage.warning('请先添加商品')
return
}
if (!validateDetailsList()) {
ElMessage.warning('请检查备货数量和备货单价是否正确填写')
return
}
const { supplierId, warehouseId, stockingUpUserId } = formData.value
const supplierName = props.supplierList.find(
(item) => item.id === supplierId,
)?.supplierName
const warehouseName = props.warehouseList.find(
(item) => item.id === warehouseId,
)?.name
const stockingUpUserName = props.stockKeeperList.find(
(item) => item.id === stockingUpUserId,
)?.account
const url =
editOrderType.value === 'add'
? 'factory/supply/stockingUpManage/add'
: editOrderType.value === 'edit'
? `factory/supply/stockingUpManage/update`
: `factory/supply/stockingUpManage/examine`
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await addStockingOrderApi(url, {
...formData.value,
id: editId.value,
supplierName,
warehouseName,
stockingUpUserName,
totalPrice: totalAmount.value,
submission: editOrderType.value === 'audit' ? undefined : isSubmit,
})
if (res.code !== 200) {
ElMessage.warning(res.message)
return
}
ElMessage.success('保存成功')
emit('refresh')
visible.value = false
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
const handleSaveAndSubmit = async () => {
await handleSave(true)
}
const handleReject = async () => {
if (!editId.value) return
try {
const { value: rejectReason } = await ElMessageBox.prompt(
'请输入驳回原因',
'审核驳回',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请输入驳回原因',
inputValidator: (val) => {
if (!val || !val.trim()) {
return '驳回原因不能为空'
}
return true
},
},
)
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await rejectedStockingOrderApi({
id: editId.value,
turnDownReason: rejectReason.trim(),
})
if (res.code !== 200) return
ElMessage.success('驳回成功')
emit('refresh')
visible.value = false
} finally {
loading.close()
}
} catch {
// 用户取消操作,不做处理
}
}
const resetForm = () => {
formRef.value?.resetFields()
formData.value = {
stockingUpManageNo: '',
supplierId: undefined,
stockingUpUserId: undefined,
warehouseId: undefined,
expectDeliveryTime: '',
currencyCode: '',
totalPrice: '',
remark: '',
detailsList: [],
}
selectedProducts.value = []
productSku.value = ''
productList.value = []
popoverVisible.value = false
editId.value = undefined
editOrderType.value = 'add'
}
const handleSupplierChange = () => {
formData.value.currencyCode = ''
formData.value.detailsList = []
}
const getStockingOrderDetailById = async (id: number) => {
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await getStockingOrderDetailByIdApi(id)
if (res.code !== 200) {
ElMessage.warning(res.message)
return
}
formData.value = res.data
visible.value = true
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
const open = (row: TableData | null, type: string) => {
if (row) {
productSku.value = ''
editId.value = row.id
editOrderType.value = type
getStockingOrderDetailById(row.id)
} else {
visible.value = true
resetForm()
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.add-stocking-order {
.form-section {
.form-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.form-item {
flex: 1;
min-width: 280px;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-date-editor) {
width: 160px;
}
}
.table-section {
height: 500px;
.empty-tip {
text-align: center;
color: #909399;
padding: 20px;
}
}
.add-sku-section {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
.label {
font-size: 14px;
color: #606266;
}
}
}
.dialog-footer {
text-align: center;
}
.popover-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
.popover-title {
font-size: 14px;
font-weight: bold;
color: #303133;
margin-right: auto;
}
.close-icon {
cursor: pointer;
color: #909399;
font-size: 18px;
&:hover {
color: #409eff;
}
}
}
.popover-table {
height: 600px;
.sku-image {
width: 50px;
height: 50px;
object-fit: contain;
}
}
.is-error {
:deep(.el-input__wrapper) {
box-shadow: 0 0 0 1px #f56c6c inset;
}
}
.field-error {
color: #f56c6c;
font-size: 12px;
line-height: 1;
margin-top: 2px;
}
</style>
<style lang="scss">
.sku-search-popover {
.el-table {
.el-button.is-circle {
padding: 6px;
}
}
}
</style>
<template>
<ElTabs
v-model="activeTab"
v-loading="loading"
class="detail-tabs"
@tab-click="handleTabClick"
>
<ElTabPane name="stockProducts" label="备货商品">
<div class="detail-table-content">
<TableView
:serial-numberable="true"
:columns="stockProductsColumns"
:paginated-data="stockProductsData"
/>
</div>
</ElTabPane>
<ElTabPane name="relatedDocuments" label="关联单据">
<div class="detail-table-content">
<TableView
:serial-numberable="true"
:columns="relatedDocumentsColumns"
:paginated-data="relatedDocumentsData"
/>
</div>
</ElTabPane>
<ElTabPane name="internalMemo" label="内部便签">
<div class="detail-table-content memo-content">
<div class="memo-input-wrapper">
<ElInput
v-model="memoInputValue"
placeholder="请输入内容"
clearable
class="memo-input"
/>
<ElButton type="primary" :loading="addingMemo" @click="handleAddMemo">
添加
</ElButton>
</div>
<div class="memo-list">
<template v-if="internalMemoData.length > 0">
<div
v-for="item in internalMemoData"
:key="item.id"
class="memo-item"
>
<span class="memo-user">{{ item.operatorEmployeeName }}</span>
<span class="memo-text">{{ item.content }}</span>
<span class="memo-time">({{ item.operatorTime }})</span>
</div>
</template>
<div v-else class="empty-content">暂无数据</div>
</div>
</div>
</ElTabPane>
<ElTabPane name="operationLog" label="操作日志">
<div class="detail-table-content">
<LogList :log-list="operationLogData" />
<div class="empty-content">暂无数据</div>
</div>
</ElTabPane>
</ElTabs>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
import TableView from '@/components/TableView.vue'
import {
InternalMemoList,
LogListData,
RelatedDocumentList,
StockingOrderProduct,
TableData,
} from '@/types/api/supply/stockingOrder'
import ImageView from '@/components/ImageView.vue'
import {
addStockingOrderInternalTagApi,
getStockingOrderDetailListByIdApi,
getStockingOrderInternalMemoListByIdApi,
getStockingOrderLogListByIdApi,
getStockingOrderRelatedDocumentListByIdApi,
} from '@/api/supplier/stockingOrder'
import { ElMessage } from 'element-plus'
import LogList from '@/components/LogList.vue'
const relatedDocumentsColumns = computed(() => {
return [
{
label: '关联单据',
prop: 'warehouseApplyNo',
align: 'center',
},
{
label: '关联单号',
prop: 'manageNo',
align: 'center',
},
{
label: '制单人',
width: 120,
prop: 'createUserName',
align: 'center',
},
{
label: '制单时间',
width: 160,
prop: 'createTime',
align: 'center',
},
{
label: 'SKU个数',
width: 120,
align: 'right',
prop: 'skuTotal',
},
{
label: '入库数量',
width: 120,
align: 'right',
prop: 'buyStored',
},
{
label: '申请数量',
width: 120,
prop: 'total',
align: 'right',
},
{
label: '预计到货日期',
width: 160,
align: 'center',
prop: 'expectDeliveryTime',
},
{
label: '实际到货日期',
width: 160,
align: 'center',
prop: 'deliveryTime',
},
]
})
const stockProductsColumns = computed(() => {
return [
{
label: 'SKU图片',
prop: 'skuImage',
width: 100,
align: 'center',
render: (item: StockingOrderProduct) => (
<ImageView src={item.warehouseSkuImage} width="50px" height="50px" />
),
},
{
label: '商品名称',
prop: 'warehouseSkuName',
minWidth: 200,
align: 'left',
},
{
label: '款号',
prop: 'productNo',
width: 120,
align: 'center',
},
{
label: '库存SKU',
prop: 'warehouseSku',
width: 180,
align: 'center',
},
{
label: '备货数量',
prop: 'buyAmount',
width: 100,
align: 'right',
},
{
label: '备货单价',
prop: 'price',
width: 100,
align: 'right',
},
{
label: '备货金额',
prop: 'totalPrice',
width: 120,
align: 'right',
},
{
label: '已发货数量',
prop: 'shipmentQuantity',
width: 120,
align: 'right',
},
{
label: '已入库数量',
prop: 'buyStored',
width: 120,
align: 'right',
},
{
label: '不良品数量',
prop: 'rejectsAmount',
width: 120,
align: 'right',
},
]
})
const props = defineProps<{
selectedRow: TableData | null
}>()
const stockProductsData = ref<StockingOrderProduct[]>([])
const relatedDocumentsData = ref<RelatedDocumentList[]>([])
const internalMemoData = ref<InternalMemoList[]>([])
const operationLogData = ref<LogListData[]>([])
const activeTab = ref('stockProducts')
const loading = ref(false)
const memoInputValue = ref('')
const addingMemo = ref(false)
const handleAddMemo = async () => {
if (!memoInputValue.value.trim()) {
ElMessage.warning('请输入内容')
return
}
if (!props.selectedRow) return
addingMemo.value = true
try {
const res = await addStockingOrderInternalTagApi(
memoInputValue.value.trim(),
[props.selectedRow.id],
)
if (res.code === 200) {
ElMessage.success('添加成功')
memoInputValue.value = ''
loadInternalMemo(props.selectedRow)
}
} catch (e) {
console.error(e)
} finally {
addingMemo.value = false
}
}
watch(
() => props.selectedRow,
(newVal) => {
if (newVal) {
if (activeTab.value === 'stockProducts') {
loadStockProducts(newVal)
} else if (activeTab.value === 'relatedDocuments') {
loadRelatedDocuments(newVal)
} else if (activeTab.value === 'internalMemo') {
loadInternalMemo(newVal)
} else if (activeTab.value === 'operationLog') {
loadOperationLog(newVal)
}
} else {
stockProductsData.value = []
relatedDocumentsData.value = []
internalMemoData.value = []
operationLogData.value = []
}
},
)
const loadStockProducts = async (row: TableData) => {
loading.value = true
try {
const res = await getStockingOrderDetailListByIdApi(row.id)
if (res.code !== 200) return
stockProductsData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const loadRelatedDocuments = async (row: TableData) => {
loading.value = true
try {
const res = await getStockingOrderRelatedDocumentListByIdApi(row.id)
if (res.code !== 200) return
relatedDocumentsData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const loadInternalMemo = async (row: TableData) => {
loading.value = true
try {
const res = await getStockingOrderInternalMemoListByIdApi(row.id)
if (res.code !== 200) return
internalMemoData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const loadOperationLog = async (row: TableData) => {
loading.value = true
try {
const res = await getStockingOrderLogListByIdApi(row.id)
if (res.code !== 200) return
operationLogData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const handleTabClick = (tab: TabsPaneContext) => {
activeTab.value = tab.paneName as string
if (!props.selectedRow) return
if (activeTab.value === 'stockProducts') {
loadStockProducts(props.selectedRow as TableData)
} else if (activeTab.value === 'relatedDocuments') {
loadRelatedDocuments(props.selectedRow as TableData)
} else if (activeTab.value === 'internalMemo') {
loadInternalMemo(props.selectedRow as TableData)
} else if (activeTab.value === 'operationLog') {
loadOperationLog(props.selectedRow as TableData)
}
}
</script>
<style lang="scss" scoped>
.detail-tabs {
height: 100%;
display: flex;
:deep(.el-tab-pane) {
height: 100%;
overflow: hidden;
}
}
.detail-table-content {
height: 100%;
overflow: hidden;
}
.empty-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
font-size: 14px;
}
.memo-content {
display: flex;
flex-direction: column;
}
.memo-input-wrapper {
display: flex;
align-items: center;
gap: 12px;
.memo-input {
flex: 1;
max-width: 300px;
}
}
.memo-list {
flex: 1;
overflow-y: auto;
}
.memo-item {
padding: 8px 0;
font-size: 14px;
line-height: 1.5;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.memo-user {
color: #409eff;
}
.memo-text {
color: #303133;
}
.memo-time {
color: #909399;
margin-left: 8px;
}
}
</style>
<template>
<ElDialog
v-model="visible"
title="供应商发货"
width="1400px"
top="15vh"
:close-on-click-modal="false"
destroy-on-close
>
<div class="supplier-dispatch-order-page">
<div class="form-section">
<ElForm ref="formRef" :model="formData" label-width="110px" inline>
<div class="form-row">
<ElFormItem label="备货单号" class="form-item">
<ElInput
v-model="formData.stockingUpManageNo"
disabled
placeholder="系统自动带出"
style="width: 100%"
/>
</ElFormItem>
<ElFormItem
label="备货仓库"
prop="warehouseId"
class="form-item"
:rules="[
{
required: true,
message: '请选择备货仓库',
trigger: 'change',
},
]"
>
<ElSelect
v-model="formData.warehouseId"
placeholder="请选择"
filterable
clearable
style="width: 100%"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
label="预计到货日期"
prop="expectDeliveryTime"
class="form-item"
:rules="[
{
required: true,
message: '请选择预计到货日期',
trigger: 'change',
},
]"
>
<ElDatePicker
v-model="formData.expectDeliveryTime"
type="datetime"
placeholder="请选择"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
style="width: 100%"
/>
</ElFormItem>
</div>
</ElForm>
</div>
<div class="table-section">
<TableView
ref="tableViewRef"
:paginated-data="formData.detailsList ?? []"
:serial-numberable="true"
:columns="tableColumns"
:selectionable="true"
@selection-change="handleSelectionChange"
>
<template #currentShipQuantity="{ row }">
<ElInput
v-model="row.currentShipQuantity"
clearable
size="small"
style="width: 100px"
@blur="validateField(row, 'currentShipQuantity')"
/>
<div v-if="row._currentShipQuantityError" class="field-error">
{{ row._currentShipQuantityError }}
</div>
</template>
</TableView>
</div>
<div class="action-section">
<ElButton type="success" @click="handleAutoFillQuantity">
剩余待发货数量>>本次发货数量
</ElButton>
<ElButton type="danger" @click="handleDeleteSelected">删除</ElButton>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton size="large" @click="visible = false">取消</ElButton>
<ElButton size="large" type="danger" @click="handleDispatch">
发货
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import type { FormInstance } from 'element-plus'
import type {
TableData,
StockingOrderProduct,
AddStockingOrderForm,
} from '@/types/api/supply/stockingOrder'
import type { WarehouseListData } from '@/types'
import {
getStockingOrderDetailByIdApi,
supplierDispatchApi,
} from '@/api/supplier/stockingOrder'
import TableView from '@/components/TableView.vue'
import { BigNumber } from 'bignumber.js'
const props = defineProps<{
warehouseList: WarehouseListData[]
}>()
const emit = defineEmits<{
(e: 'refresh'): void
}>()
const visible = ref(false)
const currentRow = ref<TableData | null>(null)
const formRef = ref<FormInstance>()
const formData = ref<AddStockingOrderForm>({
stockingUpManageNo: '',
warehouseId: undefined as number | undefined,
expectDeliveryTime: '',
detailsList: [],
})
const selectedProducts = ref<StockingOrderProduct[]>([])
const tableColumns = computed(() => [
{
label: 'SKU图片',
prop: 'skuImage',
width: 100,
align: 'center',
render: (item: StockingOrderProduct) => (
<el-image
preview-teleported
src={item.warehouseSkuImage}
preview-src-list={[item.warehouseSkuImage]}
z-index={9999}
style="width: 60px;height: 60px;text-align: center;"
/>
),
},
{
label: '商品名称',
prop: 'warehouseSkuName',
minWidth: 150,
align: 'left',
showOverflowTooltip: true,
},
{
label: '款号',
prop: 'productNo',
width: 120,
align: 'center',
},
{
label: '库存SKU',
prop: 'warehouseSku',
width: 180,
align: 'center',
},
{
label: '备货数量',
prop: 'buyAmount',
width: 100,
align: 'right',
},
{
label: '已入库数量',
prop: 'buyStored',
width: 100,
align: 'right',
render: (item: StockingOrderProduct) => (
<span style="color: #409EFF; font-weight: 500;">
{item.buyStored ?? 0}
</span>
),
},
{
label: '已发货数量',
prop: 'shipmentQuantity',
width: 100,
align: 'right',
render: (item: StockingOrderProduct) => (
<span style="color: #E6A23C; font-weight: 500;">
{item.shipmentQuantity ?? 0}
</span>
),
},
{
label: '剩余待发货数量',
prop: 'remainingQuantity',
width: 140,
align: 'right',
render: (item: StockingOrderProduct) => (
<span style="color: #F56C6C; font-weight: 500;">
{item.remainingQuantity ?? 0}
</span>
),
},
{
label: '本次发货数量',
slot: 'currentShipQuantity',
width: 140,
align: 'center',
},
])
const handleAutoFillQuantity = () => {
if (selectedProducts.value.length === 0) {
ElMessage.warning('请选择要自动填充的商品')
return
}
selectedProducts.value.forEach((item) => {
if (item.remainingQuantity && item.remainingQuantity > 0) {
item.currentShipQuantity = item.remainingQuantity
}
})
}
const handleSelectionChange = (selection: StockingOrderProduct[]) => {
selectedProducts.value = selection
}
const handleDeleteSelected = () => {
if (selectedProducts.value.length === 0) {
ElMessage.warning('请先选择要删除的商品')
return
}
const selectedSkus = selectedProducts.value.map((item) => item.warehouseSku)
formData.value.detailsList = formData.value.detailsList?.filter(
(item) => !selectedSkus.includes(item.warehouseSku),
)
selectedProducts.value = []
}
const validateDetailsList = (): boolean => {
if (!formData.value.detailsList?.length) {
return true
}
let isValid = true
formData.value.detailsList.forEach((row) => {
const extendedRow = row as StockingOrderProductWithError
validateField(extendedRow, 'currentShipQuantity')
if (extendedRow._currentShipQuantityError) {
isValid = false
}
})
return isValid
}
const handleDispatch = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (formData.value.detailsList?.length === 0) {
ElMessage.warning('没有可发货的商品')
return
}
if (!validateDetailsList()) {
ElMessage.warning('请检查本次发货数量是否正确填写')
return
}
const overQuantitySkus = (formData.value.detailsList || [])
.filter(
(item) =>
Number(item.currentShipQuantity) > (item.remainingQuantity ?? 0),
)
.map((item) => item.warehouseSku)
if (overQuantitySkus.length > 0) {
try {
await ElMessageBox.alert(
`<div style="line-height: 1.6;"><span style="white-space: nowrap; flex-shrink: 0;">库存SKU:</span><span style="word-break: break-all; margin-left: 4px;">${overQuantitySkus.join(
',',
)}</span><span style="margin-top: 12px;">本次发货数量大于剩余待发货数量,是否确认发货?</span></div>`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'over-quantity-alert-box',
dangerouslyUseHTMLString: true,
},
)
} catch {
return
}
}
const loading = ElLoading.service({
lock: true,
text: '正在提交...',
background: 'rgba(0, 0, 0, 0.7)',
})
const {
stockingUpManageNo,
warehouseId,
expectDeliveryTime,
currencyCode,
detailsList,
} = formData.value
const warehouseName = props.warehouseList.find(
(item) => item.id === warehouseId,
)?.name
try {
const res = await supplierDispatchApi({
manageId: currentRow.value?.id as number,
manageNo: stockingUpManageNo as string,
warehouseId: warehouseId as number,
warehouseName: warehouseName as string,
expectDeliveryTime: expectDeliveryTime as string,
currencyCode: currencyCode as string,
detailsList:
detailsList?.map((e) => ({
...e,
shipmentQuantity: Number(e.currentShipQuantity ?? 0),
currentShipQuantity: undefined,
remainingQuantity: undefined,
})) || [],
})
if (res.code !== 200) return
ElMessage.success('发货成功')
emit('refresh')
visible.value = false
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
const open = async (row: TableData) => {
if (!row?.id) return
selectedProducts.value = []
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await getStockingOrderDetailByIdApi(row.id)
if (res.code !== 200) {
ElMessage.warning(res.message)
return
}
const {
stockingUpManageNo,
warehouseId,
detailsList,
warehouseName,
currencyCode,
} = res.data
formData.value = {
stockingUpManageNo,
warehouseId,
warehouseName,
currencyCode,
detailsList: detailsList?.map((item) => ({
...item,
remainingQuantity: new BigNumber(item.buyAmount || 0)
.minus(item.shipmentQuantity || 0)
.toNumber(),
currentShipQuantity: undefined,
})),
}
currentRow.value = row
visible.value = true
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
type StockingOrderProductWithError = StockingOrderProduct & {
_currentShipQuantityError?: string
}
const validateField = (
row: StockingOrderProductWithError,
field: 'currentShipQuantity',
) => {
const value = row[field]
const strValue = value === 0 || value === '0' ? '0' : String(value ?? '')
// const fieldLabel = '本次发货数量'
if (!strValue) {
if (field === 'currentShipQuantity')
row._currentShipQuantityError = '必填项'
} else if (!/^\d+$/.test(strValue)) {
if (field === 'currentShipQuantity')
row._currentShipQuantityError = '只能输入正整数'
} else {
if (field === 'currentShipQuantity') row._currentShipQuantityError = ''
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.supplier-dispatch-order-page {
.form-section {
margin-bottom: 16px;
.form-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.form-item {
flex: 1;
min-width: 280px;
max-width: 350px;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-date-editor) {
width: 100%;
}
}
.table-section {
height: 450px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.action-section {
display: flex;
align-items: center;
gap: 10px;
margin-top: 16px;
}
}
.dialog-footer {
text-align: center;
}
.field-error {
color: #f56c6c;
font-size: 12px;
line-height: 1;
margin-top: 2px;
}
</style>
<style>
.over-quantity-alert-box {
width: 1000px !important;
max-width: 90vw !important;
}
.over-quantity-alert-box .el-message-box__message {
word-break: break-all;
line-height: 1.6;
padding: 10px 0;
}
</style>
<template>
<div class="stocking-order-page card h-100 flex overflow-hidden">
<div class="order-status">
<ElTree
ref="treeRef"
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="[]"
:highlight-current="true"
node-key="code"
:data="treeData"
:props="{ children: 'children', label: 'name' }"
@node-click="nodeClick"
>
<template #default="{ data }">
<div class="tree-node">
<div class="tree-node-label">{{ data.name }}</div>
<div
v-if="data.countQuantity || data.countQuantity === 0"
class="tree-node-count"
>
{{ `(${data.countQuantity})` }}
</div>
</div>
</template>
</ElTree>
</div>
<div class="order-content flex-1 flex-column overflow-hidden">
<div class="header">
<div class="header-search-form">
<ElForm
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
class="search-form"
>
<ElFormItem label="备货仓库" prop="warehouseId">
<ElSelect
v-model="searchForm.warehouseId"
placeholder="请选择"
filterable
multiple
collapse-tags
collapse-tags-tooltip
clearable
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="订单时间" prop="orderTime">
<ElDatePicker
v-model="rangeTime"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</ElFormItem>
<ElFormItem label="备货单号" prop="stockingUpManageNo">
<ElInput
v-model="searchForm.stockingUpManageNo"
clearable
placeholder="备货单号"
/>
</ElFormItem>
<ElFormItem label="供应商" prop="supplierId">
<ElSelect
v-model="searchForm.supplierId"
placeholder="请选择"
filterable
multiple
collapse-tags
collapse-tags-tooltip
clearable
>
<ElOption
v-for="item in supplierList"
:key="item.id"
:label="item.supplierName"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="发货状态" prop="shippingStatusList">
<ElSelect
v-model="searchForm.shippingStatusList"
placeholder="请选择"
filterable
multiple
collapse-tags
collapse-tags-tooltip
clearable
>
<ElOption
v-for="item in deliveryStatusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="库存SKU" prop="warehouseSku">
<ElInput
v-model="searchForm.warehouseSku"
clearable
placeholder="库存SKU"
/>
</ElFormItem>
<ElFormItem label="备货员" prop="stockingUpUserId">
<ElSelect
v-model="searchForm.stockingUpUserId"
placeholder="请选择"
filterable
multiple
collapse-tags
collapse-tags-tooltip
clearable
>
<ElOption
v-for="item in stockKeeperList"
:key="item.id"
:label="item.account"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="商品名称" prop="warehouseSkuName">
<ElInput
v-model="searchForm.warehouseSkuName"
clearable
placeholder="商品名称"
/>
</ElFormItem>
<ElFormItem label="备注" prop="remark">
<ElInput
v-model="searchForm.remark"
clearable
placeholder="备注或内部便签"
/>
</ElFormItem>
<ElFormItem class="form-item-buttons">
<ElButton type="primary" @click="() => search()">查询</ElButton>
<ElButton @click="() => resetSearchForm()">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="header-operation">
<span v-if="status === 'PENDING_SUBMIT'" class="item">
<ElButton type="success" @click="handleAddOrder">新增</ElButton>
</span>
<span v-if="status === 'PENDING_SUBMIT'" class="item">
<ElButton type="primary" @click="handleSubmitAudit"
>提交审核</ElButton
>
</span>
<span v-if="status !== 'COMPLETED' && status !== 'CANCELLED'" class="item">
<ElButton type="success" @click="addInternalTag"
>添加内部便签</ElButton
>
</span>
<span
v-if="['PENDING_SUBMIT', 'STOCKING_UP'].includes(status)"
class="item"
>
<ElButton type="warning" @click="handleCancelOrder">取消</ElButton>
</span>
<span v-if="status === 'PENDING_SUBMIT'" class="item">
<ElButton type="danger" @click="handleDeleteOrder">删除</ElButton>
</span>
</div>
</div>
<div class="table-content">
<SplitDiv size="60">
<template #top>
<div class="table-list flex-1 overflow-hidden">
<TableView
ref="tableRef"
highlight-current-row
:selectionable="true"
:serial-numberable="true"
:columns="tableColumns"
:paginated-data="tableData"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
/>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 300, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</template>
<template #bottom>
<StockingOrderDetailTabs :selected-row="selectedRow" />
</template>
</SplitDiv>
</div>
</div>
<AddStockingOrderDialog
ref="addStockingOrderDialogRef"
:supplier-list="supplierList"
:stock-keeper-list="stockKeeperList"
:warehouse-list="warehouseList"
:currency-list="currencyList"
@refresh="onRefresh"
/>
<SupplierDispatchOrderDialog
ref="supplierDispatchOrderDialogRef"
:warehouse-list="warehouseList"
@refresh="onRefresh"
/>
</div>
</template>
<script setup lang="tsx">
import { computed, ref } from 'vue'
import dayjs from 'dayjs'
import {
getStockingOrderListApi,
stockingCompleteApi,
submitStockingOrderAuditApi,
deleteStockingOrderApi,
cancelStockingOrderApi,
addStockingOrderInternalTagApi,
} from '@/api/supplier/stockingOrder'
import TableView from '@/components/TableView.vue'
import StockingOrderDetailTabs from './StockingOrderDetailTabs.vue'
import AddStockingOrderDialog from './AddStockingOrderDialog.vue'
import SupplierDispatchOrderDialog from './SupplierDispatchOrderDialog.vue'
import {
TreeData,
SearchForm,
TableData,
} from '@/types/api/supply/stockingOrder'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { CurrencyCodeData, SupplierItem, WarehouseListData } from '@/types'
import {
getBaseCurrencyInfoApi,
getEmployeeListApi,
getSupplierListApi,
loadWarehouseListApi,
} from '@/api/common'
import { getStockingOrderStatusTreeApi } from '@/api/supplier/stockingOrder'
import { userData } from '@/types/api/user'
import { ElButton, ElTag } from 'element-plus'
const deliveryStatusList = ref([
{ label: '待发货', value: '0' },
{ label: '部分发货', value: '1' },
{ label: '全部发货', value: '2' },
])
const tableColumns = computed(() => {
return [
{
label: '备货单号',
prop: 'stockingUpManageNo',
minWidth: 200,
align: 'center',
render: (row: TableData) => {
const isDelayed =
row.expectDeliveryTime &&
dayjs()
.startOf('day')
.diff(dayjs(row.expectDeliveryTime).startOf('day'), 'day') > 0
return (
<div style="position: relative; width: 100%;">
{isDelayed && (
<ElTag
type="danger"
size="small"
style="position: absolute; left: -10px; top: 2px; transform: scale(0.8);"
>
延期
</ElTag>
)}
<span>{row.stockingUpManageNo}</span>
</div>
)
},
},
{
label: '备货仓库',
prop: 'warehouseName',
width: 120,
align: 'center',
},
{
label: '供应商',
prop: 'supplierName',
width: 120,
align: 'center',
},
{
label: '订单状态',
prop: 'status',
width: 100,
align: 'center',
render: (row: TableData) => {
if (row.status === 'PENDING_SUBMIT') {
return <ElTag type="primary">待提交</ElTag>
}
if (row.status === 'PENDING_AUDIT') {
return <ElTag type="warning">待审核</ElTag>
}
if (row.status === 'STOCKING_UP') {
return <ElTag type="success">备货中</ElTag>
}
if (row.status === 'COMPLETED') {
return <ElTag type="success">备货完成</ElTag>
}
if (row.status === 'CANCELLED') {
return <ElTag type="danger">已取消</ElTag>
}
},
},
{
label: '期望交货日期',
prop: 'expectDeliveryTime',
width: 160,
align: 'center',
},
{
label: 'SKU个数',
prop: 'skuTotal',
width: 100,
align: 'right',
},
{
label: '备货总数',
prop: 'total',
width: 100,
align: 'right',
},
{
label: '备货总额',
prop: 'totalPrice',
width: 120,
align: 'right',
},
{
label: '币种',
prop: 'currencyCode',
width: 80,
align: 'center',
},
{
label: '发货状态',
prop: 'shippingStatus',
width: 100,
align: 'center',
render: (row: TableData) => {
return (
<span>
{row.shippingStatus && (
<ElTag type="primary">
{getDeliveryStatusName(row.shippingStatus)}
</ElTag>
)}
</span>
)
},
},
{
label: '备货员',
prop: 'stockingUpUserName',
width: 100,
align: 'center',
},
{
label: '最后交货日期',
prop: 'lastDeliveryTime',
width: 160,
align: 'center',
},
{
label: '订单完成日期',
prop: 'completeTime',
width: 160,
align: 'center',
},
{
label: '延误天数',
prop: 'delayDays',
width: 100,
align: 'center',
render: (row: TableData) => {
if (!row.expectDeliveryTime) return <span>-</span>
let delayDays: number
if (row.completeTime) {
delayDays = dayjs(row.completeTime)
.startOf('day')
.diff(dayjs(row.expectDeliveryTime).startOf('day'), 'day')
} else {
delayDays = dayjs()
.startOf('day')
.diff(dayjs(row.expectDeliveryTime).startOf('day'), 'day')
}
if (delayDays > 0 && row.status !== 'CANCELLED') {
return (
<span style="color: #f56c6c; font-weight: bold;">{delayDays}</span>
)
}
return <span>-</span>
},
},
{
label: '制单人',
prop: 'createUserName',
width: 100,
align: 'center',
},
{
label: '审核人',
prop: 'auditName',
width: 100,
align: 'center',
},
{
label: '制单时间',
prop: 'createTime',
width: 160,
align: 'center',
sortable: true,
},
{
label: '审核时间',
prop: 'auditTime',
width: 160,
align: 'center',
sortable: true,
},
{
label: '备注',
prop: 'remark',
minWidth: 150,
align: 'left',
showOverflowTooltip: true,
},
{
label: '操作',
prop: 'operation',
width: 180,
align: 'center',
fixed: 'right',
render: (row: TableData) => {
return (
<span>
{row.status === 'PENDING_SUBMIT' && (
<ElButton
link
type="warning"
onClick={() => handleOrder(row, 'edit')}
>
编辑
</ElButton>
)}
{row.status === 'PENDING_AUDIT' && (
<ElButton
link
type="success"
onClick={() => handleOrder(row, 'audit')}
>
审核
</ElButton>
)}
{row.status === 'STOCKING_UP' && (
<ElButton
link
type="primary"
onClick={() => handleSupplierDispatchOrder(row)}
>
供应商发货
</ElButton>
)}
{row.status === 'STOCKING_UP' && (
<ElButton
link
type="warning"
onClick={() => stockingComplete(row)}
>
备货完成
</ElButton>
)}
</span>
)
},
},
]
})
const treeData = ref<TreeData[]>([])
const status = ref<string>('PENDING_SUBMIT')
const selectedRow = ref<TableData | null>(null)
const warehouseList = ref<WarehouseListData[]>([])
const supplierList = ref<SupplierItem[]>([])
const stockKeeperList = ref<userData[]>([])
const currencyList = ref<CurrencyCodeData[]>([])
const selection = ref<TableData[]>([])
const rangeTime = ref<string[]>([])
const [searchForm, resetSearchForm] = useValue<SearchForm>({} as SearchForm)
const treeRef = ref()
const tableRef = ref()
const supplierDispatchOrderDialogRef = ref()
const addStockingOrderDialogRef = ref()
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
if (res.code !== 200) return
warehouseList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const loadEmployeeList = async () => {
try {
const res = await getEmployeeListApi()
if (res.code !== 200) return
stockKeeperList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const loadSupplierList = async () => {
try {
const res = await getSupplierListApi()
if (res.code !== 200) return
supplierList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const loadCurrencyList = async () => {
try {
const res = await getBaseCurrencyInfoApi()
if (res.code !== 200) return
currencyList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const loadTreeData = async () => {
try {
const res = await getStockingOrderStatusTreeApi()
if (res.code !== 200) return
treeData.value = [{ code: '-1', name: '全部', children: res.data }]
await nextTick(() => {
treeRef.value!.setCurrentKey(status.value, true)
})
} catch (e) {
console.error(e)
}
}
const nodeClick = (data: TreeData) => {
status.value = data.code
search()
}
const getDeliveryStatusName = (shippingStatus: number) => {
if (!shippingStatus) return ''
return deliveryStatusList.value.find(
(item) => item.value === shippingStatus.toString(),
)?.label
}
const {
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList<TableData>({
query: (page, pageSize) =>
getStockingOrderListApi(
{
...searchForm.value,
startDate: rangeTime.value?.[0],
endDate: rangeTime.value?.[1],
status: status.value === '-1' ? undefined : status.value,
},
page,
pageSize,
).then(async (res) => {
const { records } = res.data
await nextTick(() => {
tableRef.value.setCurrentRow(records[0])
selectedRow.value = (records as never)[0]
})
return res.data
}),
initLoad: true,
})
const handleSelectionChange = (val: TableData[]) => {
selection.value = val
}
const handleRowClick = (row: TableData) => {
if (row) {
selectedRow.value = row
} else {
selectedRow.value = null
}
}
const handleOrder = (row: TableData, type: string) => {
addStockingOrderDialogRef.value.open(row, type)
}
const handleSupplierDispatchOrder = (row: TableData) => {
supplierDispatchOrderDialogRef.value.open(row)
}
const stockingComplete = async (row: TableData) => {
try {
await ElMessageBox.confirm(
'备货完成将把未完成入库申请单自动取消,是否继续对选中的订单操作备货完成?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
)
} catch {
return
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await stockingCompleteApi(row.id)
if (res.code !== 200) return
ElMessage.success('备货完成成功')
search()
loadTreeData()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleAddOrder = () => {
addStockingOrderDialogRef.value.open(null, 'add')
}
const handleSubmitAudit = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择要操作的数据')
}
try {
await ElMessageBox.confirm('确定将选中的订单提交至审核?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await submitStockingOrderAuditApi(
selection.value.map((el) => el.id).join(','),
)
if (res.code !== 200) return
ElMessage.success('提交审核成功')
search()
loadTreeData()
} catch (e) {
console.error(e)
}
}
const handleDeleteOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择一条数据')
}
if (selection.value.length !== 1) {
return ElMessage.warning('不支持批量删除!')
}
try {
await ElMessageBox.confirm('确定删除选中的订单?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await deleteStockingOrderApi(selection.value[0].id)
if (res.code !== 200) return
ElMessage.success('删除成功')
onRefresh()
} catch (e) {
console.error(e)
}
}
const handleCancelOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择一条数据')
}
if (selection.value.length !== 1) {
return ElMessage.warning('不支持批量取消!')
}
ElMessageBox.prompt('', '取消原因', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请输入取消原因',
inputPattern: /.+/,
inputErrorMessage: '取消原因不能为空',
}).then(async ({ value }: { value: string }) => {
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await cancelStockingOrderApi(selection.value[0].id, value)
if (res.code !== 200) return
ElMessage.success('取消成功')
onRefresh()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
})
}
const addInternalTag = async () => {
if (selection.value.length === 0) {
return ElMessage({
message: '请选择订单',
type: 'warning',
offset: window.innerHeight / 2,
})
}
ElMessageBox.prompt('', '添加内部便签', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请输入内部便签',
inputPattern: /.+/,
inputErrorMessage: '内部便签不能为空',
}).then(async ({ value }: { value: string }) => {
try {
const res = await addStockingOrderInternalTagApi(
value,
selection.value.map((item) => item.id),
)
ElMessage.success(res.message)
search()
} catch (e) {
// showError(e)
}
})
}
const onRefresh = () => {
search()
loadTreeData()
}
onMounted(() => {
loadWarehouseList()
loadEmployeeList()
loadSupplierList()
loadTreeData()
loadCurrencyList()
})
</script>
<style lang="scss" scoped>
.order-status {
width: 160px;
:deep(.el-tree-node__content) {
height: 30px;
line-height: 30px;
}
:deep(.el-tree-node__label) {
font-size: 13px;
cursor: pointer;
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.is-current) {
.tree-node-label,
.tree-node-count {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.tree-node-label,
.tree-node-count {
background-color: transparent !important;
color: black !important;
}
}
}
:deep(.el-tree-node__label) {
font-size: 13px;
color: rgb(96, 98, 102);
cursor: pointer;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.el-tree-node__label) {
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
:deep(.is-current) {
.el-tree-node__label {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.el-tree-node__label {
background-color: transparent !important;
color: black !important;
}
}
}
}
.tree-node {
display: flex;
color: #333;
font-weight: 500;
}
.table-content {
flex: 1;
margin-top: 10px;
overflow: hidden;
:deep(#top) {
height: 100%;
}
}
.search-form {
display: grid;
grid-template-columns: repeat(6, 1fr);
align-items: flex-start;
:deep(.el-form-item) {
margin-right: 10px;
margin-bottom: 10px;
}
}
</style>
......@@ -190,10 +190,10 @@ import {
getSupplierDetailApi,
getPropertyByCateIdApi,
getProductInfoBySpuApi,
getBaseCurrencyInfoApi,
addSupplierApi,
updateSupplierApi,
} from '@/api/supplier/supplierManagement.ts'
import { getBaseCurrencyInfoApi } from '@/api/common'
import Dialog from './components/dialog.tsx'
import CustomizeForm from '@/components/CustomizeForm.tsx'
......
......@@ -856,9 +856,7 @@
maxlength="1000"
show-word-limit
/>
<div style="margin-top: 12px; color: #777">
多个字段使用,隔开
</div>
<div style="margin-top: 12px; color: #777">多个字段使用","隔开</div>
</div>
<template #footer>
<span class="dialog-footer">
......
......@@ -125,7 +125,7 @@
提交审核
</el-button>
</ElFormItem>
<ElFormItem v-if="nodeCode === 'PENDING_AUDIT'">
<ElFormItem v-if="nodeCode === 'PENDING_SUBMIT'">
<el-button type="danger" @click="auditOrder('invalid')">
作废
</el-button>
......@@ -184,7 +184,7 @@
align="center"
></ElTableColumn>
<ElTableColumn
label="备货单号"
label="来源单号"
show-overflow-tooltip
prop="sourceOn"
width="130"
......@@ -278,7 +278,16 @@
label="操作"
>
<template #default="{ row }">
<ElButton type="primary" link @click="addDialog(2, row)"
<ElButton
:disabled="row.source === 'StockingUpWarehouseApply'"
:title="
row.source === 'StockingUpWarehouseApply'
? '来源为入库申请单,不允许编辑!请先操作取消,再重新提交入库单'
: ''
"
type="primary"
link
@click="addDialog(2, row)"
>编辑
</ElButton>
</template>
......@@ -299,7 +308,7 @@
</div>
</template>
<template #bottom>
<el-tabs v-model="tabsValue" @tab-click="tabsClick">
<el-tabs v-loading="showDetailLoading" v-model="tabsValue" @tab-click="tabsClick">
<el-tab-pane name="0" label="入库商品">
<div class="table-wrap">
<ElTable size="small" :data="detailList" height="100%" border>
......@@ -878,9 +887,7 @@
maxlength="1000"
show-word-limit
/>
<div style="margin-top: 12px; color: #777">
多个字段使用,隔开
</div>
<div style="margin-top: 12px; color: #777">多个字段使用","隔开</div>
</div>
<template #footer>
<span class="dialog-footer">
......@@ -919,7 +926,7 @@ import {
} from '@/api/warehouse'
import { filePath } from '@/api/axios.ts'
import BigNumber from 'bignumber.js'
import { ref, onMounted, watch, nextTick } from 'vue'
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import 'element-plus/dist/index.css'
import {
warehouseSearchForm,
......@@ -1072,6 +1079,10 @@ const tabsValue = ref<string>('0')
const singleTableRef = ref<InstanceType<typeof ElTable>>()
const currentRow = ref<InterWarehousePage | null>(null)
const logList = ref<LogListData[]>([])
const detailLoading = ref(false)
const showDetailLoading = computed(() => {
return detailLoading.value && !newDialogVisible.value
})
const rules = {
warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }],
}
......@@ -1584,10 +1595,21 @@ const auditOrder = (key: string) => {
dataVersion,
}),
)
await auditOrderApi(url, data)
ElMessage.success('操作成功')
search()
await getTreeNum()
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
await auditOrderApi(url, data)
ElMessage.success('操作成功')
search()
await getTreeNum()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
})
}
const rejectedInRecord = () => {
......@@ -1826,6 +1848,7 @@ const nodeClick = (data: InterWarehouseTree) => {
// total: 0,
// })
const searchDetail = async () => {
detailLoading.value = true
try {
const res = await getWarehouseInRecordDetailApi(currentRow.value?.id)
res.data.productList.forEach(item => {
......@@ -1835,14 +1858,19 @@ const searchDetail = async () => {
detailList.value = res.data?.productList || []
} catch (e) {
console.error(e)
} finally {
detailLoading.value = false
}
}
const getLogList = async () => {
detailLoading.value = true
try {
const res = await getInRecordLogApi(currentRow.value?.id)
logList.value = res.data
} catch (e) {
console.error(e)
} finally {
detailLoading.value = false
}
}
const locationList = ref<ILocation[]>([])
......@@ -2106,7 +2134,6 @@ $border: solid 1px #ddd;
::v-deep(.el-tree-node) {
cursor: pointer;
margin-bottom: 5px;
}
::v-deep(.el-tree-node__label) {
......
<template>
<ElTabs
v-model="activeTab"
v-loading="loading"
class="detail-tabs"
@tab-click="handleTabClick"
>
<ElTabPane name="stockProducts" label="入库商品">
<div class="detail-table-content">
<TableView
:serial-numberable="true"
:columns="stockProductsColumns"
:paginated-data="stockProductsData"
/>
</div>
</ElTabPane>
<ElTabPane name="relatedDocuments" label="关联单据">
<div class="detail-table-content">
<TableView
:serial-numberable="true"
:columns="relatedDocumentsColumns"
:paginated-data="relatedDocumentsData"
/>
</div>
</ElTabPane>
<ElTabPane name="operationLog" label="操作日志">
<div class="detail-table-content">
<LogList :log-list="operationLogData" />
<div class="empty-content">暂无数据</div>
</div>
</ElTabPane>
</ElTabs>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
import TableView from '@/components/TableView.vue'
import {
RelatedDocumentList,
StockingApplyOrderDetailList,
TableData,
} from '@/types/api/warehouse/stockingApplyOrder'
import ImageView from '@/components/ImageView.vue'
import {
getStockingApplyOrderDetailListByIdApi,
getStockingApplyOrderLogListByIdApi,
getStockingApplyOrderRelatedDocumentListByIdApi,
} from '@/api/warehouse/stockingApplyOrder'
import LogList from '@/components/LogList.vue'
import { LogListData } from '@/types/api/supply/stockingOrder'
const relatedDocumentsColumns = computed(() => {
return [
{
label: '关联单据',
prop: 'inNo',
align: 'center',
},
{
label: '关联单号',
prop: 'sourceOn',
align: 'center',
},
{
label: '制单人',
width: 120,
prop: 'checkerName',
align: 'center',
},
{
label: '制单时间',
width: 160,
prop: 'createTime',
align: 'center',
},
{
label: 'SKU个数',
width: 120,
align: 'right',
prop: 'skuAmount',
},
{
label: '入库数量',
width: 120,
align: 'right',
prop: 'total',
},
]
})
const stockProductsColumns = computed(() => {
return [
{
label: 'SKU图片',
prop: 'skuImage',
width: 100,
align: 'center',
render: (item: StockingApplyOrderDetailList) => (
<ImageView src={item.warehouseSkuImage} width="50px" height="50px" />
),
},
{
label: '商品名称',
prop: 'warehouseSkuName',
minWidth: 200,
align: 'left',
},
{
label: '款号',
prop: 'productNo',
width: 120,
align: 'center',
},
{
label: '库存SKU',
prop: 'warehouseSku',
width: 180,
align: 'center',
},
{
label: '申请数量',
prop: 'shipmentQuantity',
width: 100,
align: 'right',
},
{
label: '入库数量',
prop: 'buyStored',
width: 120,
align: 'right',
},
{
label: '不良品数',
prop: 'rejectsAmount',
width: 120,
align: 'right',
},
{
label: '实际质检数',
prop: 'finallyShipmentQuantity',
width: 120,
align: 'right',
},
{
label: '入库待审核数',
prop: 'storedPending',
width: 120,
align: 'right',
},
{
label: '币种',
prop: 'currencyCode',
width: 100,
align: 'center',
},
{
label: '入库单价',
prop: 'price',
width: 100,
align: 'right',
},
]
})
const props = defineProps<{
selectedRow: TableData | null
}>()
const stockProductsData = ref<StockingApplyOrderDetailList[]>([])
const relatedDocumentsData = ref<RelatedDocumentList[]>([])
const activeTab = ref('stockProducts')
const loading = ref(false)
const operationLogData = ref<LogListData[]>([])
watch(
() => props.selectedRow,
(newVal) => {
if (newVal) {
if (activeTab.value === 'stockProducts') {
loadStockProducts(newVal)
} else if (activeTab.value === 'relatedDocuments') {
loadRelatedDocuments(newVal)
} else if (activeTab.value === 'operationLog') {
loadOperationLog(newVal)
}
} else {
stockProductsData.value = []
relatedDocumentsData.value = []
}
},
)
const loadStockProducts = async (row: TableData) => {
loading.value = true
try {
const res = await getStockingApplyOrderDetailListByIdApi(row.id)
if (res.code !== 200) return
stockProductsData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const loadRelatedDocuments = async (row: TableData) => {
loading.value = true
try {
const res = await getStockingApplyOrderRelatedDocumentListByIdApi(
row.warehouseApplyNo,
)
if (res.code !== 200) return
relatedDocumentsData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const loadOperationLog = async (row: TableData) => {
try {
const res = await getStockingApplyOrderLogListByIdApi(row.id)
if (res.code !== 200) return
operationLogData.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const handleTabClick = (tab: TabsPaneContext) => {
activeTab.value = tab.paneName as string
if (activeTab.value === 'stockProducts') {
loadStockProducts(props.selectedRow as TableData)
} else if (activeTab.value === 'relatedDocuments') {
loadRelatedDocuments(props.selectedRow as TableData)
} else if (activeTab.value === 'operationLog') {
loadOperationLog(props.selectedRow as TableData)
}
}
</script>
<style lang="scss" scoped>
.detail-tabs {
height: 100%;
display: flex;
:deep(.el-tab-pane) {
height: 100%;
overflow: hidden;
}
}
.detail-table-content {
height: 100%;
overflow: hidden;
}
.empty-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
font-size: 14px;
}
</style>
<template>
<ElDialog
v-model="visible"
title="提交入库"
width="1400px"
top="10vh"
:close-on-click-modal="false"
destroy-on-close
>
<div class="submit-warehousing">
<ElForm
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
inline
>
<div class="form-row">
<ElFormItem label="入库申请单" class="form-item">
<ElInput
v-model="formData.warehouseApplyNo"
disabled
placeholder="自动带出"
/>
</ElFormItem>
<ElFormItem label="入库仓库" class="form-item">
<ElInput
v-model="formData.warehouseName"
disabled
placeholder="自动带出"
/>
</ElFormItem>
<ElFormItem label="质检员" prop="checkerUserId" class="form-item">
<ElSelect
v-model="formData.checkerUserId"
placeholder="请选择"
filterable
clearable
>
<ElOption
v-for="item in userList"
:key="item.id"
:label="item.account"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="备注" class="form-item remark-item">
<ElInput v-model="formData.remark" placeholder="请输入" clearable />
</ElFormItem>
</div>
</ElForm>
<div class="table-section">
<TableView
:selectionable="true"
:serial-numberable="true"
:data="formData.detailsList"
:columns="tableColumns"
@selection-change="handleSelectionChange"
>
<template #buyStored="{ row }">
<ElInput
v-model="row.buyStored"
clearable
size="small"
style="width: 70px"
@blur="validateField(row, 'buyStored')"
/>
<div v-if="row._buyStoredError" class="field-error">
{{ row._buyStoredError }}
</div>
</template>
<template #rejectsAmount="{ row }">
<ElInput
v-model="row.rejectsAmount"
clearable
size="small"
style="width: 70px"
@blur="validateField(row, 'rejectsAmount')"
/>
<div v-if="row._rejectsAmountError" class="field-error">
{{ row._rejectsAmountError }}
</div>
</template>
<template #locationName="{ row }">
<ElSelect
v-model="row.locationId"
placeholder="请选择"
clearable
style="width: 100px"
>
<ElOption
v-for="item in allLocationList"
:key="item.id"
:label="item.locationCode"
:value="item.id"
/>
</ElSelect>
</template>
</TableView>
</div>
<div class="batch-action">
<ElButton type="primary" @click="batchSetBuyStored">
申请数量>>本次入库数量
</ElButton>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="handleCancel">取消</ElButton>
<ElButton type="primary" @click="handleSubmit"> 入库 </ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { getUserListApi, LocationInfoGetAll } from '@/api/common'
import { loactionData } from '@/api/warehouse'
import {
getLocationListApi,
getStockingApplyOrderDetailById,
submitWarehousingApi,
} from '@/api/warehouse/stockingApplyOrder'
import {
StockingApplyOrderDetailData,
StockingApplyOrderDetailList,
LocationDataBySkuData,
} from '@/types/api/warehouse/stockingApplyOrder'
import { userData } from '@/types/api/user'
import { BigNumber } from 'bignumber.js'
const emit = defineEmits<{
(e: 'refresh'): void
}>()
const visible = ref(false)
const formRef = ref<FormInstance>()
const userList = ref<userData[]>([])
const locationList = ref<LocationDataBySkuData[]>([])
const selectedRows = ref<StockingApplyOrderDetailList[]>([])
const formData = ref<StockingApplyOrderDetailData>({
detailsList: [],
id: 0,
warehouseApplyNo: '',
warehouseName: '',
warehouseId: undefined,
checkerUserId: undefined,
remark: '',
})
type StockingApplyOrderDetailListWithError = StockingApplyOrderDetailList & {
_buyStoredError?: string
_rejectsAmountError?: string
}
const tableColumns = ref([
{
label: 'SKU图片',
prop: 'warehouseSkuImage',
width: 90,
align: 'center',
render: (row: StockingApplyOrderDetailList) => (
<el-image
preview-teleported
src={row.warehouseSkuImage}
preview-src-list={[row.warehouseSkuImage]}
z-index={9999}
style="width: 60px;height: 60px;text-align: center;"
/>
),
},
{
label: '商品名称',
prop: 'warehouseSkuName',
align: 'left',
},
{
label: '款号',
width: 140,
prop: 'productNo',
align: 'center',
},
{
label: '库存SKU',
prop: 'warehouseSku',
width: 180,
align: 'center',
},
{
label: '申请数量',
width: 120,
prop: 'shipmentQuantity',
align: 'right',
},
{
label: '入库数量',
prop: 'buyStored',
width: 100,
align: 'right',
slot: 'buyStored',
},
{
label: '不良品数',
prop: 'rejectsAmount',
width: 100,
align: 'right',
slot: 'rejectsAmount',
},
{
label: '实际质检数',
prop: 'finallyShipmentQuantity',
width: 100,
align: 'right',
render: (row: StockingApplyOrderDetailList) => {
const buyStored = new BigNumber(row.buyStored || 0).toNumber()
const rejectsAmount = new BigNumber(row.rejectsAmount || 0).toNumber()
return new BigNumber(buyStored).plus(rejectsAmount).toNumber()
},
},
{
label: '库位',
prop: 'locationId',
width: 140,
align: 'right',
slot: 'locationName',
},
])
const formRules: FormRules = {
checkerUserId: [
{ required: true, message: '请选择质检员', trigger: 'change' },
],
}
const loadUserList = async () => {
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await getUserListApi()
if (res.code === 200) {
userList.value = res.data || []
}
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const loadDetailList = async (id: number) => {
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await getStockingApplyOrderDetailById(id)
if (res.code !== 200) return
if (!res.data.checkerUserId) {
res.data.checkerUserId = JSON.parse(
localStorage.getItem('user') || '{}',
).id
}
const skuList = res.data.detailsList.map((item) => item.warehouseSku)
try {
const locationRes = await getLocationListApi(
skuList.join(','),
res.data.warehouseId as string | number,
)
if (locationRes.code !== 200) return
const { detailsList } = res.data
if (detailsList && detailsList.length > 0) {
if (detailsList.some((e) => (e.storedPending as number) > 0)) {
return ElMessageBox.alert(
'已存在待审核的入库单,请勿重复操作!',
'提示',
{
confirmButtonText: '确定',
type: 'warning',
},
)
}
}
locationList.value = locationRes.data || []
res.data.detailsList.forEach((item) => {
if (!item.buyStored) {
item.buyStored = item.shipmentQuantity
}
if (locationList.value.length > 0) {
const location = locationList.value.find(
(location) => location.warehouseSku === item.warehouseSku,
)
if (location) {
item.locationId = location.locationId
}
}
})
} catch (e) {
console.error(e)
}
formData.value = res.data
visible.value = true
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const allLocationList = ref<loactionData[]>([])
const loadAllLocationList = async (
warehouseId: string | number | undefined,
) => {
if (!warehouseId) return
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await LocationInfoGetAll(warehouseId)
if (res.code !== 200) return
allLocationList.value = res.data || []
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const open = async (row: {
id: number
warehouseId: string | number | undefined
}) => {
selectedRows.value = []
loadUserList()
await loadAllLocationList(row.warehouseId)
loadDetailList(row.id)
}
const handleCancel = () => {
visible.value = false
}
const handleSelectionChange = (rows: StockingApplyOrderDetailList[]) => {
selectedRows.value = rows
}
const batchSetBuyStored = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择数据')
return
}
selectedRows.value.forEach((row) => {
row.buyStored = row.shipmentQuantity
})
}
const validateField = (
row: StockingApplyOrderDetailListWithError,
field: 'buyStored' | 'rejectsAmount',
) => {
const value = row[field]
const strValue = value === 0 || value === '0' ? '0' : String(value ?? '')
const fieldLabel = field === 'buyStored' ? '入库数量' : '不良品数'
if (!strValue) {
if (field === 'buyStored') row._buyStoredError = '必填项'
} else if (!/^\d+$/.test(strValue)) {
if (field === 'buyStored') row._buyStoredError = '只能输入正整数'
else if (field === 'rejectsAmount')
row._rejectsAmountError = '只能输入正整数'
} else if (Number(strValue) === 0) {
if (field === 'buyStored') row._buyStoredError = `${fieldLabel}不能为0`
else if (field === 'rejectsAmount') row._rejectsAmountError = ''
} else {
if (field === 'buyStored') row._buyStoredError = ''
else row._rejectsAmountError = ''
}
}
const validateDetailsList = (): boolean => {
if (!formData.value.detailsList?.length) {
return true
}
let isValid = true
formData.value.detailsList.forEach((row) => {
const extendedRow = row as StockingApplyOrderDetailListWithError
validateField(extendedRow, 'buyStored')
validateField(extendedRow, 'rejectsAmount')
if (extendedRow._buyStoredError || extendedRow._rejectsAmountError) {
isValid = false
}
})
return isValid
}
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (!validateDetailsList()) {
ElMessage.warning('请检查入库数量和不良品数是否正确填写')
return
}
const overQuantitySkus = []
for (const item of formData.value.detailsList) {
if (Number(item.buyStored) > Number(item.shipmentQuantity)) {
overQuantitySkus.push(item.warehouseSku)
}
const locationCode = allLocationList.value.find(
(location: loactionData) => location.id === Number(item.locationId),
)?.locationCode
item.locationCode = locationCode as string | undefined
const buyStored = new BigNumber(item.buyStored || 0).toNumber()
const rejectsAmount = new BigNumber(item.rejectsAmount || 0).toNumber()
item.finallyShipmentQuantity = new BigNumber(buyStored)
.plus(rejectsAmount)
.toNumber()
}
if (overQuantitySkus.length > 0) {
ElMessageBox.alert(
`<div style="line-height: 1.6;"><span style="white-space: nowrap; flex-shrink: 0;">库存SKU:</span><span style="word-break: break-all; margin-left: 4px;">${overQuantitySkus.join(
',',
)}</span><span style="margin-top: 12px;">入库数量不能大于申请数量</span></div>`,
'提示',
{
confirmButtonText: '确定',
type: 'warning',
customClass: 'over-quantity-alert-box',
dangerouslyUseHTMLString: true,
},
)
return
}
const checkerName = userList.value.find(
(item) => item.id === formData.value.checkerUserId,
)?.account
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await submitWarehousingApi({ ...formData.value, checkerName })
if (res.code === 200) {
ElMessage.success('入库成功')
visible.value = false
emit('refresh')
}
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.submit-warehousing {
height: 600px;
display: flex;
flex-direction: column;
overflow: hidden;
.form-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: flex-start;
}
.table-section {
flex: 1;
overflow: hidden;
}
.batch-action {
margin-top: 10px;
}
.form-item {
flex: 0 0 auto;
margin-right: 20px;
:deep(.el-input),
:deep(.el-select) {
width: 180px;
}
}
.remark-item {
flex: 1;
:deep(.el-input) {
width: 100%;
}
}
}
.dialog-footer {
text-align: center;
}
.field-error {
color: #f56c6c;
font-size: 12px;
line-height: 1;
margin-top: 2px;
text-align: center;
}
</style>
<style>
.over-quantity-alert-box {
width: 1000px !important;
max-width: 90vw !important;
}
.over-quantity-alert-box .el-message-box__message {
word-break: break-all;
line-height: 1.6;
padding: 10px 0;
}
</style>
<template>
<div class="stocking-application-order-page card h-100 flex overflow-hidden">
<div class="order-status">
<ElTree
ref="treeRef"
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="[]"
:highlight-current="true"
node-key="code"
:data="treeData"
:props="{ children: 'children', label: 'name' }"
@node-click="nodeClick"
>
<template #default="{ data }">
<div class="tree-node">
<div class="tree-node-label">{{ data.name }}</div>
<div
v-if="data.countQuantity || data.countQuantity === 0"
class="tree-node-count"
>
{{ `(${data.countQuantity})` }}
</div>
</div>
</template>
</ElTree>
</div>
<div class="order-content flex-1 flex-column overflow-hidden">
<div class="header">
<div class="header-search-form">
<ElForm
ref="searchFormRef"
:model="searchForm"
inline
label-width="100px"
class="search-form"
>
<ElFormItem label="入库申请单号" prop="warehouseApplyNo">
<ElInput
v-model="searchForm.warehouseApplyNo"
clearable
placeholder="入库申请单号"
/>
</ElFormItem>
<ElFormItem label="备货仓库" prop="warehouseId">
<ElSelect
v-model="searchForm.warehouseId"
placeholder="请选择"
filterable
multiple
collapse-tags
collapse-tags-tooltip
clearable
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="关联单号" prop="manageNo">
<ElInput
v-model="searchForm.manageNo"
clearable
placeholder="关联单号"
/>
</ElFormItem>
<ElFormItem label="库存SKU" prop="warehouseSku">
<ElInput
v-model="searchForm.warehouseSku"
clearable
placeholder="库存SKU"
/>
</ElFormItem>
<ElFormItem label="商品名称" prop="warehouseSkuName">
<ElInput
v-model="searchForm.warehouseSkuName"
clearable
placeholder="商品名称"
/>
</ElFormItem>
<ElFormItem class="form-item-buttons">
<ElButton type="primary" @click="() => search()">查询</ElButton>
<ElButton @click="() => resetSearchForm()">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="header-operation"></div>
</div>
<div class="table-content">
<SplitDiv size="60">
<template #top>
<div v-loading="loading" class="table-list flex-1 overflow-hidden">
<TableView
ref="tableRef"
highlight-current-row
:selectionable="true"
:serial-numberable="true"
:columns="tableColumns"
:paginated-data="tableData"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
>
<template #action="{ row }">
<ElButton
v-if="status === 'TO_BE_RECEIPT'"
link
type="success"
@click="warehouseReceipt(row)"
>仓库收货</ElButton
>
<ElButton
v-if="status === 'TO_BE_INSPECTED'"
link
type="warning"
@click="submitWarehousing(row)"
>提交入库</ElButton
>
</template>
</TableView>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 300, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</template>
<template #bottom>
<StockingApplyOrderDetailTabs :selected-row="selectedRow" />
</template>
</SplitDiv>
</div>
</div>
<!-- 提交入库弹框 -->
<SubmitWarehousingDialog ref="submitWarehousingDialogRef" @refresh="onRefresh" />
</div>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import { TreeData } from '@/types/api/supply/stockingOrder'
import { loadWarehouseListApi } from '@/api/common'
import { WarehouseListData } from '@/types'
import usePageList from '@/utils/hooks/usePageList'
import {
getStockingApplyOrderListApi,
getStockingApplyOrderTreeApi,
warehouseReceiptApi,
} from '@/api/warehouse/stockingApplyOrder'
import { TableData, SearchForm } from '@/types/api/warehouse/stockingApplyOrder'
import { useValue } from '@/utils/hooks/useValue'
import { ElTag, ElMessageBox, ElMessage } from 'element-plus'
import StockingApplyOrderDetailTabs from './StockingApplyOrderDetailTabs.vue'
import SubmitWarehousingDialog from './SubmitWarehousingDialog.vue'
const treeData = ref<TreeData[]>([])
const treeRef = ref()
const tableRef = ref()
const selectedRow = ref<TableData | null>(null)
const submitWarehousingDialogRef = ref<InstanceType<typeof SubmitWarehousingDialog>>()
const [searchForm, resetSearchForm] = useValue<SearchForm>({} as SearchForm)
const status = ref<string>('TO_BE_RECEIPT')
const warehouseList = ref<WarehouseListData[]>([])
const getStockingApplyOrderStatusName = (status: string) => {
const statusText = treeData.value[0]?.children?.find(
(item) => item.code === status,
)?.name
return statusText || ''
}
const tableColumns = computed(() => [
{
label: '入库申请单号',
prop: 'warehouseApplyNo',
width: 160,
align: 'center',
},
{
label: '关联单号',
prop: 'manageNo',
width: 160,
align: 'center',
},
{
label: '单据状态',
prop: 'storeStatus',
width: 120,
align: 'center',
render: (row: TableData) => {
return (
<ElTag type="primary">
{getStockingApplyOrderStatusName(row.storeStatus)}
</ElTag>
)
},
},
{
label: '入库仓库',
prop: 'warehouseName',
width: 160,
align: 'center',
},
{
label: 'SKU个数',
prop: 'skuTotal',
width: 100,
align: 'right',
},
{
label: '申请数量',
prop: 'total',
width: 100,
align: 'right',
},
{
label: '入库数量',
prop: 'buyStored',
width: 100,
align: 'right',
},
{
label: '预计到货日期',
prop: 'expectDeliveryTime',
width: 160,
align: 'center',
},
{
label: '实际到货日期',
prop: 'deliveryTime',
width: 160,
align: 'center',
},
{
label: '制单人',
prop: 'createUserName',
width: 100,
align: 'center',
},
{
label: '制单时间',
prop: 'createTime',
width: 160,
align: 'center',
},
{
label: '操作',
prop: 'action',
fixed: 'right',
width: 100,
align: 'center',
slot: 'action',
},
])
const selection = ref<TableData[]>([])
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
if (res.code !== 200) return
warehouseList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const loadTreeData = async () => {
try {
const res = await getStockingApplyOrderTreeApi()
if (res.code !== 200) return
treeData.value = [
{ code: '-1', name: '全部', children: res.data || [] } as TreeData,
]
await nextTick(() => {
treeRef.value!.setCurrentKey(status.value, true)
})
} catch (e) {
console.error(e)
}
}
const nodeClick = (data: TreeData) => {
status.value = data.code
search()
}
const {
currentPage,
pageSize,
loading,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList<TableData>({
query: (page, pageSize) =>
getStockingApplyOrderListApi(
{
...searchForm.value,
storeStatus: status.value === '-1' ? undefined : status.value,
},
page,
pageSize,
).then(async (res) => {
const { records } = res.data
await nextTick(() => {
tableRef.value.setCurrentRow(records[0])
selectedRow.value = (records as never)[0]
})
return res.data
}),
initLoad: true,
})
const handleRowClick = (row: TableData) => {
if (row) {
selectedRow.value = row
} else {
selectedRow.value = null
}
}
const handleSelectionChange = (val: TableData[]) => {
selection.value = val
}
const warehouseReceipt = async (row: TableData) => {
try {
await ElMessageBox.confirm(
`<div>
<p style="margin-bottom: 10px;">SKU个数:<span style="color: #F56C6C; font-weight: bold;">${row.skuTotal}</span>,预计到货数量:<span style="color:#F56C6C; font-weight: bold;">${row.total}</span></p>
<p>确认仓库是否收到货?</p>
</div>`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true,
},
)
} catch {
return
}
try {
const res = await warehouseReceiptApi(row.id)
if (res.code !== 200) return
ElMessage.success('收货成功')
search()
loadTreeData()
} catch (e) {
console.error(e)
}
}
const submitWarehousing = (row: TableData) => {
submitWarehousingDialogRef.value?.open({
id: row.id,
warehouseId: row.warehouseId,
})
}
const onRefresh = () => {
search()
loadTreeData()
}
onMounted(async () => {
await loadWarehouseList()
await loadTreeData()
})
</script>
<style lang="scss" scoped>
.order-status {
width: 160px;
:deep(.el-tree-node__content) {
height: 30px;
line-height: 30px;
}
:deep(.el-tree-node__label) {
font-size: 13px;
cursor: pointer;
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.is-current) {
.tree-node-label,
.tree-node-count {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.tree-node-label,
.tree-node-count {
background-color: transparent !important;
color: black !important;
}
}
}
:deep(.el-tree-node__label) {
font-size: 13px;
color: rgb(96, 98, 102);
cursor: pointer;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.el-tree-node__label) {
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
:deep(.is-current) {
.el-tree-node__label {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.el-tree-node__label {
background-color: transparent !important;
color: black !important;
}
}
}
}
.tree-node {
display: flex;
color: #333;
font-weight: 500;
}
.table-content {
flex: 1;
margin-top: 10px;
overflow: hidden;
:deep(#top) {
height: 100%;
}
}
.search-form {
display: grid;
grid-template-columns: repeat(6, 1fr);
align-items: flex-start;
:deep(.el-form-item) {
margin-right: 10px;
margin-bottom: 10px;
}
}
</style>
......@@ -60,11 +60,11 @@
value-format="YYYY-MM-DD HH:mm:ss"
/>
</ElFormItem>
<ElFormItem label="备货单号">
<ElFormItem label="备货计划单号">
<ElInput
v-model.trim="searchForm.inNo"
clearable
placeholder="请输入备货单号"
placeholder="请输入备货计划单号"
style="width: 160px"
/>
</ElFormItem>
......@@ -206,7 +206,7 @@
align="center"
/>
<ElTableColumn
label="备货单号"
label="备货计划单号"
show-overflow-tooltip
prop="inNo"
width="130"
......@@ -553,7 +553,7 @@
inline
label-width="90px"
>
<ElFormItem label="备货单号" prop="account">
<ElFormItem label="备货计划单号" prop="account">
<ElInput v-model.trim="editForm.inNo" clearable disabled />
</ElFormItem>
<ElFormItem label="工厂编号:" prop="factoryCode">
......@@ -926,9 +926,7 @@
maxlength="1000"
show-word-limit
/>
<div style="margin-top: 12px; color: #777">
{{ '多个字段使用_##_,_##_隔开' }}
</div>
<div style="margin-top: 12px; color: #777">多个字段使用","隔开</div>
</div>
<template #footer>
<span class="dialog-footer">
......@@ -943,7 +941,7 @@
width="500px"
>
<ElTable :data="updateShipmentNumberForm" border>
<ElTableColumn prop="inNo" label="备货单号" />
<ElTableColumn prop="inNo" label="备货计划单号" />
<ElTableColumn prop="shipmentNumber" label="物流单号">
<template #default="{ row }">
<el-input
......@@ -968,7 +966,7 @@
<el-input
ref="scanInputRef"
v-model="scanInput"
placeholder="请输入备货单号"
placeholder="请输入备货计划单号"
style="width: 90%"
clearable
@keyup.enter="scan"
......@@ -980,7 +978,7 @@
<el-descriptions-item label="备货仓库">
{{ scanData.warehouseName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="备货单号">
<el-descriptions-item label="备货计划单号">
{{ scanData.inNo || '-' }}
</el-descriptions-item>
<el-descriptions-item label="物流单号">
......@@ -2063,7 +2061,7 @@ const scan = async () => {
console.log(scanInput.value, 'scanInput.value')
if (!scanInput.value) {
return ElMessage.warning('请输入备货单号')
return ElMessage.warning('请输入备货计划单号')
}
scanInput.value = scanInput.value.trim()
// 将中文破折号转换为下划线
......@@ -2289,7 +2287,6 @@ $border: solid 1px #ddd;
::v-deep(.el-tree-node) {
cursor: pointer;
margin-bottom: 5px;
}
::v-deep(.el-tree-node__label) {
......
......@@ -6,7 +6,6 @@ import {
updateLocationApi,
updateProductNoApi,
updateCustomSkuApi,
LocationInfoGetAll,
getWarehouseInventoryInfo,
factoryLogWarehouseLog,
LogListData,
......@@ -26,6 +25,8 @@ interface UserMark{
userMark: string
userName: string
}
import { LocationInfoGetAll } from '@/api/common'
const searchForm = ref({
warehouseId: '',
inventoryStart: '',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment