| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesQcOqcApi } from '#/api/mes/qc/oqc'; |
| | | import type { OqcAction } from '#/api/mes/qc/oqc'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { useAccess } from '@vben/access'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcStatusEnum } from '@vben/constants'; |
| | | import { MesBizTypeEnum, MesQcStatusEnum } from '@vben/constants'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { deleteOqc, exportOqc, getOqcPage } from '#/api/mes/qc/oqc'; |
| | | import { approveOqc, deleteOqc, exportOqc, getOqcActions, getOqcPage, retestOqc } from '#/api/mes/qc/oqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const props = withDefaults(defineProps<{ salesMode?: boolean }>(), { salesMode: false }); |
| | | const { hasAccessByCodes } = useAccess(); |
| | | const workflowLabels: Record<number, string> = { |
| | | 0: '草稿', 10: '待复检', 20: '待销售部审批', 30: '待公司质检审批', |
| | | 40: '已放行', 50: '待销售部不合格评审', 60: '待公司不合格审批', 70: '已拒绝出货', |
| | | }; |
| | | const active = ref<MesQcOqcApi.Oqc>(); |
| | | const retestOpen = ref(false); |
| | | const retestBusy = ref(false); |
| | | const retestForm = ref({ checkQuantity: 0, qualifiedQuantity: 0, unqualifiedQuantity: 0, opinion: '' }); |
| | | function openRetest(row: MesQcOqcApi.Oqc) { |
| | | active.value = row; |
| | | retestForm.value = { |
| | | checkQuantity: Number(row.checkQuantity ?? 0), qualifiedQuantity: Number(row.qualifiedQuantity ?? 0), |
| | | unqualifiedQuantity: Number(row.unqualifiedQuantity ?? 0), opinion: '', |
| | | }; |
| | | retestOpen.value = true; |
| | | } |
| | | async function saveRetest() { |
| | | if (!active.value?.id) return; |
| | | const form = retestForm.value; |
| | | if (form.checkQuantity < 0 || form.qualifiedQuantity < 0 || form.unqualifiedQuantity < 0 |
| | | || form.checkQuantity !== form.qualifiedQuantity + form.unqualifiedQuantity) { |
| | | message.warning('检测数量必须等于合格与不合格数量之和'); |
| | | return; |
| | | } |
| | | if (form.unqualifiedQuantity > 0 && !form.opinion.trim()) { |
| | | message.warning('复检不合格请填写原因'); |
| | | return; |
| | | } |
| | | retestBusy.value = true; |
| | | try { |
| | | await retestOqc({ id: active.value.id, ...form, checkResult: form.unqualifiedQuantity === 0 ? 1 : 2 }); |
| | | message.success('复检结果已提交,待销售部评审'); |
| | | retestOpen.value = false; |
| | | handleRefresh(); |
| | | } catch (error) { |
| | | message.error((error as Error).message || '复检失败'); |
| | | } finally { retestBusy.value = false; } |
| | | } |
| | | const reviewOpen = ref(false); |
| | | const reviewBusy = ref(false); |
| | | const reviewStage = ref<'sales' | 'company'>('sales'); |
| | | const reviewApproved = ref(true); |
| | | const reviewOpinion = ref(''); |
| | | function openReview(row: MesQcOqcApi.Oqc, stage: 'sales' | 'company') { |
| | | active.value = row; reviewStage.value = stage; reviewApproved.value = true; |
| | | reviewOpinion.value = ''; reviewOpen.value = true; |
| | | } |
| | | async function saveReview() { |
| | | if (!active.value?.id) return; |
| | | if (!reviewOpinion.value.trim()) { message.warning('请填写审批意见'); return; } |
| | | reviewBusy.value = true; |
| | | try { |
| | | await approveOqc(reviewStage.value, { id: active.value.id, approved: reviewApproved.value, opinion: reviewOpinion.value.trim() }); |
| | | message.success('审批完成'); reviewOpen.value = false; handleRefresh(); |
| | | } catch (error) { message.error((error as Error).message || '审批失败'); } |
| | | finally { reviewBusy.value = false; } |
| | | } |
| | | const historyOpen = ref(false); |
| | | const historyActions = ref<OqcAction[]>([]); |
| | | const actionLabels: Record<string, string> = { |
| | | INITIAL: '初检', RETEST: '复检', SALES_APPROVE: '销售部通过', SALES_REJECT: '销售部驳回', |
| | | COMPANY_APPROVE: '公司质检通过', COMPANY_REJECT: '公司质检驳回', |
| | | }; |
| | | async function openHistory(row: MesQcOqcApi.Oqc) { |
| | | if (!row.id) return; |
| | | active.value = row; |
| | | try { historyActions.value = await getOqcActions(row.id); historyOpen.value = true; } |
| | | catch (error) { message.error((error as Error).message || '读取流程记录失败'); } |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | /** 导出表格 */ |
| | | async function handleExport() { |
| | | const data = await exportOqc(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '出货检验单.xls', source: data }); |
| | | downloadFileFromBlobPart({ fileName: props.salesMode ? '销售质检单.xls' : '出货检验单.xls', source: data }); |
| | | } |
| | | |
| | | const columns = useGridColumns(); |
| | | if (props.salesMode && columns) { |
| | | columns.splice(columns.length - 1, 0, { |
| | | field: 'workflowStatus', title: '销售质检流程', width: 170, slots: { default: 'workflow' }, |
| | | }); |
| | | const actions = columns.at(-1); |
| | | if (actions) actions.width = 320; |
| | | } |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | columns, |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | sourceDocType: props.salesMode ? MesBizTypeEnum.WM_PRODUCT_SALES : undefined, |
| | | }); |
| | | }, |
| | | }, |
| | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | |
| | | <Grid table-title="出货检验单列表"> |
| | | <Grid :table-title="props.salesMode ? '销售质检单列表' : '出货检验单列表'"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.code }} |
| | | </Button> |
| | | </template> |
| | | <template #workflow="{ row }"> |
| | | <span>{{ workflowLabels[row.workflowStatus ?? 0] || '未知状态' }}</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | }, |
| | | ]" |
| | | /> |
| | | <Button v-if="props.salesMode && row.workflowStatus === 10 && hasAccessByCodes(['mes:qc-oqc:retest'])" type="link" @click="openRetest(row)">复检</Button> |
| | | <Button v-if="props.salesMode && [20, 50].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:sales-approve'])" type="link" @click="openReview(row, 'sales')">销售部审批</Button> |
| | | <Button v-if="props.salesMode && [30, 60].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:company-approve'])" type="link" @click="openReview(row, 'company')">公司质检审批</Button> |
| | | <Button v-if="props.salesMode && hasAccessByCodes(['mes:qc-oqc:query'])" type="link" @click="openHistory(row)">流程记录</Button> |
| | | </template> |
| | | </Grid> |
| | | <a-modal v-if="props.salesMode" v-model:open="retestOpen" title="销售质检复检" :confirm-loading="retestBusy" ok-text="提交复检" @ok="saveRetest"> |
| | | <p>检验单:{{ active?.code }}</p> |
| | | <a-form layout="vertical"> |
| | | <a-form-item label="本次检测数量"><a-input-number v-model:value="retestForm.checkQuantity" :min="0" style="width: 100%" /></a-form-item> |
| | | <a-form-item label="合格数量"><a-input-number v-model:value="retestForm.qualifiedQuantity" :min="0" style="width: 100%" /></a-form-item> |
| | | <a-form-item label="不合格数量"><a-input-number v-model:value="retestForm.unqualifiedQuantity" :min="0" style="width: 100%" /></a-form-item> |
| | | <a-form-item label="复检意见"><a-textarea v-model:value="retestForm.opinion" :rows="3" :maxlength="500" /></a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | <a-modal v-if="props.salesMode" v-model:open="reviewOpen" :title="reviewStage === 'sales' ? '销售部审批' : '公司质检审批'" :confirm-loading="reviewBusy" ok-text="提交审批" @ok="saveReview"> |
| | | <p>{{ active?.code }} · {{ workflowLabels[active?.workflowStatus ?? 0] }}</p> |
| | | <a-radio-group v-model:value="reviewApproved" style="display: flex; gap: 16px; margin: 12px 0 18px"> |
| | | <a-radio :value="true">同意{{ reviewStage === 'company' ? active?.workflowStatus === 30 ? '出货放行' : '不合格处置' : '上报公司质检' }}</a-radio> |
| | | <a-radio :value="false">驳回重检</a-radio> |
| | | </a-radio-group> |
| | | <a-textarea v-model:value="reviewOpinion" :rows="3" :maxlength="500" placeholder="请填写审批意见" /> |
| | | </a-modal> |
| | | <a-modal v-if="props.salesMode" v-model:open="historyOpen" :title="`流程记录 · ${active?.code || ''}`" :footer="null"> |
| | | <a-timeline v-if="historyActions.length"> |
| | | <a-timeline-item v-for="action in historyActions" :key="action.id"> |
| | | <strong>{{ actionLabels[action.action] || action.action }}</strong> |
| | | <span> · {{ action.createTime || '—' }} · 操作人 {{ action.operatorId || '—' }}</span> |
| | | <div v-if="action.opinion">{{ action.opinion }}</div> |
| | | </a-timeline-item> |
| | | </a-timeline> |
| | | <a-empty v-else description="暂无流程记录" /> |
| | | </a-modal> |
| | | </Page> |
| | | </template> |