吴羡
11 小时以前 5620ab475b200182a4d990ee04b029396f1db2dd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
<script lang="ts" setup>
import type { QcReportTemplateApi } from '#/api/mes/qc/report/template';
 
import { ref } from 'vue';
import { useRouter } from 'vue-router';
 
import { useVbenModal } from '@vben/common-ui';
 
import { Alert, Button, Empty, Radio, Spin, Tag } from 'ant-design-vue';
 
import { generateInstanceFromQc } from '#/api/mes/qc/report/instance';
import { getSelectableTemplates } from '#/api/mes/qc/report/template';
 
/**
 * 质检类型 → 报告落库用的 businessType(就是 MES 表名)。
 * <p>
 * 与 mes_qc_type 字典的数值不是一回事,反查报告时用的就是这四个字符串。
 */
const BUSINESS_TYPE_BY_QC_TYPE: Record<number, string> = {
  1: 'mes_qc_iqc',
  2: 'mes_qc_ipqc',
  3: 'mes_qc_oqc',
  4: 'mes_qc_rqc',
};
 
/** 打开弹窗时由质检单列表页带进来 */
interface ReportModalData {
  /** 质检类型:1 来料 2 过程 3 出货 4 退货 */
  qcType: number;
  /** 质检单编号 */
  qcId: number;
  /** 单据编号,仅用于标题展示 */
  qcNo?: string;
}
 
/** 出件结果,只留弹窗要展示的字段 */
interface ReportResult {
  id: number;
  reportNo: string;
  templateVersion: string;
  itemCount: number;
  undecidableCount: number;
  warnings: string[];
  errors: string[];
}
 
defineOptions({ name: 'QcReportGenerateModal' });
 
const router = useRouter();
 
const modalData = ref<ReportModalData>();
const templates = ref<QcReportTemplateApi.Template[]>([]);
const templateId = ref<number>();
const loadingTemplates = ref(false);
const generating = ref(false);
const result = ref<ReportResult>();
 
const [Modal, modalApi] = useVbenModal({
  footer: false,
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      modalData.value = undefined;
      templates.value = [];
      templateId.value = undefined;
      result.value = undefined;
      return;
    }
    const data = modalApi.getData<ReportModalData>();
    modalData.value = data;
    result.value = undefined;
    templateId.value = undefined;
    templates.value = [];
    if (data?.qcType) {
      loadTemplates(data.qcType);
    }
  },
});
 
/**
 * 只列同类型、已启用、且当前版本已发布的模板。
 * <p>
 * 这三重过滤全在后端做(/qc-report/template/selectable),前端不再自己推导:
 * report_type 存的是 mes_qc_type 的数值字符串("1"~"4"),拿「IQC」这类英文名去比会一个都查不到;
 * 且 currentVersion 非空 ≠ 能用——停用版本不会清空模板的 currentVersion,
 * 这种模板选中后出件必报「模板版本未发布」。
 */
async function loadTemplates(qcType: number) {
  loadingTemplates.value = true;
  try {
    templates.value = await getSelectableTemplates(String(qcType));
  } catch {
    templates.value = [];
  } finally {
    loadingTemplates.value = false;
  }
}
 
/** 生成报告:数据全部由后端从质检单取,这里只决定「哪张单据 + 哪张模板」 */
async function handleGenerate() {
  const data = modalData.value;
  const chosen = templateId.value;
  if (!data || !chosen || generating.value) {
    return;
  }
  generating.value = true;
  try {
    const resp = await generateInstanceFromQc({
      qcType: data.qcType,
      qcId: data.qcId,
      templateId: chosen,
    });
    result.value = {
      id: resp.id,
      reportNo: resp.reportNo,
      templateVersion: resp.templateVersion,
      itemCount: resp.itemCount,
      undecidableCount: resp.undecidableCount,
      warnings: resp.warnings ?? [],
      errors: resp.errors ?? [],
    };
  } catch {
    // 失败报文已由请求拦截器弹出(五道校验按业务优先级只报第一处),这里不重复提示。
    // 失败都是「数据或选择不对」,重试无用,按报文提示去改数据或换模板。
  } finally {
    generating.value = false;
  }
}
 
