Commit 6dbe6fd5 by qinjianhui

feat: 备货订单开发

parent 80481a71
......@@ -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,7 +5,7 @@ import { LogisticBill } from '@/types/api/podMakeOrder'
import { userData } from '@/types/api/user'
import { VersionImageList } from '@/types/api/typesetting'
import { WarehouseListData } from '@/types'
import { SupplierItem, WarehouseListData } from '@/types'
// 获取物流公司
export function getLogisticsCompanyList() {
......@@ -61,3 +61,17 @@ export function loadWarehouseListApi() {
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',
)
}
import axios from './axios'
import { BasePaginationData, BaseRespData } from '@/types/api'
import { SearchForm, TableData, TreeData } from '@/types/api/supply/stockingOrder'
import {
SearchForm,
TableData,
TreeData,
AddStockingOrderForm,
StockingOrderProduct,
InternalMemoList,
RelatedDocumentList,
LogListData
} from '@/types/api/supply/stockingOrder'
export function getStockingOrderListApi(
data: SearchForm,
currentPage: number,
......@@ -16,4 +26,49 @@ 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}`,
)
}
\ No newline at end of file
......@@ -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)
......
......@@ -34,7 +34,6 @@
}
.el-form-item {
margin-right: 10px !important;
margin-bottom: 10px !important;
}
:root {
--el-menu-icon-width: 0px;
......
......@@ -20,7 +20,37 @@ export interface SearchForm {
warehouseSkuName?: string
remark?: string
}
export interface TableData {}
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
}
export interface StockProduct {
skuImage?: string
productName?: string
......@@ -36,3 +66,48 @@ export interface StockProduct {
}
export interface supplierData {}
// 新增备货单表单
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
detailsList?: StockingOrderProduct[]
}
// 备货单商品
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
}
export interface RelatedDocumentList {}
export interface InternalMemoList {}
export interface LogListData {
id: number
manageId?: number
userId?: number
userName?: string
description?: string
updateTime?: string
}
......@@ -8,4 +8,23 @@ export interface WarehouseListData {
name?: string
remarks?: string
sort?: number
}
\ No newline at end of file
}
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
}
<template>
<ElDialog
v-model="visible"
:title="!editId ? '新增备货单' : '编辑备货单'"
width="1400px"
top="15vh"
:close-on-click-modal="false"
destroy-on-close
>
<div class=" n ">
<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
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
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
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"
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
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
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
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 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 size="large" type="primary" @click="handleSave"
>保存</ElButton
>
<ElButton size="large" type="success" @click="handleSaveAndSubmit">
保存并提交审核
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="tsx">
import { ref, computed, watch } from 'vue'
import type { ElFormItem, FormInstance, FormRules } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type {
AddStockingOrderForm,
StockingOrderProduct,
} from '@/types/api/supply/stockingOrder'
import type { CurrencyCodeData, SupplierItem, WarehouseListData } from '@/types'
import type { userData } from '@/types/api/user'
import {
addStockingOrderApi,
getProductBySkuApi,
getStockingOrderDetailByIdApi,
} from '@/api/stockingOrder'
import ImageView from '@/components/ImageView.vue'
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<{
modelValue: boolean
supplierList: SupplierItem[]
stockKeeperList: userData[]
warehouseList: WarehouseListData[]
currencyList: CurrencyCodeData[]
editId: number | undefined
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'refresh'): void
}>()
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
})
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 ?? '')
if (!strValue) {
if (field === 'buyAmount') row._buyAmountError = '必填项'
else row._priceError = '必填项'
} else if (!/^\d+(\.\d+)?$/.test(strValue)) {
if (field === 'buyAmount') row._buyAmountError = '请输入有效数字'
else row._priceError = '请输入有效数字'
} else {
if (field === 'buyAmount') row._buyAmountError = ''
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,
render: (item: StockingOrderProduct) => (
<ImageView src={item.warehouseSkuImage} width="50px" height="50px" />
),
},
{ 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) => (
<ImageView src={item.warehouseSkuImage} width="50px" height="50px" />
),
},
{ 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(`只能添加币种为 ${formData.value.currencyCode} 的商品`)
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 () => {
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 = !props.editId
? 'factory/supply/stockingUpManage/add'
: `factory/supply/stockingUpManage/update`
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await addStockingOrderApi(url, {
...formData.value,
id: props.editId,
supplierName,
warehouseName,
stockingUpUserName,
totalPrice: totalAmount.value,
})
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 () => {}
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
}
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
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
watch(visible, (val) => {
if (val) {
if (!props.editId) {
resetForm()
} else {
getStockingOrderDetailById(props.editId)
}
}
})
</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" class="detail-tabs">
<ElTabs
v-model="activeTab"
v-loading="loading"
class="detail-tabs"
@tab-click="handleTabClick"
>
<ElTabPane name="stockProducts" label="备货商品">
<div class="detail-table-content">
<TableView
......@@ -11,7 +16,11 @@
</ElTabPane>
<ElTabPane name="relatedDocuments" label="关联单据">
<div class="detail-table-content">
<div class="empty-content">暂无数据</div>
<TableView
:serial-numberable="true"
:columns="relatedDocumentsColumns"
:paginated-data="relatedDocumentsData"
/>
</div>
</ElTabPane>
<ElTabPane name="internalMemo" label="内部便签">
......@@ -21,16 +30,76 @@
</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="ts">
<script setup lang="tsx">
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
import TableView from '@/components/TableView.vue'
import { StockProduct } from '@/types/api/supply/stockingOrder'
import {
LogListData,
StockingOrderProduct,
TableData,
} from '@/types/api/supply/stockingOrder'
import ImageView from '@/components/ImageView.vue'
import { BigNumber } from 'bignumber.js'
import {
getStockingOrderDetailListByIdApi,
getStockingOrderInternalMemoListByIdApi,
getStockingOrderLogListByIdApi,
getStockingOrderRelatedDocumentListByIdApi,
} from '@/api/stockingOrder'
import LogList from '@/components/LogList.vue'
const relatedDocumentsColumns = computed(() => {
return [
{
label: '关联单据',
width: 120,
prop: 'documentNo',
},
{
label: '关联单号',
},
{
label: '制单人',
width: 120,
},
{
label: '制单时间',
width: 160,
},
{
label: 'SKU个数',
width: 120,
align: 'right',
},
{
label: '入库数量',
width: 120,
align: 'right',
},
{
label: '申请数量',
width: 120,
align: 'right',
},
{
label: '预计到货日期',
width: 160,
align: 'center',
},
{
label: '实际到货日期',
width: 160,
align: 'center',
},
]
})
const stockProductsColumns = computed(() => {
return [
{
......@@ -38,40 +107,43 @@ const stockProductsColumns = computed(() => {
prop: 'skuImage',
width: 100,
align: 'center',
render: (item: StockingOrderProduct) => (
<ImageView src={item.warehouseSkuImage} width="50px" height="50px" />
),
},
{
label: '商品名称',
prop: 'productName',
prop: 'warehouseSkuName',
minWidth: 200,
align: 'left',
},
{
label: '款号',
prop: 'styleNumber',
prop: 'productNo',
width: 120,
align: 'center',
},
{
label: '库存SKU',
prop: 'warehouseSku',
width: 150,
width: 180,
align: 'center',
},
{
label: '备货数量',
prop: 'stockQuantity',
prop: 'buyAmount',
width: 100,
align: 'right',
},
{
label: '备货单价',
prop: 'stockUnitPrice',
prop: 'price',
width: 100,
align: 'right',
},
{
label: '备货金额',
prop: 'stockAmount',
prop: 'totalPrice',
width: 120,
align: 'right',
},
......@@ -80,32 +152,125 @@ const stockProductsColumns = computed(() => {
prop: 'unshippedQuantity',
width: 120,
align: 'right',
render: (item: StockingOrderProduct) => (
<span>
{new BigNumber(item.buyAmount ?? 0)
.minus(item.shipmentQuantity ?? 0)
.toString()}
</span>
),
},
{
label: '已发货数量',
prop: 'shippedQuantity',
prop: 'shipmentQuantity',
width: 120,
align: 'right',
},
{
label: '已入库数量',
prop: 'inWarehouseQuantity',
prop: 'buyStored',
width: 120,
align: 'right',
},
{
label: '不良品数量',
prop: 'defectiveQuantity',
prop: 'rejectsAmount',
width: 120,
align: 'right',
},
]
})
defineProps<{
stockProductsData: StockProduct[]
const props = defineProps<{
selectedRow: TableData | null
}>()
const stockProductsData = ref<StockingOrderProduct[]>([])
const relatedDocumentsData = ref<StockingOrderProduct[]>([])
const internalMemoData = ref<StockingOrderProduct[]>([])
const operationLogData = ref<LogListData[]>([])
const activeTab = ref('stockProducts')
const loading = ref(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 (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>
......
......@@ -82,7 +82,7 @@
<ElOption
v-for="item in supplierList"
:key="item.id"
:label="item.name"
:label="item.supplierName"
:value="item.id"
/>
</ElSelect>
......@@ -147,25 +147,39 @@
</ElFormItem>
<ElFormItem class="form-item-buttons">
<ElButton type="primary" @click="search">查询</ElButton>
<ElButton @click="resetSearchForm">重置</ElButton>
<ElButton type="primary" @click="() => search()">查询</ElButton>
<ElButton @click="() => resetSearchForm()">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="header-operation">
<span class="item">
<ElButton type="success">新增</ElButton>
<span v-if="status === 'PENDING_SUBMIT'" class="item">
<ElButton type="success" @click="handleAddOrder">新增</ElButton>
</span>
<span class="item">
<ElButton type="primary">提交审核</ElButton>
<span v-if="status === 'PENDING_SUBMIT'" class="item">
<ElButton type="primary" @click="handleSubmitAudit"
>提交审核</ElButton
>
</span>
<span v-if="status === 'PENDING_AUDIT'" class="item">
<ElButton type="primary">审核</ElButton>
</span>
<span v-if="status === 'STOCKING_UP'" class="item">
<ElButton type="primary">供应商发货</ElButton>
</span>
<span v-if="status === 'STOCKING_UP'" class="item">
<ElButton type="warning">备货完成</ElButton>
</span>
<span class="item">
<ElButton type="success">添加内部便签</ElButton>
</span>
<span class="item">
<span
v-if="['PENDING_SUBMIT', 'STOCKING_UP'].includes(status)"
class="item"
>
<ElButton type="warning">取消</ElButton>
</span>
<span class="item">
<span v-if="status === 'PENDING_SUBMIT'" class="item">
<ElButton type="danger">删除</ElButton>
</span>
</div>
......@@ -175,11 +189,14 @@
<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
......@@ -195,11 +212,20 @@
></ElPagination>
</template>
<template #bottom>
<StockingOrderDetailTabs :stock-products-data="stockProductsData" />
<StockingOrderDetailTabs :selected-row="selectedRow" />
</template>
</SplitDiv>
</div>
</div>
<AddStockingOrderDialog
v-model="addDialogVisible"
:supplier-list="supplierList"
:stock-keeper-list="stockKeeperList"
:warehouse-list="warehouseList"
:currency-list="currencyList"
:edit-id="editId"
@refresh="search"
/>
</div>
</template>
<script setup lang="tsx">
......@@ -207,24 +233,29 @@ import { computed, ref } from 'vue'
import { getStockingOrderListApi } from '@/api/stockingOrder'
import TableView from '@/components/TableView.vue'
import StockingOrderDetailTabs from './StockingOrderDetailTabs.vue'
import AddStockingOrderDialog from './AddStockingOrderDialog.vue'
import {
TreeData,
SearchForm,
TableData,
supplierData,
} from '@/types/api/supply/stockingOrder'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { StockProduct } from '@/types/api/supply/stockingOrder'
import { WarehouseListData } from '@/types'
import { getEmployeeListApi, loadWarehouseListApi } from '@/api/common'
import { CurrencyCodeData, SupplierItem, WarehouseListData } from '@/types'
import {
getBaseCurrencyInfoApi,
getEmployeeListApi,
getSupplierListApi,
loadWarehouseListApi,
} from '@/api/common'
import { getStockingOrderStatusTreeApi } from '@/api/stockingOrder'
import { userData } from '@/types/api/user'
import { ElButton, ElTag } from 'element-plus'
const tableColumns = computed(() => {
return [
{
label: '备货单号',
prop: 'stockingOrderNo',
prop: 'stockingUpManageNo',
minWidth: 150,
align: 'center',
},
......@@ -242,37 +273,54 @@ const tableColumns = computed(() => {
},
{
label: '订单状态',
prop: 'orderStatus',
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: 'expectedDeliveryDate',
width: 130,
prop: 'expectDeliveryTime',
width: 160,
align: 'center',
},
{
label: 'SKU个数',
prop: 'skuCount',
prop: 'skuTotal',
width: 100,
align: 'center',
align: 'right',
},
{
label: '备货总数',
prop: 'totalStockQuantity',
prop: 'total',
width: 100,
align: 'right',
},
{
label: '备货总额',
prop: 'totalStockAmount',
prop: 'totalPrice',
width: 120,
align: 'right',
},
{
label: '币种',
prop: 'currency',
prop: 'currencyCode',
width: 80,
align: 'center',
},
......@@ -284,19 +332,19 @@ const tableColumns = computed(() => {
},
{
label: '备货员',
prop: 'stockKeeperName',
prop: 'stockingUpUserName',
width: 100,
align: 'center',
},
{
label: '最后交货日期',
prop: 'lastDeliveryDate',
prop: 'lastDeliveryTime',
width: 130,
align: 'center',
},
{
label: '订单完成日期',
prop: 'orderCompletionDate',
prop: 'completeTime',
width: 130,
align: 'center',
},
......@@ -308,13 +356,13 @@ const tableColumns = computed(() => {
},
{
label: '制单人',
prop: 'creatorName',
prop: 'createUserName',
width: 100,
align: 'center',
},
{
label: '审核人',
prop: 'approverName',
prop: 'auditName',
width: 100,
align: 'center',
},
......@@ -327,7 +375,7 @@ const tableColumns = computed(() => {
},
{
label: '审核时间',
prop: 'approvalTime',
prop: 'auditTime',
width: 160,
align: 'center',
sortable: true,
......@@ -345,57 +393,40 @@ const tableColumns = computed(() => {
width: 100,
align: 'center',
fixed: 'right',
render: (row: TableData) => {
return (
<span>
{row.status === 'PENDING_SUBMIT' && (
<ElButton link type="warning" onClick={() => handleEdit(row)}>
编辑
</ElButton>
)}
</span>
)
},
},
]
})
const treeData = ref<TreeData[]>([
{
children: [
{
status: '0',
statusName: '待提交',
quantity: 0,
},
{
status: '1',
statusName: '待审核',
quantity: 0,
},
{
status: '2',
statusName: '备货中',
quantity: 0,
},
{
status: '3',
statusName: '备货完成',
quantity: 0,
},
{
status: '4',
statusName: '已取消',
quantity: 0,
},
],
status: '-1',
statusName: '全部',
},
])
const treeData = ref<TreeData[]>([])
const status = ref<string>('-1')
const selectedRow = ref<TableData | null>(null)
const warehouseList = ref<WarehouseListData[]>([])
const supplierList = ref<supplierData[]>([])
const supplierList = ref<SupplierItem[]>([])
const stockKeeperList = ref<userData[]>([])
const currencyList = ref<CurrencyCodeData[]>([])
const deliveryStatusList = ref([
{ label: '待发货', value: 'pending' },
{ label: '部分发货', value: 'partial' },
{ label: '全部发货', value: 'completed' },
])
const editId = ref<number | undefined>(undefined)
const selection = ref<TableData[]>([])
const rangeTime = ref<string[]>([])
const [searchForm, resetSearchForm] = useValue<SearchForm>({} as SearchForm)
const treeRef = ref()
const tableRef = ref()
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
......@@ -414,7 +445,24 @@ const loadEmployeeList = async () => {
console.error(e)
}
}
const loadSupplierList = async () => {}
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()
......@@ -453,32 +501,55 @@ const {
: (searchForm.value.supplierId as string | undefined),
startDate: rangeTime.value?.[0],
endDate: rangeTime.value?.[1],
status: status.value,
status: status.value === '-1' ? undefined : status.value,
},
page,
pageSize,
).then((res) => res.data),
).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 stockProductsData = ref<StockProduct[]>([])
const handleSelectionChange = (val: TableData[]) => {
selection.value = val
}
const handleRowClick = (row: TableData) => {
selectedRow.value = row
loadStockProducts(row)
if (row) {
selectedRow.value = row
} else {
selectedRow.value = null
}
}
const loadStockProducts = (_row: TableData) => {
stockProductsData.value = []
const handleEdit = (row: TableData) => {
editId.value = row.id as number
addDialogVisible.value = true
}
const handleEdit = (_row: TableData) => {
ElMessage.info('编辑功能待实现')
// 新增备货单弹窗
const addDialogVisible = ref(false)
const handleAddOrder = () => {
editId.value = undefined
addDialogVisible.value = true
}
const handleSubmitAudit = () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择要操作的数据')
}
}
onMounted(() => {
loadWarehouseList()
loadEmployeeList()
loadSupplierList()
loadTreeData()
loadCurrencyList()
})
</script>
<style lang="scss" scoped>
......@@ -567,5 +638,9 @@ onMounted(() => {
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'
......
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