| | |
| | | import type { UserSimple } from "../../../../api/purchase"; |
| | | import { getSupplierSimpleList, type SupplierItem } from "../../../../api/supplier"; |
| | | import { generateAutoCode, getWorkstationPage, type MesWorkstation } from "../../../../api/process-design"; |
| | | import { createFeedback, getBatchRequirements, getFeedback, getTaskPage, getWorkOrder, getWorkOrderPage, getWorkOrderParamRecordPage, submitFeedback, updateFeedback, type FeedbackBatchRequirement, type ProFeedback, type ProTask, type WorkOrder, type WorkOrderParamRecordDetail } from "../../../../api/production"; |
| | | import { approveFeedback, createFeedback, getBatchRequirements, getFeedback, getTaskPage, getWorkOrder, getWorkOrderPage, getWorkOrderParamRecordPage, rejectFeedback, submitFeedback, updateFeedback, type FeedbackBatchRequirement, type ProFeedback, type ProTask, type WorkOrder, type WorkOrderParamRecordDetail } from "../../../../api/production"; |
| | | import { useAuth } from "../../../../stores/auth"; |
| | | import { dateText, dictLabel, dictOptions } from "../../dv/shared"; |
| | | |
| | | const { hasPermission, state } = useAuth(); |
| | | const id = ref<number>(); |
| | | const mode = ref<"edit" | "detail" | "submit">("edit"); |
| | | const mode = ref<"edit" | "detail" | "submit" | "approve">("edit"); |
| | | const saving = ref(false); |
| | | const generating = ref(false); |
| | | const dicts = ref<DictData[]>([]); |
| | |
| | | const paramDetails = ref<WorkOrderParamRecordDetail[]>([]); |
| | | const batchReq = ref<FeedbackBatchRequirement>({}); |
| | | const batch = reactive<Record<string, string | number | undefined>>({}); |
| | | const readonly = computed(() => mode.value === "detail" || mode.value === "submit"); |
| | | const readonly = computed(() => mode.value === "detail" || mode.value === "submit" || mode.value === "approve"); |
| | | const canSave = computed(() => !readonly.value && (id.value ? hasPermission("mes:pro-feedback:update") : hasPermission("mes:pro-feedback:create"))); |
| | | const canSubmit = computed(() => id.value && form.status === 0 && hasPermission("mes:pro-feedback:update")); |
| | | const canApprove = computed(() => mode.value === "approve" && form.status === 2 && form.approveUserId === state.user?.id && hasPermission("mes:pro-feedback:approve")); |
| | | const feedbackTypes = computed(() => dictOptions(dicts.value, "mes_pro_feedback_type")); |
| | | const qualityOptions = computed(() => dictOptions(dicts.value, "mes_wm_quality_status")); |
| | | const taskOptions = computed(() => tasks.value.filter(item => !form.workOrderId || item.workOrderId === form.workOrderId)); |
| | |
| | | uni.showToast({ title: error instanceof Error ? error.message : "提交失败", icon: "none" }); |
| | | } |
| | | } |
| | | function validateBatch() { |
| | | if (needExpireDate.value && !batch.expireDate) return uni.showToast({ title: "请选择有效期", icon: "none" }), false; |
| | | if (needReceiptDate.value && !batch.receiptDate) return uni.showToast({ title: "请选择入库日期", icon: "none" }), false; |
| | | if (needLotNumber.value && !batch.lotNumber) return uni.showToast({ title: "请输入生产批号", icon: "none" }), false; |
| | | if (needTool.value && !batch.toolId) return uni.showToast({ title: "请输入工具ID", icon: "none" }), false; |
| | | if (needMold.value && !batch.moldId) return uni.showToast({ title: "请输入模具ID", icon: "none" }), false; |
| | | if (needQualityStatus.value && batch.qualityStatus === undefined) return uni.showToast({ title: "请选择质量状态", icon: "none" }), false; |
| | | if (needVendor.value && !batch.vendorId) return uni.showToast({ title: "请选择供应商", icon: "none" }), false; |
| | | if (needClient.value && !batch.clientId) return uni.showToast({ title: "请选择客户", icon: "none" }), false; |
| | | if (needSalesOrderCode.value && !batch.salesOrderCode) return uni.showToast({ title: "请输入销售订单编号", icon: "none" }), false; |
| | | if (needPurchaseOrderCode.value && !batch.purchaseOrderCode) return uni.showToast({ title: "请输入采购订单编号", icon: "none" }), false; |
| | | return true; |
| | | } |
| | | async function approve() { |
| | | if (!state.accessToken || !id.value || saving.value || !canApprove.value || !validateBatch()) return; |
| | | const confirmed = await new Promise<boolean>(resolve => uni.showModal({ title: "确认审批", content: "确认审批通过该报订单?", success: result => resolve(result.confirm) })); |
| | | if (!confirmed) return; |
| | | saving.value = true; |
| | | try { |
| | | const finished = await approveFeedback(state.accessToken, { id: id.value, ...batch }); |
| | | uni.showToast({ title: finished ? "报订单已审批完成" : "审批通过,等待质量检验", icon: "success" }); |
| | | setTimeout(() => uni.navigateBack(), 500); |
| | | } catch (error) { |
| | | uni.showToast({ title: error instanceof Error ? error.message : "审批失败", icon: "none" }); |
| | | } finally { |
| | | saving.value = false; |
| | | } |
| | | } |
| | | async function reject() { |
| | | if (!state.accessToken || !id.value || saving.value || !canApprove.value) return; |
| | | const confirmed = await new Promise<boolean>(resolve => uni.showModal({ title: "确认驳回", content: "确认审批不通过该报订单?", success: result => resolve(result.confirm) })); |
| | | if (!confirmed) return; |
| | | saving.value = true; |
| | | try { |
| | | await rejectFeedback(state.accessToken, id.value); |
| | | uni.showToast({ title: "报订单已驳回", icon: "success" }); |
| | | setTimeout(() => uni.navigateBack(), 500); |
| | | } catch (error) { |
| | | uni.showToast({ title: error instanceof Error ? error.message : "驳回失败", icon: "none" }); |
| | | } finally { |
| | | saving.value = false; |
| | | } |
| | | } |
| | | async function save(stay = false) { |
| | | if (!form.code?.trim()) return uni.showToast({ title: "请填写报订单号", icon: "none" }); |
| | | if (!form.type) return uni.showToast({ title: "请选择报工类型", icon: "none" }); |
| | |
| | | } |
| | | onLoad(async query => { |
| | | id.value = query.id ? Number(query.id) : undefined; |
| | | mode.value = query.mode === "detail" ? "detail" : query.mode === "submit" ? "submit" : "edit"; |
| | | mode.value = query.mode === "detail" ? "detail" : query.mode === "submit" ? "submit" : query.mode === "approve" ? "approve" : "edit"; |
| | | uni.setNavigationBarTitle({ title: mode.value === "approve" ? "审批生产报工" : mode.value === "detail" ? "生产报工详情" : mode.value === "submit" ? "提交生产报工" : id.value ? "编辑生产报工" : "新增生产报工" }); |
| | | if (!state.accessToken) return; |
| | | try { |
| | | const [ds, taskPage, orderPage, ws, us, supplierData, customerData] = await Promise.all([ |
| | |
| | | if (id.value) { |
| | | Object.assign(form, await getFeedback(state.accessToken, id.value)); |
| | | await loadParamDetails(form.taskId); |
| | | if (mode.value === "submit") batchReq.value = await getBatchRequirements(state.accessToken, id.value); |
| | | if (mode.value === "submit" || mode.value === "approve") batchReq.value = await getBatchRequirements(state.accessToken, id.value); |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: error instanceof Error ? error.message : "数据加载失败", icon: "none" }); |
| | |
| | | <view class="section-title">工艺参数</view> |
| | | <view v-for="item in paramDetails" :key="item.id" class="field"> |
| | | <text>{{ item.paramName || item.paramCode }}<text v-if="item.required" class="required"> *</text><text v-if="item.unitMeasureName">({{ item.unitMeasureName }})</text></text> |
| | | <input v-model="item.paramValue" :disabled="mode==='detail'" placeholder="请输入参数值" /> |
| | | <input v-model="item.paramValue" :disabled="mode==='detail' || mode==='approve'" placeholder="请输入参数值" /> |
| | | </view> |
| | | </view> |
| | | <view v-if="mode==='submit' && batchReq.needBatchInput" class="form-card batch-card"> |
| | | <view v-if="(mode==='submit' || mode==='approve') && batchReq.needBatchInput" class="form-card batch-card"> |
| | | <view class="section-title">批次属性</view> |
| | | <view v-if="needExpireDate" class="field"><text>有效期 <text class="required">*</text></text><picker mode="date" :value="String(batch.expireDate||'').slice(0,10)" @change="batch.expireDate=$event.detail.value+' 00:00:00'"><view class="picker"><text>{{ dateText(batch.expireDate).replace('--','请选择有效期') }}</text><text>›</text></view></picker></view> |
| | | <view v-if="needReceiptDate" class="field"><text>入库日期 <text class="required">*</text></text><picker mode="date" :value="String(batch.receiptDate||'').slice(0,10)" @change="batch.receiptDate=$event.detail.value+' 00:00:00'"><view class="picker"><text>{{ dateText(batch.receiptDate).replace('--','请选择入库日期') }}</text><text>›</text></view></picker></view> |
| | |
| | | </view> |
| | | <button v-if="canSave" class="save" :loading="saving" :disabled="saving" @click="save()">保存</button> |
| | | <button v-if="canSubmit" class="confirm" :loading="saving" :disabled="saving" @click="submit">提交</button> |
| | | <view v-if="canApprove" class="approval-actions"> |
| | | <button class="reject" :loading="saving" :disabled="saving" @click="reject">不通过</button> |
| | | <button class="confirm" :loading="saving" :disabled="saving" @click="approve">通过</button> |
| | | </view> |
| | | <button v-else-if="readonly && hasPermission('mes:pro-feedback:update') && form.status === 0" class="save" @click="mode='edit'">编辑</button> |
| | | </view> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | .page{min-height:100vh;padding:24rpx 30rpx 70rpx;box-sizing:border-box;background:#f6f7f9}.form-card{overflow:hidden;background:#fff;border-radius:16rpx}.batch-card{margin-top:22rpx}.section-title{padding:24rpx;color:#172b45;font-size:31rpx;font-weight:600;border-bottom:1rpx solid #eef1f5}.field{display:flex;align-items:center;min-height:96rpx;padding:0 24rpx;gap:20rpx;border-bottom:1rpx solid #eef1f5}.field>text:first-child{width:210rpx;flex:none;color:#33455d;font-size:27rpx}.field input,.field picker,.picker,.value{min-width:0;flex:1;color:#1e3049;font-size:27rpx}.field input,.value{text-align:right}.picker{display:flex;align-items:center;justify-content:space-between;color:#65758a}.picker text:first-child,.value{overflow:hidden;text-align:right;text-overflow:ellipsis;white-space:nowrap}.picker text:last-child{margin-left:16rpx;color:#8092a7;font-size:36rpx}.required{color:#e23b3b}.code-field button{flex:none;min-width:92rpx;padding:0 12rpx;margin:0;color:var(--color-primary, #1769d8);font-size:23rpx;line-height:56rpx;background:#fff;border:1rpx solid var(--color-primary, #1769d8);border-radius:8rpx}.code-field button::after,.save::after,.confirm::after{border:0}.column{align-items:flex-start;flex-direction:column;padding:20rpx 24rpx}.column>text:first-child{width:auto}.column textarea{width:100%;height:150rpx;font-size:27rpx;box-sizing:border-box}.save,.confirm{margin-top:30rpx;color:#fff;font-size:30rpx;background:var(--color-primary, #1769d8);border-radius:12rpx}.confirm{background:#16a36a} |
| | | .page{min-height:100vh;padding:24rpx 30rpx 70rpx;box-sizing:border-box;background:#f6f7f9}.form-card{overflow:hidden;background:#fff;border-radius:16rpx}.batch-card{margin-top:22rpx}.section-title{padding:24rpx;color:#172b45;font-size:31rpx;font-weight:600;border-bottom:1rpx solid #eef1f5}.field{display:flex;align-items:center;min-height:96rpx;padding:0 24rpx;gap:20rpx;border-bottom:1rpx solid #eef1f5}.field>text:first-child{width:210rpx;flex:none;color:#33455d;font-size:27rpx}.field input,.field picker,.picker,.value{min-width:0;flex:1;color:#1e3049;font-size:27rpx}.field input,.value{text-align:right}.picker{display:flex;align-items:center;justify-content:space-between;color:#65758a}.picker text:first-child,.value{overflow:hidden;text-align:right;text-overflow:ellipsis;white-space:nowrap}.picker text:last-child{margin-left:16rpx;color:#8092a7;font-size:36rpx}.required{color:#e23b3b}.code-field button{flex:none;min-width:92rpx;padding:0 12rpx;margin:0;color:var(--color-primary, #1769d8);font-size:23rpx;line-height:56rpx;background:#fff;border:1rpx solid var(--color-primary, #1769d8);border-radius:8rpx}.code-field button::after,.save::after,.confirm::after,.reject::after{border:0}.column{align-items:flex-start;flex-direction:column;padding:20rpx 24rpx}.column>text:first-child{width:auto}.column textarea{width:100%;height:150rpx;font-size:27rpx;box-sizing:border-box}.save,.confirm,.reject{margin-top:30rpx;color:#fff;font-size:30rpx;border-radius:12rpx}.save{background:var(--color-primary, #1769d8)}.confirm{background:#16a36a}.approval-actions{display:flex;gap:20rpx}.approval-actions button{flex:1}.reject{color:#c65050;background:#fff;border:1rpx solid #e3a0a0} |
| | | </style> |