/** 生成完跳到报告实例列表,并把单据过滤条件带过去,直接看到这份报告 */
function handleGoReportList() {
  const data = modalData.value;
  modalApi.close();
  if (!data) {
    return;
  }
  router.push({
    path: '/qc/report-instance',
    query: {
      businessType: BUSINESS_TYPE_BY_QC_TYPE[data.qcType],
      businessId: String(data.qcId),
    },
  });
}
</script>
 
<template>
  <Modal
    :title="`出报告 - ${modalData?.qcNo ?? ''}`"
    class="w-3/5"
    :mask-closable="false"
  >
    <!-- 选模板阶段 -->
    <template v-if="!result">
      <Spin :spinning="loadingTemplates">
        <div class="px-1 py-2">
          <div class="mb-3 text-sm text-gray-600">
            报告数据由后端从该质检单读取,只需选一张模板;模板类型必须与单据类型一致。
          </div>
          <div v-if="templates.length === 0 && !loadingTemplates">
            <Empty description="没有可用的同类型模板,请先到「报告模板设计」里配好并发布一个版本" />
          </div>
          <Radio.Group v-else v-model:value="templateId" class="w-full">
            <div class="flex flex-col gap-2">
              <Radio v-for="item in templates" :key="item.id" :value="item.id">
                <span>{{ item.templateName }}</span>
                <Tag class="ml-2" color="green">{{ item.currentVersion }}</Tag>
                <span v-if="item.industry" class="ml-2 text-xs text-gray-400">
                  {{ item.industry }}
                </span>
              </Radio>
            </div>
          </Radio.Group>
        </div>
      </Spin>
      <div class="mt-4 flex justify-end gap-2">
        <Button @click="modalApi.close()">取消</Button>
        <Button
          type="primary"
          :loading="generating"
          :disabled="!templateId"
          @click="handleGenerate"
        >
          生成报告
        </Button>
      </div>
    </template>
 
    <!-- 结果阶段:告警必须展示,不要静默吞掉 -->
    <template v-else>
      <div class="px-1 py-2">
        <Alert type="success" show-icon>
          <template #message>
            报告已生成:<b>{{ result.reportNo }}</b>
          </template>
          <template #description>
            <div>
              模板版本 {{ result.templateVersion }};写入检验项
              {{ result.itemCount }} 条
            </div>
          </template>
        </Alert>
 
        <Alert
          v-if="result.undecidableCount > 0"
          class="mt-3"
          type="warning"
          show-icon
          :message="`本次有 ${result.undecidableCount} 项没有判定结论`"
          description="缺规格上下限又没规则的项标为「无判定规则」,不计入合格率也不参与报告结论;规则执行失败的项标为「待判定」,仍计入合格率。两项都计入总数。如需判定,请先在质检单上补规格上下限,或修好模板里的规则后重新出件。"
        />
        <Alert
          v-else
          class="mt-3"
          type="info"
          show-icon
          message="全部检验项均已判定,没有无法判定的项"
        />
 
        <div v-if="result.warnings.length > 0" class="mt-3">
          <div class="mb-1 text-sm font-medium text-gray-700">
            数据取舍提示({{ result.warnings.length }})
          </div>
          <Alert type="warning">
            <ul class="m-0 list-disc pl-4">
              <li v-for="(w, i) in result.warnings" :key="i">{{ w }}</li>
            </ul>
          </Alert>
        </div>
 
        <div v-if="result.errors.length > 0" class="mt-3">
          <div class="mb-1 text-sm font-medium text-gray-700">
            模板字段缺口({{ result.errors.length }})
          </div>
          <Alert type="error">
            <ul class="m-0 list-disc pl-4">
              <li v-for="(e, i) in result.errors" :key="i">{{ e }}</li>
            </ul>
          </Alert>
        </div>
 
        <div class="mt-3 text-xs text-gray-400">
          以上提示只在本次响应里出现,不落库;事后回看历史报告时看不到,需要留痕请自行记录。
        </div>
      </div>
      <div class="mt-4 flex justify-end gap-2">
        <Button @click="modalApi.close()">关闭</Button>
        <Button type="primary" @click="handleGoReportList">查看报告列表</Button>
      </div>
    </template>
  </Modal>
</template>