Commit 60defb7c by wusiyi

feat: 订单添加质检流程 #1009863

parent 9bf605b9
......@@ -12,58 +12,42 @@ declare module 'vue' {
DatePicker: typeof import('./src/components/Form/DatePicker.vue')['default']
DateRangePicker: typeof import('./src/components/Form/DateRangePicker.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCascaderPanel: typeof import('element-plus/es')['ElCascaderPanel']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
Icon: typeof import('./src/components/Icon.vue')['default']
ImageView: typeof import('./src/components/ImageView.vue')['default']
LeftRightLayout: typeof import('./src/components/leftRightLayout.vue')['default']
......
......@@ -8,6 +8,8 @@ import type {
OrderInventoryData,
PickCompleteData,
ProductListData,
QualityParams,
QualityTypeItem,
RestockData,
SearchForm,
StatusTreeNode,
......@@ -94,6 +96,27 @@ export function getPodOrderAcceptedStatisticsApi(
})
}
// 待质检 数量统计
export function getPodOrderQualityCountApi(
data: SearchForm,
pageSize: number,
status?: string,
) {
return axios.post<
never,
BaseRespData<{
qualityCount?: number
noQualityCount?: number
totalCount?: number
}>
>('factory/podOrderOperation/quality-count', {
...normalizePodOrderQueryPayload({ ...data } as Record<string, unknown>),
currentPage: 1,
pageSize,
status,
})
}
export function getFactoryOrderNewDetailApi(id: number | string) {
return axios.get<never, BaseRespData<ProductListData[]>>(
'factory/podOrderProduct/getListByPodOrderId',
......@@ -568,6 +591,16 @@ export function getByOperationNoLogApi(operationNo: string) {
)
}
// 快捷质检 查询
export function getQualityScanByOperationNoApi(operationNo: string) {
return axios.get<never, BaseRespData<operateOrderListData>>(
'factory/podOrderOperation/qualityByOperationNo',
{
params: { operationNo },
},
)
}
// 校验库存
export function checkInventoryApi(id: number | string, status: string) {
return axios.get<never, BaseRespData<never>>(
......@@ -707,3 +740,28 @@ export function deliveryCompleteApi(data: { id: number; version?: number }[]) {
{ orderParamList: data },
)
}
/** 重新生产 */
export function remanufactureApi(data: {
updateParams: { id: number; version?: number }[]
}) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderOperation/remanufacture',
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 {
/** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc'
customerNo?: string // 客户交运单号
/** 待质检子 tab:true 待质检,false 质检不通过 */
qualityPassed?: boolean
}
export interface FactoryOrderNewListData {
......@@ -304,3 +306,18 @@ export interface OrderInventoryData {
variantImage?: string
inventoryStatus?: number
}
/** 质检操作参数 */
export interface QualityParams {
updateParams: { id: number; version?: number }[]
qualityIssues: string
qualifyProblemType: number | string
qualityPassed: boolean
source?: number // 0 列表页,1 快捷质检
}
/** 质检不通过原因 */
export interface QualityTypeItem {
key: number | string
value: string
}
......@@ -57,6 +57,8 @@ export interface SearchForm {
/** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc'
customerNo?: string // 客户交运单号
/** 待质检子 tab:true 待质检,false 质检不通过 */
qualityPassed?: boolean
}
export interface FactoryOrderNewListData {
......
<template>
<div class="page card h-100 flex-gap-10 overflow-hidden flex flex-column">
<div class="flow-steps">
<div
v-for="item in showFlowConfigList"
:key="item.code"
class="flow-step"
>
{{ item.name }}
<div class="page-wrap">
<div class="page card h-100 overflow-hidden">
<div class="flow-config-container">
<div class="flow-steps">
<div
v-for="item in showFlowConfigList"
:key="item.code"
class="flow-step"
>
{{ item.name }}
</div>
</div>
<div class="step-button mt-10">
<el-checkbox v-model="draftSkipInspect">
跳过质检流程 (跳过后生产完成到待配货状态)
</el-checkbox>
</div>
</div>
</div>
<div class="flex-1 mt-10">
<el-checkbox v-model="draftSkipInspect">
跳过质检流程 (跳过后生产完成到待配货状态)
</el-checkbox>
</div>
<div class="footer-actions">
<el-button type="primary" @click="updateConfig"> 保存 </el-button>
</div>
......@@ -25,10 +29,12 @@ import {
updateFactoryOrderFlowConfigApi,
getFactoryOrderFlowConfigApi,
} from '@/api/order'
import { getPodOrderQualityCountApi } from '@/api/factoryOrderNew'
import type {
FactoryOrderFlowConfigItem,
FactoryOrderFlowConfig,
} from '@/types/api/order'
import type { SearchForm } from '@/types/api/factoryOrderNew'
const flowConfigList = ref<FactoryOrderFlowConfigItem[]>([])
const factoryOrderFlowConfig = ref<FactoryOrderFlowConfig>()
......@@ -36,7 +42,7 @@ const factoryOrderFlowConfig = ref<FactoryOrderFlowConfig>()
const draftSkipInspect = ref(false)
const showFlowConfigList = computed(() =>
factoryOrderFlowConfig.value?.skipQualityInspect ?? false
draftSkipInspect.value
? flowConfigList.value.filter((item) => item.code !== 'INSPECTION')
: flowConfigList.value,
)
......@@ -61,6 +67,40 @@ const getList = async () => {
// 更新配置
const updateConfig = async () => {
try {
const prevSkip = factoryOrderFlowConfig.value?.skipQualityInspect ?? false
// 从未勾选改为勾选时,先查询待质检数量
if (!prevSkip && draftSkipInspect.value) {
const res = await getPodOrderQualityCountApi(
{} as SearchForm,
20,
'PENDING_QUALITY',
)
if (res.data?.totalCount) {
await ElMessageBox.confirm(
`有操作单仍在待质检状态,跳过质检流程将不展示待质检页面,但不影响操作单配货,<br/>
<span style="color: red; margin: 15px 0">是否确定跳过质检流程?</span>`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
type: 'warning',
},
)
} else {
await ElMessageBox.confirm(`是否确定保存`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
}
} else {
await ElMessageBox.confirm(`是否确定保存`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
}
await updateFactoryOrderFlowConfigApi({
id: factoryOrderFlowConfig.value?.id ?? 0,
skipQualityInspect: draftSkipInspect.value,
......@@ -78,9 +118,35 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
.page-wrap {
position: relative;
height: 100%;
}
.page {
width: 1200px;
margin: 0 auto;
}
.flow-config-container {
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
min-width: 900px;
margin: 50px auto 0;
}
.footer-actions {
position: absolute;
bottom: -10px;
left: -10px;
right: -50px;
z-index: 1;
display: flex;
justify-content: center;
padding: 16px 0;
background: rgba(144, 147, 153, 0.2);
}
.flow-steps {
......
<template>
<ElDialog
v-model="visible"
:title="qualityPassed ? '质检通过' : '质检不通过'"
width="450px"
top="30vh"
:close-on-click-modal="false"
:destroy-on-close="true"
@close="handleClose"
>
<div class="confirm-body">
<ElIcon class="confirm-tip__icon" :size="24">
<WarningFilled />
</ElIcon>
<ElForm ref="formRef" :model="form" :rules="rules" class="confirm-form">
<div class="confirm-tip">
{{ qualityPassed ? '确定质检通过吗?' : '确定质检不通过吗?' }}
</div>
<ElFormItem
v-if="!qualityPassed"
label="原因"
prop="qualifyProblemType"
>
<ElSelect
v-model="form.qualifyProblemType"
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.qualifyProblemType === 5"
label="具体原因"
prop="qualityIssues"
>
<ElInput
v-model="form.qualityIssues"
placeholder="请输入不通过原因"
maxlength="50"
show-word-limit
clearable
/>
</ElFormItem>
</ElForm>
</div>
<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 { WarningFilled } from '@element-plus/icons-vue'
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 source = ref(0) // 0 列表页,1 快捷质检
const formRef = ref<FormInstance>()
const updateParams = ref<{ id: number; version?: number }[]>([])
const reasonOptions = ref<QualityTypeItem[]>([])
const form = reactive<{
qualifyProblemType: number | string
qualityIssues: string
}>({
qualifyProblemType: '',
qualityIssues: '',
})
const rules = computed<FormRules>(() => {
if (qualityPassed.value) return {}
const result: FormRules = {
qualifyProblemType: [
{ required: true, message: '请选择不通过原因', trigger: 'change' },
],
}
if (form.qualifyProblemType && form.qualifyProblemType === 5) {
result.qualityIssues = [
{ required: true, message: '请输入不通过原因', trigger: 'change' },
]
}
return result
})
const loadReasons = async () => {
try {
const res = await getQualityTypeApi()
reasonOptions.value = res.data || []
} catch (e) {
console.error(e)
reasonOptions.value = []
}
}
const handleReasonChange = () => {
form.qualityIssues =
form.qualifyProblemType === 5
? ''
: reasonOptions.value.find((item) => item.key === form.qualifyProblemType)
?.value || ''
formRef.value?.clearValidate('qualityIssues')
}
const open = (
rows: { id: number; version?: number }[],
passed: boolean,
sourceType: number = 0,
) => {
updateParams.value = rows.map((row) => ({
id: row.id,
version: row.version,
}))
qualityPassed.value = passed
source.value = sourceType
form.qualifyProblemType = ''
form.qualityIssues = ''
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()
}
submitLoading.value = true
try {
const params = {
...form,
updateParams: updateParams.value,
qualityPassed: qualityPassed.value,
source: source.value,
}
const res = await qualityPodOrderOperationApi(params)
if (res.code !== 200) return
if (source.value !== 1) {
ElMessage.success('操作成功')
}
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.confirm-body {
display: flex;
gap: 12px;
}
.confirm-form {
flex: 1;
min-width: 0;
}
.confirm-tip {
margin-bottom: 12px;
line-height: 24px;
}
.confirm-tip__icon {
flex-shrink: 0;
color: var(--el-color-warning);
}
</style>
......@@ -2,6 +2,7 @@ import { computed, ref, nextTick } from 'vue'
import {
getCancelledOrderStatisticsApi,
getPodOrderAcceptedStatisticsApi,
getPodOrderQualityCountApi,
getPodOrderStateGroupListApi,
getSuspendStatisticsApi,
} from '@/api/factoryOrderNew'
......@@ -24,6 +25,7 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
'PENDING_REPLENISH',
'IN_PRODUCTION',
'PENDING_PACKING',
'PENDING_QUALITY',
]
const specialLayoutStatuses = ['BATCH_MANAGE', 'AWAITING_RESTOCK']
......@@ -34,6 +36,8 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
PENDING_REPLENISH: '件',
IN_PRODUCTION: '件',
PENDING_PACKING: '件',
PENDING_RECEIVE: '件',
PENDING_QUALITY: '件',
BATCH_MANAGE: '批',
AWAITING_RESTOCK: '个',
}
......@@ -51,6 +55,15 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
pendingCount: 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([
{
label: '客户拦截-取消订单',
......@@ -87,6 +100,12 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
: 2
: undefined
// 待质检子 tab:待质检 / 质检不通过
const getListPageQualitySubStatus = () =>
status.value === 'PENDING_QUALITY'
? qualitySubTab.value === 'PENDING_QUALITY'
: undefined
const getPendingReceiveCounts = async () => {
try {
const res = await getPodOrderAcceptedStatisticsApi(
......@@ -105,6 +124,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 () => {
try {
const res = await getSuspendStatisticsApi(
......@@ -195,6 +231,15 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
refreshTableList()
}
const handleQualityTabClick = (
tab: 'PENDING_QUALITY' | 'NO_QUALITY',
refreshList: () => void,
) => {
if (qualitySubTab.value === tab) return
qualitySubTab.value = tab
refreshList()
}
const toggleExpand = (node: { expanded?: boolean }) => {
node.expanded = !node.expanded
}
......@@ -206,6 +251,8 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
status,
pendingAcceptSubTab,
pendingAcceptCounts,
qualitySubTab,
qualityCounts,
suspendedTabs,
suspendedSubTab,
cancelledTabs,
......@@ -215,11 +262,14 @@ export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
isSpecialLayout,
isTableLayout,
getListPageAcceptedSubStatus,
getListPageQualitySubStatus,
getPendingReceiveCounts,
getQualityCounts,
getSuspendCounts,
getCancelledCounts,
loadStatusTreeCounts,
handlePendingAcceptTabClick,
handleQualityTabClick,
toggleExpand,
}
}
......@@ -426,6 +426,7 @@ const operationStatusMap: Record<string, string> = {
PENDING_REPLENISH: '待补胚',
PENDING_PICK: '待拣胚',
IN_PRODUCTION: '生产中',
PENDING_QUALITY: '待质检',
PACKING_COMPLETED: '配货完成',
COMPLETED: '已完成',
CANCELLED: '已取消',
......@@ -454,6 +455,7 @@ const operationStatusTagMap: Record<
borderColor: '#409EFF',
},
},
PENDING_QUALITY: { type: 'warning' },
IN_PRODUCTION: { type: 'primary' },
PACKING_COMPLETED: { type: 'success' },
COMPLETED: { type: 'success' },
......
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