Commit fb93d512 by wusiyi

feat: 添加质检流程 #1009863

parent 24d145d2
...@@ -8,6 +8,8 @@ import type { ...@@ -8,6 +8,8 @@ import type {
OrderInventoryData, OrderInventoryData,
PickCompleteData, PickCompleteData,
ProductListData, ProductListData,
QualityParams,
QualityTypeItem,
RestockData, RestockData,
SearchForm, SearchForm,
StatusTreeNode, StatusTreeNode,
...@@ -94,6 +96,26 @@ export function getPodOrderAcceptedStatisticsApi( ...@@ -94,6 +96,26 @@ export function getPodOrderAcceptedStatisticsApi(
}) })
} }
// 待质检 数量统计
export function getPodOrderQualityCountApi(
data: SearchForm,
pageSize: number,
status?: string,
) {
return axios.post<
never,
BaseRespData<{
qualityCount?: number
noQualityCount?: number
}>
>('factory/podOrderOperation/quality-count', {
...normalizePodOrderQueryPayload({ ...data } as Record<string, unknown>),
currentPage: 1,
pageSize,
status,
})
}
export function getFactoryOrderNewDetailApi(id: number | string) { export function getFactoryOrderNewDetailApi(id: number | string) {
return axios.get<never, BaseRespData<ProductListData[]>>( return axios.get<never, BaseRespData<ProductListData[]>>(
'factory/podOrderProduct/getListByPodOrderId', 'factory/podOrderProduct/getListByPodOrderId',
...@@ -707,3 +729,18 @@ export function deliveryCompleteApi(data: { id: number; version?: number }[]) { ...@@ -707,3 +729,18 @@ export function deliveryCompleteApi(data: { id: number; version?: number }[]) {
{ orderParamList: data }, { orderParamList: data },
) )
} }
/** 质检通过 / 质检不通过 */
export function qualityPodOrderOperationApi(data: QualityParams) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrderOperation/quality',
data,
)
}
/** 质检不通过原因下拉 */
export function getQualityTypeApi() {
return axios.get<never, BaseRespData<QualityTypeItem[]>>(
'factory/podOrderOperation/quality-type',
)
}
...@@ -51,6 +51,8 @@ export interface SearchForm { ...@@ -51,6 +51,8 @@ export interface SearchForm {
/** list_page 排序方向:asc 正序,desc 倒序 */ /** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc' order?: 'asc' | 'desc'
customerNo?: string // 客户交运单号 customerNo?: string // 客户交运单号
/** 待质检子 tab:true 待质检,false 质检不通过 */
qualityPassed?: boolean
} }
export interface FactoryOrderNewListData { export interface FactoryOrderNewListData {
...@@ -304,3 +306,17 @@ export interface OrderInventoryData { ...@@ -304,3 +306,17 @@ export interface OrderInventoryData {
variantImage?: string variantImage?: string
inventoryStatus?: number inventoryStatus?: number
} }
/** 质检操作参数 */
export interface QualityParams {
updateParams: { id: number; version?: number }[]
qualityIssues: string
qualify_problem_type: number | string
qualityPassed: boolean
}
/** 质检不通过原因 */
export interface QualityTypeItem {
key: number | string
value: string
}
...@@ -57,6 +57,8 @@ export interface SearchForm { ...@@ -57,6 +57,8 @@ export interface SearchForm {
/** list_page 排序方向:asc 正序,desc 倒序 */ /** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc' order?: 'asc' | 'desc'
customerNo?: string // 客户交运单号 customerNo?: string // 客户交运单号
/** 待质检子 tab:true 待质检,false 质检不通过 */
qualityPassed?: boolean
} }
export interface FactoryOrderNewListData { export interface FactoryOrderNewListData {
......
<template>
<ElDialog
v-model="visible"
:title="qualityPassed ? '质检通过' : '质检不通过'"
width="450px"
:close-on-click-modal="false"
:destroy-on-close="true"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules">
<div class="mb-10">
{{ qualityPassed ? '确定质检通过吗?' : '确定质检不通过吗?' }}
</div>
<ElFormItem v-if="!qualityPassed" label="原因" prop="reasonKey">
<ElSelect
v-model="form.reasonKey"
placeholder="请选择不通过原因"
style="width: 100%"
clearable
filterable
@change="handleReasonChange"
>
<ElOption
v-for="item in reasonOptions"
:key="item.key"
:label="item.value"
:value="item.key"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
v-if="!qualityPassed && form.reasonKey === 5"
label="具体原因"
prop="qualify_problem_type"
>
<ElInput
v-model="form.qualify_problem_type"
placeholder="请输入不通过原因"
clearable
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer" style="text-align: center">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import {
getQualityTypeApi,
qualityPodOrderOperationApi,
} from '@/api/factoryOrderNew'
import type { QualityTypeItem } from '@/types/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const qualityPassed = ref(true)
const formRef = ref<FormInstance>()
const updateParams = ref<{ id: number; version?: number }[]>([])
const reasonOptions = ref<QualityTypeItem[]>([])
const form = reactive<{
reasonKey: number | string | undefined
qualify_problem_type: string
}>({
reasonKey: undefined,
qualify_problem_type: '',
})
const rules = computed<FormRules>(() => {
if (qualityPassed.value) return {}
const result: FormRules = {
reasonKey: [
{ required: true, message: '请选择不通过原因', trigger: 'change' },
],
}
if (form.reasonKey && form.reasonKey === 5) {
result.qualify_problem_type = [
{ required: true, message: '请输入不通过原因', trigger: 'blur' },
]
}
return result
})
const loadReasons = async () => {
try {
const res = await getQualityTypeApi()
reasonOptions.value = res.data || []
} catch (e) {
console.error(e)
reasonOptions.value = []
}
}
const handleReasonChange = () => {
form.qualify_problem_type = ''
formRef.value?.clearValidate('qualify_problem_type')
}
/** qualityPassed: true 质检通过,false 质检不通过 */
const open = (rows: { id: number; version?: number }[], passed: boolean) => {
updateParams.value = rows.map((row) => ({
id: row.id,
version: row.version,
}))
qualityPassed.value = passed
form.reasonKey = undefined
form.qualify_problem_type = ''
visible.value = true
if (!passed) void loadReasons()
}
const handleClose = () => {
formRef.value?.resetFields()
reasonOptions.value = []
}
const handleSubmit = async () => {
if (!formRef.value) return
if (!qualityPassed.value) {
await formRef.value.validate()
}
const selected = reasonOptions.value.find(
(item) => item.key === form.reasonKey,
)
submitLoading.value = true
try {
const params = {
updateParams: updateParams.value,
qualityIssues: qualityPassed.value ? '' : selected?.value || '',
qualify_problem_type: qualityPassed.value
? ''
: form.reasonKey === 5
? form.qualify_problem_type
: (form.reasonKey as number | string),
qualityPassed: qualityPassed.value,
}
console.log(params)
return
const res = await qualityPodOrderOperationApi(params)
if (res.code !== 200) return
ElMessage.success('操作成功')
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
...@@ -2,6 +2,7 @@ import { computed, ref, nextTick } from 'vue' ...@@ -2,6 +2,7 @@ import { computed, ref, nextTick } from 'vue'
import { import {
getCancelledOrderStatisticsApi, getCancelledOrderStatisticsApi,
getPodOrderAcceptedStatisticsApi, getPodOrderAcceptedStatisticsApi,
getPodOrderQualityCountApi,
getPodOrderStateGroupListApi, getPodOrderStateGroupListApi,
getSuspendStatisticsApi, getSuspendStatisticsApi,
} from '@/api/factoryOrderNew' } from '@/api/factoryOrderNew'
...@@ -24,6 +25,7 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -24,6 +25,7 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
'PENDING_REPLENISH', 'PENDING_REPLENISH',
'IN_PRODUCTION', 'IN_PRODUCTION',
'PENDING_PACKING', 'PENDING_PACKING',
'PENDING_QUALITY',
] ]
const specialLayoutStatuses = ['BATCH_MANAGE', 'AWAITING_RESTOCK'] const specialLayoutStatuses = ['BATCH_MANAGE', 'AWAITING_RESTOCK']
...@@ -34,6 +36,7 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -34,6 +36,7 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
PENDING_REPLENISH: '件', PENDING_REPLENISH: '件',
IN_PRODUCTION: '件', IN_PRODUCTION: '件',
PENDING_PACKING: '件', PENDING_PACKING: '件',
PENDING_RECEIVE: '件',
BATCH_MANAGE: '批', BATCH_MANAGE: '批',
AWAITING_RESTOCK: '个', AWAITING_RESTOCK: '个',
} }
...@@ -51,6 +54,15 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -51,6 +54,15 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
pendingCount: 0, pendingCount: 0,
acceptedOutOfStockCount: 0, acceptedOutOfStockCount: 0,
}) })
/** 待质检子 tab:待质检 / 质检不通过 */
const qualitySubTab = ref<'PENDING_QUALITY' | 'NO_QUALITY'>('PENDING_QUALITY')
const qualityCounts = ref<{
qualityCount?: number
noQualityCount?: number
}>({
qualityCount: 0,
noQualityCount: 0,
})
const suspendedTabs = ref([ const suspendedTabs = ref([
{ {
label: '客户拦截-取消订单', label: '客户拦截-取消订单',
...@@ -87,6 +99,12 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -87,6 +99,12 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
: 2 : 2
: undefined : undefined
// 待质检子 tab:待质检 / 质检不通过
const getListPageQualitySubStatus = () =>
status.value === 'PENDING_QUALITY'
? qualitySubTab.value === 'PENDING_QUALITY'
: undefined
const getPendingReceiveCounts = async () => { const getPendingReceiveCounts = async () => {
try { try {
const res = await getPodOrderAcceptedStatisticsApi( const res = await getPodOrderAcceptedStatisticsApi(
...@@ -105,6 +123,23 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -105,6 +123,23 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
} }
} }
const getQualityCounts = async () => {
try {
const res = await getPodOrderQualityCountApi(
getQueryPayload() as SearchForm,
pageSize.value,
status.value,
)
if (res.code !== 200) return
qualityCounts.value = res.data || {
qualityCount: 0,
noQualityCount: 0,
}
} catch (e) {
console.error(e)
}
}
const getSuspendCounts = async () => { const getSuspendCounts = async () => {
try { try {
const res = await getSuspendStatisticsApi( const res = await getSuspendStatisticsApi(
...@@ -195,6 +230,15 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -195,6 +230,15 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
refreshTableList() refreshTableList()
} }
const handleQualityTabClick = (
tab: 'PENDING_QUALITY' | 'NO_QUALITY',
refreshList: () => void,
) => {
if (qualitySubTab.value === tab) return
qualitySubTab.value = tab
refreshList()
}
const toggleExpand = (node: { expanded?: boolean }) => { const toggleExpand = (node: { expanded?: boolean }) => {
node.expanded = !node.expanded node.expanded = !node.expanded
} }
...@@ -206,6 +250,8 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -206,6 +250,8 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
status, status,
pendingAcceptSubTab, pendingAcceptSubTab,
pendingAcceptCounts, pendingAcceptCounts,
qualitySubTab,
qualityCounts,
suspendedTabs, suspendedTabs,
suspendedSubTab, suspendedSubTab,
cancelledTabs, cancelledTabs,
...@@ -215,11 +261,14 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) { ...@@ -215,11 +261,14 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
isSpecialLayout, isSpecialLayout,
isTableLayout, isTableLayout,
getListPageAcceptedSubStatus, getListPageAcceptedSubStatus,
getListPageQualitySubStatus,
getPendingReceiveCounts, getPendingReceiveCounts,
getQualityCounts,
getSuspendCounts, getSuspendCounts,
getCancelledCounts, getCancelledCounts,
loadStatusTreeCounts, loadStatusTreeCounts,
handlePendingAcceptTabClick, handlePendingAcceptTabClick,
handleQualityTabClick,
toggleExpand, toggleExpand,
} }
} }
...@@ -763,6 +763,32 @@ ...@@ -763,6 +763,32 @@
>订单库存明细</ElButton >订单库存明细</ElButton
> >
</span> </span>
<!-- 待质检 -->
<span v-if="status === 'PENDING_QUALITY'" class="item">
<ElButton type="primary" @click="handleGetOrderInventoryDetail">
质检
</ElButton>
</span>
<span v-if="status === 'PENDING_QUALITY'" class="item">
<ElButton type="success" @click="handleQuality(true)">
质检通过
</ElButton>
</span>
<span
v-if="
status === 'PENDING_QUALITY' && qualitySubTab === 'PENDING_QUALITY'
"
class="item"
>
<ElButton type="danger" @click="handleQuality(false)">
质检不通过
</ElButton>
</span>
<span v-if="status === 'PENDING_QUALITY'" class="item">
<ElButton type="success" @click="handleGetOrderInventoryDetail">
打印操作单
</ElButton>
</span>
</div> </div>
<div v-if="status === 'SUSPEND'" class="status-subtabs"> <div v-if="status === 'SUSPEND'" class="status-subtabs">
<div <div
...@@ -808,6 +834,23 @@ ...@@ -808,6 +834,23 @@
</span> </span>
</div> </div>
</div> </div>
<div v-if="status === 'PENDING_QUALITY'" class="status-subtabs">
<div
class="status-subtab"
:class="{ active: qualitySubTab === 'PENDING_QUALITY' }"
@click="handleQualityTabClick('PENDING_QUALITY')"
>
待质检<span> ({{ qualityCounts.qualityCount || 0 }}) </span>
</div>
<div
class="status-subtab"
:class="{ active: qualitySubTab === 'NO_QUALITY' }"
@click="handleQualityTabClick('NO_QUALITY')"
>
质检不通过<span> ({{ qualityCounts.noQualityCount || 0 }}) </span>
</div>
</div>
<!-- 批次管理 --> <!-- 批次管理 -->
<BatchManageTable v-if="status === 'BATCH_MANAGE'" ref="batchManageRef" /> <BatchManageTable v-if="status === 'BATCH_MANAGE'" ref="batchManageRef" />
...@@ -1050,6 +1093,10 @@ ...@@ -1050,6 +1093,10 @@
ref="pickFailDialogRef" ref="pickFailDialogRef"
@success="() => refreshCurrentView({ isRefreshTree: true })" @success="() => refreshCurrentView({ isRefreshTree: true })"
/> />
<QualityFailDialog
ref="qualityFailDialogRef"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<CreateLogisticDialog <CreateLogisticDialog
ref="createLogisticDialogRef" ref="createLogisticDialogRef"
...@@ -1337,6 +1384,7 @@ import CancelOrderDialog from './component/CancelOrderDialog.vue' ...@@ -1337,6 +1384,7 @@ import CancelOrderDialog from './component/CancelOrderDialog.vue'
import SuspendDialog from './component/SuspendDialog.vue' import SuspendDialog from './component/SuspendDialog.vue'
import PickCompleteDialog from './component/PickCompleteDialog.vue' import PickCompleteDialog from './component/PickCompleteDialog.vue'
import PickFailDialog from './component/PickFailDialog.vue' import PickFailDialog from './component/PickFailDialog.vue'
import QualityFailDialog from './component/QualityFailDialog.vue'
import CardLayout from './component/CardLayout.vue' import CardLayout from './component/CardLayout.vue'
import BatchManageTable from './component/BatchManageTable.vue' import BatchManageTable from './component/BatchManageTable.vue'
import WaitingRestockTable from './component/WaitingRestockTable.vue' import WaitingRestockTable from './component/WaitingRestockTable.vue'
...@@ -1437,6 +1485,8 @@ const { ...@@ -1437,6 +1485,8 @@ const {
status, status,
pendingAcceptSubTab, pendingAcceptSubTab,
pendingAcceptCounts, pendingAcceptCounts,
qualitySubTab,
qualityCounts,
suspendedTabs, suspendedTabs,
suspendedSubTab, suspendedSubTab,
// cancelledTabs, // cancelledTabs,
...@@ -1446,11 +1496,14 @@ const { ...@@ -1446,11 +1496,14 @@ const {
isSpecialLayout, isSpecialLayout,
isTableLayout, isTableLayout,
getListPageAcceptedSubStatus, getListPageAcceptedSubStatus,
getListPageQualitySubStatus,
getPendingReceiveCounts, getPendingReceiveCounts,
getQualityCounts,
getSuspendCounts, getSuspendCounts,
// getCancelledCounts, // getCancelledCounts,
loadStatusTreeCounts, loadStatusTreeCounts,
handlePendingAcceptTabClick: handlePendingAcceptTabClickRaw, handlePendingAcceptTabClick: handlePendingAcceptTabClickRaw,
handleQualityTabClick: handleQualityTabClickRaw,
toggleExpand, toggleExpand,
} = useOrderStatusTree({ } = useOrderStatusTree({
getQueryPayload, getQueryPayload,
...@@ -1520,6 +1573,10 @@ const getListQueryPayload = () => { ...@@ -1520,6 +1573,10 @@ const getListQueryPayload = () => {
payload.prop = defaultSort.prop payload.prop = defaultSort.prop
payload.order = defaultSort.order payload.order = defaultSort.order
} }
const qualityPassed = getListPageQualitySubStatus()
if (qualityPassed !== undefined) {
payload.qualityPassed = qualityPassed
}
return payload return payload
} }
...@@ -1629,6 +1686,11 @@ const refreshCurrentView = (options?: { isRefreshTree?: boolean }) => { ...@@ -1629,6 +1686,11 @@ const refreshCurrentView = (options?: { isRefreshTree?: boolean }) => {
return return
} }
if (isCardLayout.value) { if (isCardLayout.value) {
if (status.value === 'PENDING_QUALITY') {
statusCurrentPageRef.value = currentPage.value
statusPageSizeRef.value = pageSize.value
void getQualityCounts()
}
cardLayoutRef.value?.clearSelection() cardLayoutRef.value?.clearSelection()
nextTick(() => { nextTick(() => {
cardLayoutRef.value?.refresh(true) cardLayoutRef.value?.refresh(true)
...@@ -1741,6 +1803,9 @@ const handleStatusNodeClick = (node: { status: string }) => { ...@@ -1741,6 +1803,9 @@ const handleStatusNodeClick = (node: { status: string }) => {
if (node.status !== 'PENDING_RECEIVE') { if (node.status !== 'PENDING_RECEIVE') {
pendingAcceptSubTab.value = 'PENDING_RECEIVE' pendingAcceptSubTab.value = 'PENDING_RECEIVE'
} }
if (node.status !== 'PENDING_QUALITY') {
qualitySubTab.value = 'PENDING_QUALITY'
}
if (node.status !== 'SUSPEND') { if (node.status !== 'SUSPEND') {
suspendedSubTab.value = 1 suspendedSubTab.value = 1
} }
...@@ -1773,6 +1838,14 @@ const handlePendingAcceptTabClick = ( ...@@ -1773,6 +1838,14 @@ const handlePendingAcceptTabClick = (
tab: 'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK', tab: 'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK',
) => handlePendingAcceptTabClickRaw(tab, refreshTableList) ) => handlePendingAcceptTabClickRaw(tab, refreshTableList)
const handleQualityTabClick = (tab: 'PENDING_QUALITY' | 'NO_QUALITY') =>
handleQualityTabClickRaw(tab, () => {
cardLayoutRef.value?.clearSelection()
nextTick(() => {
cardLayoutRef.value?.refresh(true)
})
})
const handleSuspendTabClick = (value: number) => { const handleSuspendTabClick = (value: number) => {
if (suspendedSubTab.value === value) return if (suspendedSubTab.value === value) return
suspendedSubTab.value = value suspendedSubTab.value = value
...@@ -2549,6 +2622,7 @@ const cancelOrderDialogRef = ref<InstanceType<typeof CancelOrderDialog>>() ...@@ -2549,6 +2622,7 @@ const cancelOrderDialogRef = ref<InstanceType<typeof CancelOrderDialog>>()
const suspendDialogRef = ref<InstanceType<typeof SuspendDialog>>() const suspendDialogRef = ref<InstanceType<typeof SuspendDialog>>()
const pickCompleteDialogRef = ref<InstanceType<typeof PickCompleteDialog>>() const pickCompleteDialogRef = ref<InstanceType<typeof PickCompleteDialog>>()
const pickFailDialogRef = ref<InstanceType<typeof PickFailDialog>>() const pickFailDialogRef = ref<InstanceType<typeof PickFailDialog>>()
const qualityFailDialogRef = ref<InstanceType<typeof QualityFailDialog>>()
const operateDetailsDialogRef = ref() const operateDetailsDialogRef = ref()
const arrangeDialogRef = ref<InstanceType<typeof ArrangeDialog>>() const arrangeDialogRef = ref<InstanceType<typeof ArrangeDialog>>()
const createLogisticDialogRef = ref() const createLogisticDialogRef = ref()
...@@ -3573,6 +3647,12 @@ const handleInterceptionFail = async (row?: FactoryOrderNewListData) => { ...@@ -3573,6 +3647,12 @@ const handleInterceptionFail = async (row?: FactoryOrderNewListData) => {
const handleGetOrderInventoryDetail = async () => { const handleGetOrderInventoryDetail = async () => {
orderInventoryDetailVisible.value = true orderInventoryDetailVisible.value = true
} }
const handleQuality = (qualityPassed: boolean) => {
if (!ensureSelection()) return
const rows = isCardLayout.value ? cardSelectList.value : selectedRows.value
qualityFailDialogRef.value?.open(rows, qualityPassed)
}
const isEnableSorting = ref(false) const isEnableSorting = ref(false)
// 获取功能开关 // 获取功能开关
const getFunctionSwitch = async () => { const getFunctionSwitch = async () => {
...@@ -3600,6 +3680,8 @@ onMounted(() => { ...@@ -3600,6 +3680,8 @@ onMounted(() => {
void tableRef.value void tableRef.value
if (status.value === 'PENDING_RECEIVE') { if (status.value === 'PENDING_RECEIVE') {
getPendingReceiveCounts() getPendingReceiveCounts()
} else if (status.value === 'PENDING_QUALITY') {
void getQualityCounts()
} }
}) })
</script> </script>
......
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