Commit 63026784 by qinjianhui

feat: 备货完成功能开发

parent e212c47d
...@@ -81,3 +81,35 @@ export function submitStockingOrderAuditApi(ids: string) { ...@@ -81,3 +81,35 @@ export function submitStockingOrderAuditApi(ids: string) {
}, },
) )
} }
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
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}`,
)
}
...@@ -83,7 +83,10 @@ export interface AddStockingOrderForm { ...@@ -83,7 +83,10 @@ export interface AddStockingOrderForm {
currencyName?: string currencyName?: string
remark?: string remark?: string
status?: number status?: number
submission?: boolean
detailsList?: StockingOrderProduct[] detailsList?: StockingOrderProduct[]
examineStatus?: number // 审核状态:1通过,0驳回
rejectReason?: string // 驳回原因
} }
// 备货单商品 // 备货单商品
...@@ -99,6 +102,10 @@ export interface StockingOrderProduct { ...@@ -99,6 +102,10 @@ export interface StockingOrderProduct {
currencyName?: string currencyName?: string
totalPrice?: number | string totalPrice?: number | string
shipmentQuantity?: number | string shipmentQuantity?: number | string
buyStored?: number
// 供应商发货相关字段
remainingQuantity?: number // 剩余待发货数量
currentShipQuantity?: number | string // 本次发货数量
} }
export interface RelatedDocumentList {} export interface RelatedDocumentList {}
......
...@@ -228,7 +228,7 @@ ...@@ -228,7 +228,7 @@
v-if="editOrderType === 'add' || editOrderType === 'edit'" v-if="editOrderType === 'add' || editOrderType === 'edit'"
size="large" size="large"
type="primary" type="primary"
@click="handleSave" @click="handleSave(false)"
>保存</ElButton >保存</ElButton
> >
<ElButton <ElButton
...@@ -243,10 +243,14 @@ ...@@ -243,10 +243,14 @@
v-if="editOrderType === 'audit'" v-if="editOrderType === 'audit'"
size="large" size="large"
type="success" type="success"
@click="handleSave" @click="handleSave(false)"
>审核通过</ElButton >审核通过</ElButton
> >
<ElButton v-if="editOrderType === 'audit'" size="large" type="danger" <ElButton
v-if="editOrderType === 'audit'"
size="large"
type="danger"
@click="handleReject"
>审核驳回</ElButton >审核驳回</ElButton
> >
</div> </div>
...@@ -268,6 +272,7 @@ import { ...@@ -268,6 +272,7 @@ import {
addStockingOrderApi, addStockingOrderApi,
getProductBySkuApi, getProductBySkuApi,
getStockingOrderDetailByIdApi, getStockingOrderDetailByIdApi,
rejectedStockingOrderApi,
} from '@/api/stockingOrder' } from '@/api/stockingOrder'
import ImageView from '@/components/ImageView.vue' import ImageView from '@/components/ImageView.vue'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
...@@ -528,7 +533,7 @@ const handleCancel = () => { ...@@ -528,7 +533,7 @@ const handleCancel = () => {
visible.value = false visible.value = false
} }
const handleSave = async () => { const handleSave = async (isSubmit: boolean = false) => {
if (!formRef.value) return if (!formRef.value) return
try { try {
...@@ -573,6 +578,7 @@ const handleSave = async () => { ...@@ -573,6 +578,7 @@ const handleSave = async () => {
warehouseName, warehouseName,
stockingUpUserName, stockingUpUserName,
totalPrice: totalAmount.value, totalPrice: totalAmount.value,
submission: props.editOrderType === 'audit' ? undefined : isSubmit,
}) })
if (res.code !== 200) { if (res.code !== 200) {
ElMessage.warning(res.message) ElMessage.warning(res.message)
...@@ -588,7 +594,52 @@ const handleSave = async () => { ...@@ -588,7 +594,52 @@ const handleSave = async () => {
} }
} }
const handleSaveAndSubmit = async () => {} const handleSaveAndSubmit = async () => {
await handleSave(true)
}
const handleReject = async () => {
if (!props.editId) 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: props.editId,
turnDownReason: rejectReason.trim(),
})
if (res.code !== 200) return
ElMessage.success('驳回成功')
emit('refresh')
visible.value = false
} finally {
loading.close()
}
} catch {
// 用户取消操作,不做处理
}
}
const resetForm = () => { const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
......
<template>
<ElDialog
v-model="visible"
title="供应商发货"
width="1400px"
top="15vh"
:close-on-click-modal="false"
destroy-on-close
>
<div
class="supplier-dispatch-order-page card h-100 flex overflow-hidden"
></div>
</ElDialog>
</template>
<script setup lang="tsx">
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refresh'): void
}>()
const visible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
})
</script>
<template>
<ElDialog
v-model="visible"
title="供应商发货"
width="1400px"
top="10vh"
: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"
type="number"
:min="0"
style="width: 100px"
/>
</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, computed, watch } 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/stockingOrder'
import ImageView from '@/components/ImageView.vue'
import TableView from '@/components/TableView.vue'
import { BigNumber } from 'bignumber.js'
const props = defineProps<{
visible: boolean
selectedRow: TableData | null
warehouseList: WarehouseListData[]
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refresh'): void
}>()
const visible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
})
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) => (
<ImageView src={item.warehouseSkuImage} width="50px" height="50px" />
),
},
{
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 handleDispatch = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (formData.value.detailsList?.length === 0) {
ElMessage.warning('没有可发货的商品')
return
}
for (const item of formData.value.detailsList || []) {
if (!item.currentShipQuantity) {
ElMessage.warning('请填写本次发货数量')
return
}
if (Number(item.currentShipQuantity) > (item.remainingQuantity ?? 0)) {
try {
await ElMessageBox.confirm(
`库存SKU:${item.warehouseSku}本次发货数量大于剩余待发货数量,是否确认发货?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
)
} catch {
return
}
}
}
const loading = ElLoading.service({
lock: true,
text: '正在提交...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await supplierDispatchApi({
manageId: props.selectedRow?.id as number,
manageNo: formData.value.stockingUpManageNo as string,
warehouseId: formData.value.warehouseId as number,
warehouseName: formData.value.warehouseName as string,
expectDeliveryTime: formData.value.expectDeliveryTime as string,
detailsList:
formData.value.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 loadProductList = async () => {
if (!props.selectedRow?.id) return
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await getStockingOrderDetailByIdApi(props.selectedRow.id)
if (res.code !== 200) {
ElMessage.warning(res.message)
return
}
const { stockingUpManageNo, warehouseId, detailsList, warehouseName } =
res.data
formData.value = {
stockingUpManageNo,
warehouseId,
warehouseName,
detailsList: detailsList?.map((item) => ({
...item,
remainingQuantity: new BigNumber(item.buyAmount || 0)
.minus(item.shipmentQuantity || 0)
.toNumber(),
currentShipQuantity: undefined,
})),
}
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
watch(visible, (val) => {
if (val && props.selectedRow) {
loadProductList()
}
})
</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>
...@@ -161,9 +161,7 @@ ...@@ -161,9 +161,7 @@
>提交审核</ElButton >提交审核</ElButton
> >
</span> </span>
<span v-if="status === 'STOCKING_UP'" class="item">
<ElButton type="warning">备货完成</ElButton>
</span>
<span class="item"> <span class="item">
<ElButton type="success">添加内部便签</ElButton> <ElButton type="success">添加内部便签</ElButton>
</span> </span>
...@@ -221,9 +219,10 @@ ...@@ -221,9 +219,10 @@
:edit-id="selectedRow?.id" :edit-id="selectedRow?.id"
@refresh="onRefresh" @refresh="onRefresh"
/> />
<SupplierDispatchOrder <SupplierDispatchOrderDialog
v-model:visible="supplierDispatchOrderVisible" v-model:visible="supplierDispatchOrderVisible"
:selected-row="selectedRow" :selected-row="selectedRow"
:warehouse-list="warehouseList"
@refresh="onRefresh" @refresh="onRefresh"
/> />
</div> </div>
...@@ -232,12 +231,13 @@ ...@@ -232,12 +231,13 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { import {
getStockingOrderListApi, getStockingOrderListApi,
stockingCompleteApi,
submitStockingOrderAuditApi, submitStockingOrderAuditApi,
} from '@/api/stockingOrder' } from '@/api/stockingOrder'
import TableView from '@/components/TableView.vue' import TableView from '@/components/TableView.vue'
import StockingOrderDetailTabs from './StockingOrderDetailTabs.vue' import StockingOrderDetailTabs from './StockingOrderDetailTabs.vue'
import AddStockingOrderDialog from './AddStockingOrderDialog.vue' import AddStockingOrderDialog from './AddStockingOrderDialog.vue'
import SupplierDispatchOrder from './SupplierDispatchOrder.vue' import SupplierDispatchOrderDialog from './SupplierDispatchOrderDialog.vue'
import { import {
TreeData, TreeData,
SearchForm, SearchForm,
...@@ -394,7 +394,7 @@ const tableColumns = computed(() => { ...@@ -394,7 +394,7 @@ const tableColumns = computed(() => {
{ {
label: '操作', label: '操作',
prop: 'operation', prop: 'operation',
width: 140, width: 180,
align: 'center', align: 'center',
fixed: 'right', fixed: 'right',
render: (row: TableData) => { render: (row: TableData) => {
...@@ -427,6 +427,15 @@ const tableColumns = computed(() => { ...@@ -427,6 +427,15 @@ const tableColumns = computed(() => {
供应商发货 供应商发货
</ElButton> </ElButton>
)} )}
{row.status === 'STOCKING_UP' && (
<ElButton
link
type="warning"
onClick={() => stockingComplete(row)}
>
备货完成
</ElButton>
)}
</span> </span>
) )
}, },
...@@ -562,6 +571,30 @@ const handleSupplierDispatchOrder = (row: TableData) => { ...@@ -562,6 +571,30 @@ const handleSupplierDispatchOrder = (row: TableData) => {
selectedRow.value = row selectedRow.value = row
supplierDispatchOrderVisible.value = true supplierDispatchOrderVisible.value = true
} }
const stockingComplete = async (row: TableData) => {
try {
await ElMessageBox.confirm(
'备货完成将把未完成入库申请单自动取消,是否继续对选中的订单操作备货完成?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
)
} catch {
return
}
try {
const res = await stockingCompleteApi(row.id)
if (res.code !== 200) return
ElMessage.success('备货完成成功')
search()
loadTreeData()
} catch (e) {
console.error(e)
}
}
// 新增备货单弹窗 // 新增备货单弹窗
const addDialogVisible = ref(false) const addDialogVisible = ref(false)
const handleAddOrder = () => { const handleAddOrder = () => {
......
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