6 天以前 f569e2257372a2f940aace9ad151fd758196eb9a
修改客户,销售,协同,报价,质量
已添加17个文件
已修改26个文件
已删除2个文件
2039 ■■■■■ 文件已修改
doc/sql/20260609_knowledge_base_vector.sql 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/20260613_customer_level.sql 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/20260613_quality_attachment_optimize.sql 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/20260613_quality_module_completion.sql 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/20260613_sales_ledger_approval.sql 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/customer_follow_up_export.md 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/customer_handover.md 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/quality_attachment_optimize.md 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/quality_unqualified_order.md 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sales_ledger_approval.md 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/不合格管理模块逻辑分析.md 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerFollowUpMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerFollowUpService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/vo/CustomerFollowUpExportVo.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/TypeEnums.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionPlan.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedOrderController.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedOrderMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityInspect.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityTestStandard.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityUnqualifiedOrder.java 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedOrderService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedOrderServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerFollowUpMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityUnqualifiedOrderMapper.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/20260609_knowledge_base_vector.sql
ÎļþÒÑɾ³ý
doc/sql/20260613_customer_level.sql
ÎļþÒÑɾ³ý
doc/sql/20260613_quality_attachment_optimize.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
-- ============================================================
-- ä¸åˆæ ¼ç®¡ç†æ¨¡å—优化
-- 1. åºŸå¼ƒ quality_inspect_file è¡¨ï¼Œé™„件改用 storage_attachment ä½“ç³»
-- 2. æ£€éªŒè®°å½•附件通过 storage_attachment (record_type='quality_inspect') å…³è”
-- 3. ä¸åˆæ ¼å“é™„件通过 storage_attachment (record_type='quality_unqualified') å…³è”
-- ç”Ÿæˆæ—¥æœŸï¼š2026-06-13
-- ============================================================
-- å¯é€‰ï¼šå¦‚æžœ quality_inspect_file è¡¨å­˜åœ¨ä¸”有数据,需要先迁移数据再删除
-- æ•°æ®è¿ç§»ï¼ˆå°†æ—§é™„件数据迁移到 storage_attachment è¡¨ï¼‰
-- INSERT INTO storage_attachment (name, url, file_size, application, record_type, record_id, create_time, create_user, tenant_id, dept_id, is_deleted)
-- SELECT name, url, file_size, 'FILE', 'quality_inspect', inspect_id, create_time, create_user, tenant_id, dept_id, 0
-- FROM quality_inspect_file;
-- åˆ é™¤æ—§çš„æ£€éªŒé™„件表(确认数据已迁移后执行)
-- DROP TABLE IF EXISTS quality_inspect_file;
doc/sql/20260613_quality_module_completion.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
-- ============================================================
-- ä¸åˆæ ¼ç®¡ç†æ¨¡å—补全
-- 1. å·²æœ‰è¡¨è¡¥å……缺失字段
-- 2. æ–°å»º quality_unqualified_order ä¸åˆæ ¼å“å¤„理单表
-- ç”Ÿæˆæ—¥æœŸï¼š2026-06-13
-- ============================================================
-- Phase 1A: quality_inspect è¡¥å…… process_type å­—段
ALTER TABLE quality_inspect ADD COLUMN process_type INT DEFAULT NULL COMMENT '工序类型,匹配数据字典 product_process_type';
-- Phase 1B: quality_unqualified è¡¥å…… 4 ä¸ªå­—段
ALTER TABLE quality_unqualified ADD COLUMN reason_analysis VARCHAR(500) DEFAULT NULL COMMENT '原因分析';
ALTER TABLE quality_unqualified ADD COLUMN preventive_corrective VARCHAR(500) DEFAULT NULL COMMENT '预防与纠正措施';
ALTER TABLE quality_unqualified ADD COLUMN loss_working VARCHAR(200) DEFAULT NULL COMMENT '工时损失';
ALTER TABLE quality_unqualified ADD COLUMN loss_material VARCHAR(200) DEFAULT NULL COMMENT '材料费损失';
-- Phase 1C: quality_test_standard è¡¥å…… process_type å­—段
ALTER TABLE quality_test_standard ADD COLUMN process_type INT DEFAULT NULL COMMENT '工序类型';
-- Phase 2: æ–°å»º quality_unqualified_order ä¸åˆæ ¼å“å¤„理单表
CREATE TABLE IF NOT EXISTS quality_unqualified_order (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
    order_no VARCHAR(32) NOT NULL COMMENT '处理单编号,前缀BHG+日期+自增序号',
    unqualified_id BIGINT COMMENT '关联quality_unqualified.id',
    project_name VARCHAR(100) COMMENT '项目名称',
    project_no VARCHAR(50) COMMENT '项目编号',
    equipment_id BIGINT COMMENT '关联设备ID',
    equipment_name VARCHAR(100) COMMENT '设备名称',
    equipment_drawing_no VARCHAR(50) COMMENT '设备图号',
    material_name VARCHAR(100) COMMENT '物料/部件名称',
    product_model_id BIGINT COMMENT '关联产品型号ID',
    material_drawing_no VARCHAR(50) COMMENT '物料图号',
    specification_model VARCHAR(100) COMMENT '型号规格',
    material_quality VARCHAR(50) COMMENT '材质',
    quantity DECIMAL(10,2) COMMENT '总数量',
    unqualified_quantity DECIMAL(10,2) COMMENT '不合格数量',
    unqualified_process TINYINT COMMENT '不合格工序:1=来料,2=制程,3=成品',
    supplier_name VARCHAR(100) COMMENT '供应商名称',
    inspector_name VARCHAR(50) COMMENT '检验员',
    inspect_date DATE COMMENT '检验日期',
    responsible_person VARCHAR(50) COMMENT '责任人',
    responsible_dept VARCHAR(50) COMMENT '责任部门',
    problem_description VARCHAR(500) COMMENT '问题描述',
    reason_analysis VARCHAR(500) COMMENT '原因分析及建议',
    correction_action VARCHAR(500) COMMENT '纠正措施',
    disposal_method TINYINT COMMENT '处置方式:1=让步接收,2=厂内维修,3=返厂维修,4=换货,5=退货,6=报废',
    repair_evaluation VARCHAR(500) COMMENT '厂内/返厂维修评估',
    preventive_action VARCHAR(500) COMMENT '预防措施',
    status TINYINT DEFAULT 0 COMMENT '状态:0=草稿,1=待审批,2=审批中,3=已完成,4=已驳回',
    remark VARCHAR(500) COMMENT '备注',
    create_by INT COMMENT '创建用户',
    update_by INT COMMENT '修改用户',
    create_time DATETIME COMMENT '创建时间',
    update_time DATETIME COMMENT '修改时间',
    tenant_id BIGINT COMMENT '租户ID',
    dept_id BIGINT COMMENT '部门ID',
    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0=否,1=是'
);
doc/sql/20260613_sales_ledger_approval.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
-- é”€å”®å°è´¦å¢žåŠ å®¡æ‰¹çŠ¶æ€å­—æ®µ
-- 0-待审批,1-审批中,2-已通过,3-已驳回
ALTER TABLE sales_ledger
ADD COLUMN IF NOT EXISTS approval_status INT DEFAULT 0 COMMENT '审批状态:0-待审批,1-审批中,2-已通过,3-已驳回';
-- ç”Ÿäº§ä¸»è®¡åˆ’增加审批状态字段(与销售台账审批状态同步)
ALTER TABLE production_plan
ADD COLUMN IF NOT EXISTS approval_status INT DEFAULT NULL COMMENT '审批状态:0-待审批,1-审批中,2-已通过,3-已驳回';
docs/customer_follow_up_export.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
# æ´½è°ˆè¿›åº¦å¯¼å‡º
## æ¶‰åŠé¡µé¢
- **私海客户列表** â€” æ“ä½œåˆ—或工具栏增加"导出洽谈进度"按钮
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/basic/customer-follow/export` | å¯¼å‡ºæ´½è°ˆè¿›åº¦ |
**请求参数(Query String æˆ– Form Data):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerName | String | å¦ | å®¢æˆ·åç§°ï¼ˆæ¨¡ç³Šæœç´¢ï¼‰ |
| customerType | String | å¦ | å®¢æˆ·åˆ†ç±» |
**响应:** æ–‡ä»¶æµï¼ˆExcel ä¸‹è½½ï¼‰ï¼Œå“åº”头 `Content-Disposition: attachment; filename=洽谈进度.xlsx`
## å¯¼å‡ºè¡¨æ ¼åˆ—
| åˆ—名 | å­—段 | è¯´æ˜Ž |
|------|------|------|
| å®¢æˆ·åç§° | customerName | |
| å®¢æˆ·åˆ†ç±» | customerType | |
| å®¢æˆ·ç­‰çº§ | customerLevel | |
| è”系人 | contactPerson | |
| è”系电话 | contactPhone | |
| ç»´æŠ¤äºº | maintainer | |
| è·Ÿè¿›æ–¹å¼ | followUpMethod | |
| è·Ÿè¿›ç¨‹åº¦ | followUpLevel | |
| è·Ÿè¿›æ—¶é—´ | followUpTime | æ ¼å¼ yyyy-MM-dd HH:mm:ss |
| è·Ÿè¿›äºº | followerUserName | |
| è·Ÿè¿›å†…容 | content | |
每行一条跟进记录(客户有多条跟进时展开多行)。
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. å·¥å…·æ æˆ–操作列增加按钮
```html
<el-button type="warning" icon="el-icon-download" @click="exportFollowUp">导出洽谈进度</el-button>
```
### 2. å¯¼å‡ºæ–¹æ³•
```js
// å¯¼å‡ºæ´½è°ˆè¿›åº¦
exportFollowUp() {
  this.$confirm('确认导出所有客户的洽谈进度数据吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'info'
  }).then(() => {
    this.downloadFollowUpExcel();
  }).catch(() => {});
},
// æ‰§è¡Œå¯¼å‡º
downloadFollowUpExcel() {
  const params = new URLSearchParams();
  if (this.queryParams.customerName) {
    params.append('customerName', this.queryParams.customerName);
  }
  if (this.queryParams.customerType) {
    params.append('customerType', this.queryParams.customerType);
  }
  // POST æ–¹å¼ä¸‹è½½æ–‡ä»¶
  fetch('/basic/customer-follow/export', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: params.toString(),
    responseType: 'blob'
  }).then(response => {
    if (!response.ok) throw new Error('导出失败');
    return response.blob();
  }).then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = '洽谈进度.xlsx';
    a.click();
    window.URL.revokeObjectURL(url);
  }).catch(err => {
    this.$message.error('导出失败:' + err.message);
  });
}
```
> æ³¨ï¼šå¦‚果项目中有封装好的文件下载工具函数(如 `downloadBlob`),建议直接复用。
docs/customer_handover.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
# å®¢æˆ·æ¡£æ¡ˆ â€” å®¢æˆ·äº¤æŽ¥åŠŸèƒ½
## æ¶‰åŠé¡µé¢
- **私海客户列表** â€” æ“ä½œåˆ—增加"客户交接"按钮
- å…¬æµ·å®¢æˆ·ä¸æ˜¾ç¤ºæ­¤æŒ‰é’®
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/basic/customer/handover` | å®¢æˆ·äº¤æŽ¥ |
**请求体:**
```json
{
  "id": 123,
  "maintainer": "张三"
}
```
**响应:**
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. ç§æµ·å®¢æˆ·åˆ—表 â€” æ“ä½œåˆ—增加按钮
```html
<el-button type="text" @click="handleHandover(row)">客户交接</el-button>
```
仅在 `row.type === 0`(私海客户)时显示。
### 2. äº¤æŽ¥å¼¹çª—
```html
<el-dialog title="客户交接" :visible.sync="handoverDialogVisible" width="400px">
  <el-form :model="handoverForm" label-width="100px">
    <el-form-item label="当前维护人">
      <span>{{ handoverForm.oldMaintainer }}</span>
    </el-form-item>
    <el-form-item label="新维护人" prop="maintainer">
      <el-select v-model="handoverForm.maintainer" placeholder="请选择新维护人" filterable>
        <el-option
          v-for="user in userList"
          :key="user.userId"
          :label="user.userName"
          :value="user.userName"
        />
      </el-select>
    </el-form-item>
  </el-form>
  <div slot="footer">
    <el-button @click="handoverDialogVisible = false">取消</el-button>
    <el-button type="primary" @click="submitHandover">确定</el-button>
  </div>
</el-dialog>
```
### 3. data æ•°æ®
```js
data() {
  return {
    handoverDialogVisible: false,
    handoverForm: {
      id: null,
      oldMaintainer: '',
      maintainer: ''
    },
    userList: [],  // è´Ÿè´£äººåˆ—表,复用现有用户下拉数据源
  }
}
```
### 4. æ–¹æ³•
```js
// æ‰“开交接弹窗
handleHandover(row) {
  this.handoverForm.id = row.id;
  this.handoverForm.oldMaintainer = row.maintainer;
  this.handoverForm.maintainer = '';
  this.handoverDialogVisible = true;
},
// æäº¤äº¤æŽ¥
submitHandover() {
  if (!this.handoverForm.maintainer) {
    this.$message.warning('请选择新维护人');
    return;
  }
  postRequest('/basic/customer/handover', {
    id: this.handoverForm.id,
    maintainer: this.handoverForm.maintainer
  }).then(res => {
    if (res.code === 200) {
      this.$message.success('交接成功');
      this.handoverDialogVisible = false;
      this.getList();  // åˆ·æ–°åˆ—表
    }
  });
}
```
## æ³¨æ„äº‹é¡¹
- äº¤æŽ¥æŒ‰é’®ä»…在私海客户列表显示(`row.type === 0`)
- æ–°ç»´æŠ¤äººä¸‹æ‹‰æ•°æ®æºå¯å¤ç”¨ç³»ç»Ÿä¸­å·²æœ‰çš„用户选择器接口(如 `/system/user/list`)
- äº¤æŽ¥æˆåŠŸåŽåˆ—è¡¨è‡ªåŠ¨åˆ·æ–°ï¼Œç»´æŠ¤äººåˆ—æ›´æ–°ä¸ºæ–°å€¼
- åŽç«¯ä¼šåŒæ­¥æ›´æ–° `maintenanceTime`(维护时间)为当前时间
docs/quality_attachment_optimize.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,131 @@
# ä¸åˆæ ¼ç®¡ç†æ¨¡å— â€” å‰ç«¯è”调文档
## æ¶‰åŠé¡µé¢
- **检验记录列表/详情** â€” é™„件上传/查看
- **不合格品管理列表/详情** â€” é™„件上传/查看
## å˜æ›´è¯´æ˜Ž
附件体系从独立的 `quality_inspect_file` è¡¨æ”¹ä¸ºä½¿ç”¨ç³»ç»Ÿç»Ÿä¸€çš„ `storage_attachment` è¡¨ï¼Œé€šè¿‡ `recordType` + `recordId` å…³è”。前端附件上传/回显方式和系统其他模块(如采购台账、设备维修等)保持一致。
## API å˜æ›´
### æ£€éªŒè®°å½•
| æŽ¥å£ | å˜æ›´è¯´æ˜Ž |
|------|---------|
| `POST /quality/qualityInspect/add` | è¯·æ±‚体新增 `storageBlobDTOs` å­—段 |
| `POST /quality/qualityInspect/update` | è¯·æ±‚体新增 `storageBlobDTOs` å­—段 |
| `GET /quality/qualityInspect/{id}` | å“åº”新增 `storageBlobVOs` å­—段 |
### ä¸åˆæ ¼å“
| æŽ¥å£ | å˜æ›´è¯´æ˜Ž |
|------|---------|
| `POST /quality/qualityUnqualified/add` | è¯·æ±‚体新增 `storageBlobDTOs` å­—段 |
| `POST /quality/qualityUnqualified/update` | è¯·æ±‚体新增 `storageBlobDTOs` å­—段 |
| `GET /quality/qualityUnqualified/{id}` | å“åº”新增 `storageBlobVOs` å­—段 |
## æ•°æ®ç»“æž„
### StorageBlobDTO(上传时传入)
```json
{
  "id": "临时文件ID(字符串)",
  "name": "文件名",
  "url": "文件路径",
  "fileSize": 1024
}
```
### StorageBlobVO(查询时返回)
```json
{
  "id": 1,
  "name": "检验报告.pdf",
  "url": "/upload/20260613/xxx.pdf",
  "fileSize": 102400,
  "application": "FILE"
}
```
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. æ£€éªŒè®°å½• â€” æ–°å¢ž/编辑表单增加附件上传
```html
<!-- é™„件上传组件(使用系统已有的文件上传组件) -->
<file-upload
  v-model="form.storageBlobDTOs"
  :file-list="form.storageBlobVOs"
  @change="handleFileChange"
/>
```
### 2. æ£€éªŒè®°å½• â€” è¯¦æƒ…页展示附件
```html
<el-form-item label="附件">
  <template v-if="form.storageBlobVOs && form.storageBlobVOs.length > 0">
    <div v-for="file in form.storageBlobVOs" :key="file.id">
      <a :href="file.url" target="_blank">{{ file.name }}</a>
      <span style="margin-left: 10px; color: #999">{{ (file.fileSize / 1024).toFixed(1) }}KB</span>
    </div>
  </template>
  <span v-else>无附件</span>
</el-form-item>
```
### 3. ä¸åˆæ ¼å“ â€” æ–°å¢ž/编辑表单增加附件上传
```html
<file-upload
  v-model="form.storageBlobDTOs"
  :file-list="form.storageBlobVOs"
/>
```
### 4. ä¸åˆæ ¼å“ â€” è¯¦æƒ…展示附件
```html
<el-form-item label="附件">
  <template v-if="form.storageBlobVOs && form.storageBlobVOs.length > 0">
    <div v-for="file in form.storageBlobVOs" :key="file.id">
      <a :href="file.url" target="_blank">{{ file.name }}</a>
    </div>
  </template>
  <span v-else>无附件</span>
</el-form-item>
```
### 5. è¡¨å• data è¡¥å……字段
```js
data() {
  return {
    form: {
      storageBlobDTOs: [],  // ä¸Šä¼ æ—¶ä¼ å…¥
      storageBlobVOs: [],   // æŸ¥è¯¢æ—¶è¿”回
      // ... å…¶ä»–字段
    }
  }
}
```
## åºŸå¼ƒå†…容
- `quality_inspect_file` è¡¨åºŸå¼ƒï¼Œæ•°æ®å·²è¿ç§»åˆ° `storage_attachment` è¡¨
- å‰ç«¯å¦‚果之前有对接 `POST /quality/qualityInspectFile/add` ç­‰æŽ¥å£ï¼Œéœ€ç§»é™¤ç›¸å…³ä»£ç 
## æ•°æ®åº“变更
执行文件:`doc/sql/20260613_quality_attachment_optimize.sql`
## æ³¨æ„äº‹é¡¹
- é™„件上传使用系统已有的文件上传接口(如 `/file/upload`),将返回的临时文件ID通过 `storageBlobDTOs` ä¼ å…¥
- å¦‚果不传 `storageBlobDTOs` æˆ–传空数组,不会删除已有附件;需要删除附件时传空数组即可
- å‰ç«¯é¡¹ç›®å¦‚已有通用的文件上传/附件展示组件,直接复用即可
docs/quality_unqualified_order.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,282 @@
# ä¸åˆæ ¼å“å¤„理单 â€” å‰ç«¯è”调文档
## æ¶‰åŠé¡µé¢
- **不合格品处理单列表/详情** â€” æ–°å¢žé¡µé¢
## å˜æ›´è¯´æ˜Ž
新增 `quality_unqualified_order` ä¸åˆæ ¼å“å¤„理单模块,支持对不合格品进行正式的处置记录,包含审批状态机(草稿→待审批→审批中→已完成/已驳回)。附件使用系统统一的 `storage_attachment` è¡¨ã€‚
## API
### ä¸åˆæ ¼å“å¤„理单
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/qualityUnqualifiedOrder/save` | æ–°å¢žå¤„理单(编号自动生成) |
| PUT | `/qualityUnqualifiedOrder/update` | ä¿®æ”¹å¤„理单 |
| DELETE | `/qualityUnqualifiedOrder/delete` | æ‰¹é‡åˆ é™¤ï¼ˆé€»è¾‘删除) |
| GET | `/qualityUnqualifiedOrder/listPage` | åˆ†é¡µæŸ¥è¯¢ |
| GET | `/qualityUnqualifiedOrder/{id}` | æŸ¥çœ‹è¯¦æƒ… |
### è¯·æ±‚/响应参数
**QualityUnqualifiedOrder å¯¹è±¡ï¼š**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | å¦ | ä¸»é”®ï¼ˆä¿®æ”¹æ—¶å¿…填) |
| orderNo | String | å¦ | å¤„理单编号,自动生成,前缀 BHG+日期+序号 |
| unqualifiedId | Long | å¦ | å…³è”不合格品ID |
| projectName | String | å¦ | é¡¹ç›®åç§° |
| projectNo | String | å¦ | é¡¹ç›®ç¼–号 |
| equipmentId | Long | å¦ | å…³è”设备ID |
| equipmentName | String | å¦ | è®¾å¤‡åç§° |
| equipmentDrawingNo | String | å¦ | è®¾å¤‡å›¾å· |
| materialName | String | å¦ | ç‰©æ–™/部件名称 |
| productModelId | Long | å¦ | å…³è”产品型号ID |
| materialDrawingNo | String | å¦ | ç‰©æ–™å›¾å· |
| specificationModel | String | å¦ | åž‹å·è§„æ ¼ |
| materialQuality | String | å¦ | æè´¨ |
| quantity | BigDecimal | å¦ | æ€»æ•°é‡ |
| unqualifiedQuantity | BigDecimal | å¦ | ä¸åˆæ ¼æ•°é‡ |
| unqualifiedProcess | Integer | å¦ | ä¸åˆæ ¼å·¥åºï¼š1=来料,2=制程,3=成品 |
| supplierName | String | å¦ | ä¾›åº”商名称 |
| inspectorName | String | å¦ | æ£€éªŒå‘˜ |
| inspectDate | Date | å¦ | æ£€éªŒæ—¥æœŸ (yyyy-MM-dd) |
| responsiblePerson | String | å¦ | è´£ä»»äºº |
| responsibleDept | String | å¦ | è´£ä»»éƒ¨é—¨ |
| problemDescription | String | å¦ | é—®é¢˜æè¿° |
| reasonAnalysis | String | å¦ | åŽŸå› åˆ†æžåŠå»ºè®® |
| correctionAction | String | å¦ | çº æ­£æŽªæ–½ |
| disposalMethod | Integer | å¦ | å¤„置方式:1=让步接收,2=厂内维修,3=返厂维修,4=换货,5=退货,6=报废 |
| repairEvaluation | String | å¦ | åނ内/返厂维修评估 |
| preventiveAction | String | å¦ | é¢„防措施 |
| status | Integer | å¦ | çŠ¶æ€ï¼š0=草稿,1=待审批,2=审批中,3=已完成,4=已驳回 |
| remark | String | å¦ | å¤‡æ³¨ |
| storageBlobDTOs | List\<StorageBlobDTO\> | å¦ | é™„件上传列表 |
| storageBlobVOs | List\<StorageBlobVO\> | å¦ | é™„件回显列表(查询时返回) |
### åˆ†é¡µæŸ¥è¯¢å‚æ•°
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNum | Integer | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Integer | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
| status | Integer | å¦ | çŠ¶æ€ç­›é€‰ |
| projectName | String | å¦ | é¡¹ç›®åç§°æ¨¡ç³Šæœç´¢ |
| orderNo | String | å¦ | ç¼–号模糊搜索 |
| entryDateStart | String | å¦ | åˆ›å»ºæ—¶é—´èµ·å§‹ |
| entryDateEnd | String | å¦ | åˆ›å»ºæ—¶é—´ç»“束 |
### åˆ é™¤è¯·æ±‚体
```json
[1, 2, 3]
```
## æ•°æ®ç»“æž„
### StorageBlobDTO(上传时传入)
```json
{
  "id": "临时文件ID(字符串)",
  "name": "文件名",
  "url": "文件路径",
  "fileSize": 1024
}
```
### StorageBlobVO(查询时返回)
```json
{
  "id": 1,
  "name": "检验报告.pdf",
  "url": "/upload/20260613/xxx.pdf",
  "fileSize": 102400,
  "application": "FILE"
}
```
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. ä¸åˆæ ¼å“å¤„理单 â€” æ–°å¢ž/编辑表单
```html
<el-form :model="form" :rules="rules" ref="formRef">
  <el-form-item label="项目名称" prop="projectName">
    <el-input v-model="form.projectName" />
  </el-form-item>
  <el-form-item label="项目编号" prop="projectNo">
    <el-input v-model="form.projectNo" />
  </el-form-item>
  <el-form-item label="设备名称" prop="equipmentName">
    <el-input v-model="form.equipmentName" />
  </el-form-item>
  <el-form-item label="物料/部件名称" prop="materialName">
    <el-input v-model="form.materialName" />
  </el-form-item>
  <el-form-item label="型号规格" prop="specificationModel">
    <el-input v-model="form.specificationModel" />
  </el-form-item>
  <el-form-item label="材质" prop="materialQuality">
    <el-input v-model="form.materialQuality" />
  </el-form-item>
  <el-form-item label="总数量" prop="quantity">
    <el-input-number v-model="form.quantity" />
  </el-form-item>
  <el-form-item label="不合格数量" prop="unqualifiedQuantity">
    <el-input-number v-model="form.unqualifiedQuantity" />
  </el-form-item>
  <el-form-item label="不合格工序" prop="unqualifiedProcess">
    <el-select v-model="form.unqualifiedProcess">
      <el-option label="来料" :value="1" />
      <el-option label="制程" :value="2" />
      <el-option label="成品" :value="3" />
    </el-select>
  </el-form-item>
  <el-form-item label="供应商名称" prop="supplierName">
    <el-input v-model="form.supplierName" />
  </el-form-item>
  <el-form-item label="检验员" prop="inspectorName">
    <el-input v-model="form.inspectorName" />
  </el-form-item>
  <el-form-item label="检验日期" prop="inspectDate">
    <el-date-picker v-model="form.inspectDate" type="date" value-format="yyyy-MM-dd" />
  </el-form-item>
  <el-form-item label="责任人" prop="responsiblePerson">
    <el-input v-model="form.responsiblePerson" />
  </el-form-item>
  <el-form-item label="责任部门" prop="responsibleDept">
    <el-input v-model="form.responsibleDept" />
  </el-form-item>
  <el-form-item label="问题描述" prop="problemDescription">
    <el-input type="textarea" v-model="form.problemDescription" />
  </el-form-item>
  <el-form-item label="原因分析" prop="reasonAnalysis">
    <el-input type="textarea" v-model="form.reasonAnalysis" />
  </el-form-item>
  <el-form-item label="纠正措施" prop="correctionAction">
    <el-input type="textarea" v-model="form.correctionAction" />
  </el-form-item>
  <el-form-item label="处置方式" prop="disposalMethod">
    <el-select v-model="form.disposalMethod">
      <el-option label="让步接收" :value="1" />
      <el-option label="厂内维修" :value="2" />
      <el-option label="返厂维修" :value="3" />
      <el-option label="换货" :value="4" />
      <el-option label="退货" :value="5" />
      <el-option label="报废" :value="6" />
    </el-select>
  </el-form-item>
  <el-form-item label="维修评估" prop="repairEvaluation">
    <el-input type="textarea" v-model="form.repairEvaluation" />
  </el-form-item>
  <el-form-item label="预防措施" prop="preventiveAction">
    <el-input type="textarea" v-model="form.preventiveAction" />
  </el-form-item>
  <el-form-item label="备注" prop="remark">
    <el-input type="textarea" v-model="form.remark" />
  </el-form-item>
  <el-form-item label="附件">
    <file-upload
      v-model="form.storageBlobDTOs"
      :file-list="form.storageBlobVOs"
    />
  </el-form-item>
</el-form>
```
### 2. ä¸åˆæ ¼å“å¤„理单 â€” åˆ—表页
```html
<el-table :data="tableData" border>
  <el-table-column prop="orderNo" label="处理单编号" />
  <el-table-column prop="projectName" label="项目名称" />
  <el-table-column prop="materialName" label="物料名称" />
  <el-table-column prop="specificationModel" label="型号规格" />
  <el-table-column prop="unqualifiedQuantity" label="不合格数量" />
  <el-table-column prop="disposalMethod" label="处置方式">
    <template #default="{ row }">
      {{ disposalMethodMap[row.disposalMethod] }}
    </template>
  </el-table-column>
  <el-table-column prop="status" label="状态">
    <template #default="{ row }">
      <el-tag :type="statusTagType(row.status)">{{ statusMap[row.status] }}</el-tag>
    </template>
  </el-table-column>
  <el-table-column label="操作">
    <template #default="{ row }">
      <el-button type="text" @click="handleDetail(row.id)">详情</el-button>
      <el-button type="text" @click="handleEdit(row)">编辑</el-button>
      <el-button type="text" @click="handleDelete(row.id)">删除</el-button>
    </template>
  </el-table-column>
</el-table>
```
### 3. data æ•°æ®
```js
data() {
  return {
    form: {
      storageBlobDTOs: [],
      storageBlobVOs: [],
    },
    query: {
      pageNum: 1,
      pageSize: 10,
      status: null,
      projectName: '',
      orderNo: '',
    },
    statusMap: { 0: '草稿', 1: '待审批', 2: '审批中', 3: '已完成', 4: '已驳回' },
    disposalMethodMap: { 1: '让步接收', 2: '厂内维修', 3: '返厂维修', 4: '换货', 5: '退货', 6: '报废' },
    unqualifiedProcessMap: { 1: '来料', 2: '制程', 3: '成品' },
  }
}
```
### 4. API è°ƒç”¨
```js
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({ url: '/qualityUnqualifiedOrder/listPage', method: 'get', params: query })
}
// è¯¦æƒ…
export function getDetail(id) {
  return request({ url: `/qualityUnqualifiedOrder/${id}`, method: 'get' })
}
// æ–°å¢ž
export function save(data) {
  return request({ url: '/qualityUnqualifiedOrder/save', method: 'post', data })
}
// ä¿®æ”¹
export function update(data) {
  return request({ url: '/qualityUnqualifiedOrder/update', method: 'put', data })
}
// åˆ é™¤
export function remove(ids) {
  return request({ url: '/qualityUnqualifiedOrder/delete', method: 'delete', data: ids })
}
```
## æ³¨æ„äº‹é¡¹
- å¤„理单编号 `orderNo` ç”±åŽç«¯è‡ªåŠ¨ç”Ÿæˆï¼ˆå‰ç¼€ "BHG" + æ—¥æœŸ + 3位自增序号),前端无需传入
- æ–°å¢žæ—¶ `status` é»˜è®¤ä¸º 0(草稿),无需前端设置
- åˆ é™¤ä¸ºé€»è¾‘删除,通过 `deleted` å­—段标记
- é™„件上传使用系统已有的文件上传组件,将返回的临时文件ID通过 `storageBlobDTOs` ä¼ å…¥
- ä¸åˆæ ¼å“å¤„理单可关联 `quality_unqualified` è¡¨çš„记录(通过 `unqualifiedId` å­—段)
docs/sales_ledger_approval.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
# é”€å”®å°è´¦ â€” ååŒå®¡æ‰¹
## æ¶‰åŠé¡µé¢
- **销售台账新增/编辑页面** â€” æ–°å¢žæ—¶è‡ªåŠ¨å‘èµ·å®¡æ‰¹ï¼Œåˆ—è¡¨å±•ç¤ºå®¡æ‰¹çŠ¶æ€
- **协同审批管理** â€” å®¡æ‰¹æ¨¡æ¿é…ç½®ã€å®¡æ‰¹æµç¨‹å¤„理
- **生产主计划列表** â€” å±•示审批状态列
## åŽç«¯å˜æ›´è¯´æ˜Ž
### æ–°å¢žæžšä¸¾
`TypeEnums` æ–°å¢ž `SALES_LEDGER_APPROVAL(19L, "销售审批")`
### å®¡æ‰¹çŠ¶æ€
`SalesLedger.approvalStatus` å’Œ `ProductionPlan.approvalStatus`:
| å€¼ | å«ä¹‰ |
|----|------|
| 0 | å¾…审批 |
| 1 | å®¡æ‰¹ä¸­ |
| 2 | å·²é€šè¿‡ |
| 3 | å·²é©³å›ž |
### å®¡æ‰¹æµç¨‹
1. é”€å”®å°è´¦æ–°å¢žæ—¶è‡ªåŠ¨å‘èµ·å®¡æ‰¹ï¼Œ`sales_ledger.approval_status = 0`
2. å®¡æ‰¹ä¸­çŠ¶æ€å˜ä¸º `1`,审批通过变为 `2`,审批驳回变为 `3`
3. å®¡æ‰¹é€šè¿‡åŽï¼Œè‡ªåŠ¨åˆ›å»ºç”Ÿäº§ä¸»è®¡åˆ’ï¼Œ`production_plan.approval_status = 2`
4. å®¡æ‰¹æ¨¡æ¿éœ€è¦åœ¨**协同审批管理**中单独配置(不在协同审批模板列表中展示)
## API
无新增前端调用接口,审批由后端自动发起。
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é”€å”®å°è´¦åˆ—表 â€” å¢žåŠ å®¡æ‰¹çŠ¶æ€åˆ—
```html
<el-table-column label="审批状态" prop="approvalStatus" width="100">
  <template slot-scope="scope">
    <el-tag v-if="scope.row.approvalStatus === 0" type="info">待审批</el-tag>
    <el-tag v-else-if="scope.row.approvalStatus === 1" type="warning">审批中</el-tag>
    <el-tag v-else-if="scope.row.approvalStatus === 2" type="success">已通过</el-tag>
    <el-tag v-else-if="scope.row.approvalStatus === 3" type="danger">已驳回</el-tag>
  </template>
</el-table-column>
```
### 2. é”€å”®å°è´¦è¯¦æƒ… â€” å±•示审批状态
```html
<el-form-item label="审批状态">
  <el-tag v-if="form.approvalStatus === 0" type="info">待审批</el-tag>
  <el-tag v-else-if="form.approvalStatus === 1" type="warning">审批中</el-tag>
  <el-tag v-else-if="form.approvalStatus === 2" type="success">已通过</el-tag>
  <el-tag v-else-if="form.approvalStatus === 3" type="danger">已驳回</el-tag>
</el-form-item>
```
### 3. ç”Ÿäº§ä¸»è®¡åˆ’列表 â€” å¢žåŠ å®¡æ‰¹çŠ¶æ€åˆ—
```html
<el-table-column label="审批状态" prop="approvalStatus" width="100">
  <template slot-scope="scope">
    <el-tag v-if="scope.row.approvalStatus === 2" type="success">已通过</el-tag>
    <el-tag v-else-if="scope.row.approvalStatus === 3" type="danger">已驳回</el-tag>
    <el-tag v-else type="info">-</el-tag>
  </template>
</el-table-column>
```
### 4. å®¡æ‰¹æ¨¡æ¿é…ç½®
在**协同审批管理 > å®¡æ‰¹æ¨¡æ¿**页面,新增模板时:
- **业务类型**选择"销售审批(19)"
- é…ç½®å®¡æ‰¹èŠ‚ç‚¹
- æ¨¡æ¿é…ç½®åŽï¼Œé”€å”®å°è´¦æ–°å¢žæ—¶è‡ªåŠ¨åŒ¹é…æœ€æ–°æ¨¡æ¿å‘èµ·å®¡æ‰¹
### 5. ååŒå®¡æ‰¹åˆ—表
审批列表已自动支持销售审批类型的展示:
- å®¡æ‰¹æ ‡é¢˜æ ¼å¼ï¼š`{销售合同号}销售审批`
- åˆ—表中的单号列展示销售合同号
## æ•°æ®åº“变更
执行文件:`doc/sql/20260613_sales_ledger_approval.sql`
```sql
ALTER TABLE sales_ledger
ADD COLUMN IF NOT EXISTS approval_status INT DEFAULT 0 COMMENT '审批状态:0-待审批,1-审批中,2-已通过,3-已驳回';
ALTER TABLE production_plan
ADD COLUMN IF NOT EXISTS approval_status INT DEFAULT NULL COMMENT '审批状态:0-待审批,1-审批中,2-已通过,3-已驳回';
```
## æ³¨æ„äº‹é¡¹
- é”€å”®å®¡æ‰¹æ¨¡æ¿ä¸åœ¨ååŒå®¡æ‰¹æ¨¡æ¿ä¸‹æ‹‰åˆ—表中展示(与采购审批、报价审批、发货审批一致),需要在审批模板管理页面单独配置
- å®¡æ‰¹é€šè¿‡åŽè‡ªåŠ¨åˆ›å»ºç”Ÿäº§ä¸»è®¡åˆ’ï¼Œæ¥æºæ ‡è®°ä¸º"销售",下发状态为"未下发"
- å®¡æ‰¹é©³å›žåŽï¼Œé”€å”®å°è´¦å¯ç¼–辑重新提交(当前版本仅支持新增时发起审批)
- å¯¼å…¥çš„销售台账不会自动发起审批,如需审批需单独处理
docs/²»ºÏ¸ñ¹ÜÀíÄ£¿éÂß¼­·ÖÎö.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,495 @@
# ä¸åˆæ ¼ç®¡ç†æ¨¡å—逻辑分析
## 1. æ¨¡å—定位
`quality` åŒ…下的"不合格管理"是一个独立的业务闭环:**检验(质检) â†’ ä¸åˆæ ¼å“ç™»è®° â†’ ä¸åˆæ ¼å“å¤„理 â†’ ä¸åˆæ ¼å“å¤„理单(审批流)**。此外还包含检测标准维护、检验报告生成、质量报表统计三个配套能力。
核心目标:
1. è¦†ç›–原材料检验、过程检验、出厂检验三种质检场景。
2. æ£€éªŒä¸åˆæ ¼æ—¶è‡ªåŠ¨ç™»è®°ä¸åˆæ ¼å“ï¼Œæ”¯æŒè¿”å·¥/返修/报废/让步放行四种处理方式。
3. ä¸åˆæ ¼å“å¤„理单走审批流程(草稿→待审批→审批中→已完成/已驳回)。
4. æä¾›åˆæ ¼çŽ‡ã€æœˆåº¦è¶‹åŠ¿ã€æ£€æµ‹æŒ‡æ ‡æŽ’åç­‰ç»Ÿè®¡æŠ¥è¡¨ã€‚
---
## 2. æ•°æ®åº“表结构
### 2.1 quality_inspect(检验记录主表)
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| inspect_type | INT | **必填**。0=原材料检验,1=过程检验,2=出厂检验 |
| inspect_state | INT | 0=未提交(草稿),1=已提交 |
| check_time | DATE | æ£€æµ‹æ—¥æœŸ |
| supplier | VARCHAR | ä¾›åº”商名称(原材料检验使用) |
| customer | VARCHAR | å®¢æˆ·åç§°ï¼ˆè¿‡ç¨‹/出厂检验使用) |
| process | VARCHAR | å·¥åº/部件类型(过程检验使用) |
| process_type | INT | å·¥åºç±»åž‹ï¼Œéœ€åŒ¹é…æ•°æ®å­—å…¸ `product_process_type` |
| check_name | VARCHAR | æ£€éªŒå‘˜ |
| product_id | BIGINT | **必填**。关联产品ID |
| product_name | VARCHAR | äº§å“åç§° |
| model | VARCHAR | è§„格型号 |
| unit | VARCHAR | å•位 |
| quantity | DECIMAL | æ€»æ•°é‡ |
| qualified_quantity | DECIMAL | åˆæ ¼æ•°é‡ |
| unqualified_quantity | DECIMAL | ä¸åˆæ ¼æ•°é‡ |
| check_company | VARCHAR | æ£€æµ‹å•位 |
| check_result | VARCHAR | æ£€æµ‹ç»“果("合格"/"不合格") |
| defective_phenomena | VARCHAR | ä¸åˆæ ¼çŽ°è±¡æè¿° |
| product_main_id | BIGINT | å…³è”报工ID(过程/出厂检验使用) |
| product_model_id | BIGINT | å…³è”产品型号ID |
| purchase_ledger_id | BIGINT | å…³è”采购台账ID(原材料检验使用) |
| test_standard_id | BIGINT | å…³è”检测标准主表ID |
| create_time | DATETIME | åˆ›å»ºæ—¶é—´ï¼ˆè‡ªåŠ¨å¡«å……ï¼‰ |
| create_user | INT | åˆ›å»ºç”¨æˆ·ï¼ˆè‡ªåŠ¨å¡«å……ï¼‰ |
| update_time | DATETIME | ä¿®æ”¹æ—¶é—´ï¼ˆè‡ªåŠ¨å¡«å……ï¼‰ |
| update_user | INT | ä¿®æ”¹ç”¨æˆ·ï¼ˆè‡ªåŠ¨å¡«å……ï¼‰ |
| tenant_id | BIGINT | ç§Ÿæˆ·ID(自动填充) |
| dept_id | BIGINT | éƒ¨é—¨ID(自动填充) |
### 2.2 quality_inspect_param(检验参数明细)
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| parameter_item | VARCHAR | æ£€æµ‹æŒ‡æ ‡åç§° |
| unit | VARCHAR | å•位 |
| standard_value | VARCHAR | æ ‡å‡†å€¼ |
| control_value | VARCHAR | å†…控值 |
| test_value | VARCHAR | æ£€éªŒå€¼ |
| inspect_id | BIGINT | **必填**。关联 quality_inspect.id |
| create_time / update_time / create_user / update_user / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
> ä¸€æ¡æ£€éªŒè®°å½• (quality_inspect) å¯¹å¤šæ¡æ£€éªŒå‚æ•° (quality_inspect_param)。
### 2.3 quality_inspect_file(检验附件)⚠️ **已废弃**
> **此表已废弃**,附件体系改为使用系统统一的 `storage_attachment` è¡¨ï¼Œé€šè¿‡ `record_type='quality_inspect'` + `record_id` å…³è”。
> æ•°æ®è¿ç§» SQL:`doc/sql/20260613_quality_attachment_optimize.sql`
> å‰ç«¯è”调文档:`docs/quality_attachment_optimize.md`
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| name | VARCHAR | æ–‡ä»¶åç§° |
| url | VARCHAR | æ–‡ä»¶è·¯å¾„ |
| file_size | INT | æ–‡ä»¶å¤§å° |
| inspect_id | BIGINT | **必填**。关联 quality_inspect.id |
| create_time / update_time / create_user / update_user / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
### 2.4 quality_unqualified(不合格品记录)
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| inspect_type | INT | **必填**。0=原材料检验,1=过程检验,2=出厂检验 |
| inspect_state | INT | **必填**。0=待处理,1=已处理 |
| check_time | DATE | æ£€æµ‹æ—¥æœŸ |
| check_name | VARCHAR | æ£€éªŒå‘˜ |
| product_id | BIGINT | å…³è”产品ID |
| product_name | VARCHAR | äº§å“åç§° |
| model | VARCHAR | è§„格型号(存储的可能是 product_model.id æˆ–直接文本) |
| unit | VARCHAR | å•位 |
| quantity | DECIMAL | æ•°é‡ |
| defective_phenomena | VARCHAR | ä¸åˆæ ¼çŽ°è±¡ |
| deal_result | VARCHAR | å¤„理结果:**"返修"/"返工"/"报废"/"让步放行"** |
| deal_name | VARCHAR | å¤„理人 |
| deal_time | DATE | å¤„理日期 |
| reason_analysis | VARCHAR | åŽŸå› åˆ†æž |
| preventive_corrective | VARCHAR | é¢„防与纠正措施 |
| loss_working | VARCHAR | å·¥æ—¶æŸå¤± |
| loss_material | VARCHAR | ææ–™è´¹æŸå¤± |
| inspect_id | BIGINT | å…³è” quality_inspect.id(如果是检验不合格自动生成) |
| product_model_id | BIGINT | å…³è”产品型号ID |
| create_time / update_time / create_user / update_user / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
| storage_blob_dtos (虚拟) | List\<StorageBlobDTO\> | é™„件上传参数(`@TableField(exist=false)`),替代旧的 temp_file_ids |
| storage_blob_vos (虚拟) | List\<StorageBlobVO\> | é™„件查询回显(`@TableField(exist=false)`),替代旧的 common_file_list |
| method (虚拟) | Boolean | æ ‡è¯†æ˜¯å¦ä¸åˆæ ¼å¤„理自己新增(`@TableField(exist=false)`) |
### 2.5 quality_unqualified_order(不合格品处理单)
> **已实现**。SQL:`doc/sql/20260613_quality_module_completion.sql`,前端文档:`docs/quality_unqualified_order.md`
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| order_no | VARCHAR | å¤„理单编号,前缀 "BHG"+ æ—¥æœŸ + åºå·ï¼Œè‡ªåŠ¨ç”Ÿæˆ |
| unqualified_id | BIGINT | å…³è” quality_unqualified.id |
| project_name | VARCHAR | é¡¹ç›®åç§° |
| project_no | VARCHAR | é¡¹ç›®ç¼–号 |
| equipment_id | BIGINT | å…³è”设备ID |
| equipment_name | VARCHAR | è®¾å¤‡åç§° |
| equipment_drawing_no | VARCHAR | è®¾å¤‡å›¾å· |
| material_name | VARCHAR | ç‰©æ–™/部件名称 |
| product_model_id | BIGINT | å…³è”产品型号ID |
| material_drawing_no | VARCHAR | ç‰©æ–™å›¾å· |
| specification_model | VARCHAR | åž‹å·è§„æ ¼ |
| material_quality | VARCHAR | æè´¨ |
| quantity | DECIMAL | æ€»æ•°é‡ |
| unqualified_quantity | DECIMAL | ä¸åˆæ ¼æ•°é‡ |
| unqualified_process | TINYINT | ä¸åˆæ ¼å·¥åºï¼š1=来料,2=制程,3=成品 |
| supplier_name | VARCHAR | ä¾›åº”商名称 |
| inspector_name | VARCHAR | æ£€éªŒå‘˜ |
| inspect_date | DATE | æ£€éªŒæ—¥æœŸ |
| responsible_person | VARCHAR | è´£ä»»äºº |
| responsible_dept | VARCHAR | è´£ä»»éƒ¨é—¨ |
| problem_description | VARCHAR | é—®é¢˜æè¿° |
| reason_analysis | VARCHAR | åŽŸå› åˆ†æžåŠå»ºè®® |
| correction_action | VARCHAR | çº æ­£æŽªæ–½ |
| disposal_method | TINYINT | å¤„置方式:1=让步接收,2=厂内维修,3=返厂维修,4=换货,5=退货,6=报废 |
| repair_evaluation | VARCHAR | åނ内/返厂维修评估 |
| preventive_action | VARCHAR | é¢„防措施 |
| status | TINYINT | çŠ¶æ€ï¼š0=草稿,1=待审批,2=审批中,3=已完成,4=已驳回 |
| remark | VARCHAR | å¤‡æ³¨ |
| create_by / update_by / create_time / update_time / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
| deleted | TINYINT | é€»è¾‘删除:0=否,1=是 |
### 2.6 quality_test_standard(检测标准主表)
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| standard_no | VARCHAR | æ ‡å‡†ç¼–号 |
| standard_name | VARCHAR | æ ‡å‡†åç§° |
| state | VARCHAR | çŠ¶æ€ |
| remark | VARCHAR | å¤‡æ³¨ |
| inspect_type | INT | ç±»åˆ«ï¼š0=原材料检验,1=过程检验,2=出厂检验 |
| process_id | INT | å·¥åºID |
| process_type | INT | å·¥åºç±»åž‹ |
| create_time / update_time / create_user / update_user / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
### 2.7 quality_test_standard_param(检测标准参数)
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | BIGINT | ä¸»é”®ï¼Œè‡ªå¢ž |
| parameter_item | VARCHAR | å‚数项(指标名称) |
| unit | VARCHAR | å•位 |
| standard_value | VARCHAR | æ ‡å‡†å€¼ |
| control_value | VARCHAR | å†…控值 |
| default_value | VARCHAR | é»˜è®¤å€¼ |
| test_standard_id | BIGINT | å…³è” quality_test_standard.id |
| create_time / update_time / create_user / update_user / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
### 2.8 quality_test_standard_binding(检测标准-产品关联)
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | INT | ä¸»é”®ï¼Œè‡ªå¢ž |
| product_id | BIGINT | äº§å“ID |
| test_standard_id | INT | å…³è” quality_test_standard.id |
| create_time / update_time / create_user / update_user / tenant_id / dept_id | â€” | é€šç”¨å®¡è®¡å­—段 |
> å¤šå¯¹å¤šå…³ç³»ï¼šä¸€ä¸ªäº§å“å¯ç»‘定多个检测标准,一个检测标准可用于多个产品。
---
## 3. æ ¸å¿ƒä¸šåŠ¡æµç¨‹
### 3.1 è´¨æ£€å®Œæ•´é“¾è·¯
```
检测标准维护 â†’ åˆ›å»ºæ£€éªŒå• â†’ å¡«å†™æ£€éªŒå‚æ•° â†’ æäº¤æ£€éªŒ
                                   â†“
                    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”´â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                    â†“                              â†“
              ç»“æžœ=不合格                        ç»“æžœ=合格
                    â†“                              â†“
         è‡ªåŠ¨ç”Ÿæˆä¸åˆæ ¼å“è®°å½•                  è‡ªåŠ¨åˆæ ¼å…¥åº“
         (quality_unqualified)              (调用 StockUtils)
                    â†“
              ä¸åˆæ ¼å“å¤„理(deal)
                    â†“
    â”Œâ”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”
    â†“       â†“        â†“        â†“
  è¿”ä¿®    è¿”å·¥      æŠ¥åºŸ    è®©æ­¥æ”¾è¡Œ
(过程/出厂)               (全部类型)
    â†“       â†“        â†“        â†“
 å…‹éš†ç”Ÿäº§  å…‹éš†ç”Ÿäº§  å…¥ä¸åˆæ ¼åº“  å…¥åˆæ ¼åº“
 è®¢å•+路线   åŒä¸Š   (unqualified (qualified
 +工单            stock)      stock)
    â†“
  ä¸åˆæ ¼å“å¤„理单(approval flow)
  è‰ç¨¿ â†’ å¾…审批 â†’ å®¡æ‰¹ä¸­ â†’ å·²å®Œæˆ/已驳回
```
### 3.2 æ£€éªŒæäº¤é€»è¾‘(QualityInspectServiceImpl.submit)
1. æ ¡éªŒ `checkResult` ä¸èƒ½ä¸ºç©ºã€‚
2. å¦‚æžœ `checkResult = "不合格"`:
   - è‡ªåŠ¨åˆ›å»ºä¸€æ¡ `quality_unqualified` è®°å½•,`inspect_state=0`(待处理)。
   - ä¸åˆæ ¼çŽ°è±¡è‡ªåŠ¨ç”Ÿæˆä¸ºï¼šæ‰€æœ‰æ£€éªŒå‚æ•°åç§°æ‹¼æŽ¥ + "这些指标中存在不合格"。
3. å¦‚æžœ `checkResult = "合格"`:
   - è°ƒç”¨ `StockUtils.addStock()` ç›´æŽ¥å…¥åº“(入库类型 `QUALITYINSPECT_STOCK_IN`)。
4. å°†æ£€éªŒå• `inspect_state` æ›´æ–°ä¸º 1(已提交)。
### 3.3 ä¸åˆæ ¼å“å¤„理逻辑(QualityUnqualifiedServiceImpl.deal)
核心参数:`dealResult` å†³å®šå¤„理路径,分为两大分支。
#### åˆ†æ”¯A:过程检验/出厂检验(inspectType != 0 ä¸”有 productMainId)
| dealResult | å¤„理逻辑 |
|------------|----------|
| **"返修"或"返工"** | 1. æ ¹æ® `productMainId` æŸ¥åˆ°åŽŸå§‹ç”Ÿäº§è®¢å•(ProductOrder)、工艺路线主表(ProductProcessRoute)、工艺路线子表(ProductProcessRouteItem) |
| | 2. å…‹éš†ä¸€ä»½æ–°çš„生产订单(quantity=不合格数量,completeQuantity=0,时间清空) |
| | 3. å…‹éš†ä¸€ä»½æ–°çš„工艺路线主表(关联新订单) |
| | 4. é€æ¡å…‹éš†å·¥è‰ºè·¯çº¿å­è¡¨ï¼Œæ¯æ¡å…‹éš†åŽè‡ªåŠ¨ç”Ÿæˆå¯¹åº”å·¥å•(ProductWorkOrder):工单号规则 `"FG" + yyyyMMdd + 3位自增序号` |
| **"报废"** | è°ƒç”¨ `StockUtils.addUnStock()` å…¥ä¸åˆæ ¼åº“(类型 `DEFECTIVE_SCRAP`) |
| **"让步放行"** | è°ƒç”¨ `StockUtils.addStock()` å…¥åˆæ ¼åº“(类型 `DEFECTIVE_PASS`) |
#### åˆ†æ”¯B:原材料检验(inspectType == 0)
| dealResult | å¤„理逻辑 |
|------------|----------|
| **"报废"** | å…ˆé€šè¿‡ productName + model æŸ¥ product_model.id,再调用 `StockUtils.addUnStock()` å…¥ä¸åˆæ ¼åº“ |
| **"让步放行"** | åŒä¸ŠæŸ¥æ‰¾ modelId,再调用 `StockUtils.addStock()` å…¥åˆæ ¼åº“ |
| **"返修"/"返工"** | åŽŸææ–™æ£€éªŒä¸æ”¯æŒè¿”ä¿®/返工(代码中无此分支,不执行任何操作) |
> å…³é”®åˆ¤æ–­é€»è¾‘位于 `QualityUnqualifiedServiceImpl.java:81`:
> ```java
> if (ObjectUtils.isNotNull(qualityInspect) && qualityInspect.getInspectType() != 0)
> ```
> åªæœ‰å…³è”了检验单且检验类型不是原材料(0)时,才走过程/出厂的处理分支(包含返修/返工逻辑)。
处理完成后将 `inspect_state` ç½®ä¸º 1(已处理)。
### 3.4 ä¸åˆæ ¼å“å¤„理单流程(QualityUnqualifiedOrder)
处理单是对不合格品的正式处置记录,包含更丰富的字段和审批状态机:
```
草稿(0) â†’ å¾…审批(1) â†’ å®¡æ‰¹ä¸­(2) â†’ å·²å®Œæˆ(3)
  â†“                      â†“
 å¯ç¼–辑/删除          å·²é©³å›ž(4)
```
- **编号自动生成**:前缀 `"BHG"` + å½“日自增序号(通过 `OrderUtils.countTodayByCreateTime` å®žçŽ°ï¼‰ã€‚
- **处置方式枚举**:1=让步接收,2=厂内维修,3=返厂维修,4=换货,5=退货,6=报废。
- **分页查询**:支持按 status、projectName、orderNo ç­›é€‰ã€‚
- **CRUD**:支持新增、修改、删除(逻辑删除通过 deleted å­—段)。
### 3.5 æ£€éªŒå•删除逻辑(QualityInspectController.del)
1. æ ¡éªŒæ˜¯å¦å·²æäº¤ï¼ˆ`inspectState==1` ä¸å…è®¸åˆ é™¤ï¼‰ã€‚
2. çº§è”删除关联的检验参数 (quality_inspect_param)。
3. çº§è”删除关联的检验附件 (quality_inspect_file)。
4. åˆ é™¤æ£€éªŒå•本身。
### 3.6 ä¸åˆæ ¼å“åˆ é™¤é€»è¾‘(QualityUnqualifiedController.del)
1. æ ¡éªŒæ˜¯å¦å·²å¤„理(`inspectState==1` ä¸å…è®¸åˆ é™¤ï¼‰ã€‚
2. çº§è”删除关联附件(通过 commonFileService æŒ‰ businessId + type åˆ é™¤ï¼‰ã€‚
3. æ‰¹é‡åˆ é™¤ä¸åˆæ ¼å“è®°å½•。
### 3.7 æ£€æµ‹æ ‡å‡†ç»´æŠ¤
- æ£€æµ‹æ ‡å‡†ä¸»è¡¨ (quality_test_standard) ç»´æŠ¤æ ‡å‡†ç¼–号、名称、适用检验类型、工序类型。
- æ£€æµ‹æ ‡å‡†å‚æ•° (quality_test_standard_param) ç»´æŠ¤æŒ‡æ ‡ã€å•位、标准值、内控值、默认值。
- æ£€æµ‹æ ‡å‡†-产品绑定 (quality_test_standard_binding) å®žçŽ°å¤šå¯¹å¤šå…³è”ã€‚
- **删除保护**:删除检测标准前会检查是否被检验单引用,引用则禁止删除。
- **参数复制**:支持 `copyParam` å°†æŸä¸ªæ ‡å‡†çš„参数复制到另一个标准。
- **审核**:提供 `qualityTestStandardAudit` æŽ¥å£ã€‚
---
## 4. API æŽ¥å£æ€»è§ˆ
### 4.1 æ£€éªŒè®°å½•
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/quality/qualityInspect/add` | æ–°å¢žæ£€éªŒå•(含检验参数) |
| DELETE | `/quality/qualityInspect/del` | æ‰¹é‡åˆ é™¤æ£€éªŒå•(级联删除参数和附件) |
| GET | `/quality/qualityInspect/{id}` | æŸ¥çœ‹è¯¦æƒ…(含检验参数) |
| POST | `/quality/qualityInspect/update` | ä¿®æ”¹æ£€éªŒå•(先删旧参数再插新参数) |
| GET | `/quality/qualityInspect/listPage` | åˆ†é¡µæŸ¥è¯¢ï¼ˆæŒ‰ç±»åž‹ JOIN ä¸åŒä¸šåŠ¡è¡¨ï¼‰ |
| POST | `/quality/qualityInspect/export` | å¯¼å‡º Excel(不同类型隐藏不同列) |
| POST | `/quality/qualityInspect/submit` | æäº¤æ£€éªŒï¼ˆåˆæ ¼å…¥åº“/不合格登记) |
| POST | `/quality/qualityInspect/down` | ä¸‹è½½ Word æ£€éªŒæŠ¥å‘Šï¼ˆpoi-tl æ¨¡æ¿æ¸²æŸ“) |
### 4.2 ä¸åˆæ ¼å“ç®¡ç†
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/quality/qualityUnqualified/add` | æ–°å¢žä¸åˆæ ¼å“ï¼ˆæ‰‹å·¥å½•入,状态=待处理) |
| DELETE | `/quality/qualityUnqualified/del` | æ‰¹é‡åˆ é™¤ï¼ˆå·²å¤„理不可删) |
| GET | `/quality/qualityUnqualified/{id}` | è¯¦æƒ…(含附件列表) |
| POST | `/quality/qualityUnqualified/update` | ä¿®æ”¹ä¸åˆæ ¼å“ |
| GET | `/quality/qualityUnqualified/listPage` | åˆ†é¡µæŸ¥è¯¢ï¼ˆæŒ‰ç±»åž‹ã€çŠ¶æ€ã€äº§å“åã€æ—¥æœŸç­›é€‰ï¼‰ |
| POST | `/quality/qualityUnqualified/export` | å¯¼å‡º Excel |
| POST | `/quality/qualityUnqualified/deal` | **处理不合格品**(返修/返工/报废/让步放行) |
### 4.3 ä¸åˆæ ¼å“å¤„理单
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/qualityUnqualifiedOrder/listPage` | åˆ†é¡µæŸ¥è¯¢ï¼ˆæ”¯æŒ status/projectName/orderNo ç­›é€‰ï¼‰ |
| POST | `/qualityUnqualifiedOrder/save` | æ–°å¢žï¼ˆè‡ªåŠ¨ç”Ÿæˆç¼–å·ï¼‰ |
| PUT | `/qualityUnqualifiedOrder/update` | ä¿®æ”¹ |
| DELETE | `/qualityUnqualifiedOrder/delete` | æ‰¹é‡åˆ é™¤ |
### 4.4 æ£€æµ‹æ ‡å‡†
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/qualityTestStandard/add` | æ–°å¢žæ£€æµ‹æ ‡å‡† |
| DELETE | `/qualityTestStandard/del` | åˆ é™¤ï¼ˆæœ‰å¼•用时禁止) |
| POST | `/qualityTestStandard/update` | ä¿®æ”¹ |
| GET | `/qualityTestStandard/listPage` | åˆ†é¡µæŸ¥è¯¢ |
| POST | `/qualityTestStandard/copyParam` | å¤åˆ¶å‚数到另一个标准 |
| POST | `/qualityTestStandard/qualityTestStandardAudit` | å®¡æ ¸æ£€æµ‹æ ‡å‡† |
| GET | `/qualityTestStandard/getQualityTestStandardByProductId` | æŒ‰äº§å“ID获取标准(通过绑定表 JOIN) |
| GET | `/qualityTestStandard/getQualityTestStandardParamByTestStandardId` | æŒ‰æ ‡å‡†ID获取参数列表 |
### 4.5 æ£€æµ‹æ ‡å‡†ç»‘定
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/qualityTestStandardBinding/add` | æ–°å¢žç»‘定 |
| DELETE | `/qualityTestStandardBinding/del` | åˆ é™¤ç»‘定 |
| GET | `/qualityTestStandardBinding/list` | æŸ¥è¯¢ç»‘定列表 |
### 4.6 æ£€æµ‹æ ‡å‡†å‚æ•°
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/qualityTestStandardParam/add` | æ–°å¢žå‚æ•° |
| DELETE | `/qualityTestStandardParam/del` | åˆ é™¤å‚æ•° |
| POST | `/qualityTestStandardParam/update` | ä¿®æ”¹å‚æ•° |
| GET | `/qualityTestStandardParam/list` | æŸ¥è¯¢å‚数列表 |
### 4.7 æ£€éªŒå‚æ•°
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/quality/qualityInspectParam/{inspectId}` | æŒ‰æ£€éªŒID查参数列表 |
| POST | `/quality/qualityInspectParam/update` | ä¿®æ”¹å‚æ•° |
| DELETE | `/quality/qualityInspectParam/del` | åˆ é™¤å‚æ•° |
### 4.8 æ£€éªŒé™„ä»¶
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/quality/qualityInspectFile/add` | æ–°å¢žé™„ä»¶ |
| DELETE | `/quality/qualityInspectFile/del` | åˆ é™¤é™„ä»¶ |
| GET | `/quality/qualityInspectFile/listPage` | åˆ†é¡µæŸ¥è¯¢ |
### 4.9 è´¨é‡æŠ¥è¡¨
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/qualityReport/getInspectStatistics` | æ£€éªŒç»Ÿè®¡ï¼ˆæŒ‰åŽŸææ–™/半成品/成品分组的总数和完成数) |
| GET | `/qualityReport/getPassRateStatistics` | åˆæ ¼çŽ‡ç»Ÿè®¡ï¼ˆæŒ‰æœŸæ±‡æ€»ï¼šæ€»æ•°ã€å®Œæˆæ•°ã€åˆæ ¼æ•°ã€ä¸åˆæ ¼æ•°ã€å®ŒæˆçŽ‡ã€åˆæ ¼çŽ‡ï¼‰ |
| GET | `/qualityReport/getMonthlyPassRateStatistics` | æœˆåº¦åˆæ ¼çŽ‡ï¼ˆ12个月 Ã— 3类,CTE递归) |
| GET | `/qualityReport/getYearlyPassRateStatistics` | å¹´åº¦åˆæ ¼çއ |
| GET | `/qualityReport/getMonthlyCompletionDetails` | æœˆåº¦å®Œæˆæ˜Žç»†ï¼ˆæ¯æœˆåŽŸææ–™/半成品/成品完成数) |
| GET | `/qualityReport/getTopParameters` | æ£€æµ‹æŒ‡æ ‡æŽ’名(Top 4 + å…¶ä»–汇总) |
---
## 5. å…³é”®æ•°æ®å…³è”
```
product (产品分类)
  â””── product (具体产品)
        â””── product_model (产品型号)
              â”œâ”€â”€ quality_inspect.product_model_id
              â”œâ”€â”€ quality_unqualified (通过 model å­—段关联)
              â””── quality_test_standard_binding.product_id
production_product_main (生产报工)
  â””── quality_inspect.product_main_id (过程/出厂检验)
        â””── production_product_main.work_order_id â†’ product_work_order
              â””── product_order â†’ sales_ledger (销售台账)
purchase_ledger (采购台账)
  â””── quality_inspect.purchase_ledger_id (原材料检验)
product_process_route (工艺路线主表)
  â””── product_process_route_item (工艺路线子表)
        â””── product_work_order (工单)
```
### åˆ†é¡µæŸ¥è¯¢æ—¶ JOIN ç­–ç•¥
- **原材料检验 (inspectType=0)**:LEFT JOIN `purchase_ledger` èŽ·å–é‡‡è´­åˆåŒå·ã€‚
- **过程/出厂检验 (inspectType != 0)**:LEFT JOIN `production_product_main` â†’ `product_work_order` â†’ `product_order` â†’ `sales_ledger` èŽ·å–å·¥å•å·å’Œé”€å”®åˆåŒå·ã€‚
- **不合格品列表**:LEFT JOIN `product_model` è§£æžåž‹å·ï¼ˆmodel å­—段可能是 ID æˆ–文本)。
---
## 6. ä¸Žå¤–部模块的交互
| äº¤äº’点 | è°ƒç”¨æ–¹å¼ | è¯´æ˜Ž |
|--------|----------|------|
| åº“存管理 (stock) | `StockUtils.addStock()` / `StockUtils.addUnStock()` | åˆæ ¼å…¥åº“ / ä¸åˆæ ¼å…¥åº“ |
| ç”Ÿäº§ç®¡ç† (production) | ç›´æŽ¥æ“ä½œ Mapper | è¿”å·¥/返修时克隆生产订单+工艺路线+工单 |
| é™„件管理 (commonFile) | `CommonFileService` / `TempFileService` | ä¸´æ—¶æ–‡ä»¶è½¬æ­£å¼æ–‡ä»¶ã€æŒ‰ä¸šåŠ¡ID+类型查询删除 |
| æ•°æ®å­—å…¸ (dict) | `DictUtils` | processType æ ¡éªŒï¼Œå¯¼å‡ºæ—¶ translate |
| é‡‡è´­å°è´¦ (procurement) | `ProcurementRecordService` | åŽŸææ–™æ£€éªŒå…³è”é‡‡è´­è®°å½• |
---
## 7. æžšä¸¾å€¼æ±‡æ€»
### inspectType(检验类别)
| å€¼ | å«ä¹‰ |
|----|------|
| 0 | åŽŸææ–™æ£€éªŒ |
| 1 | è¿‡ç¨‹æ£€éªŒ |
| 2 | å‡ºåŽ‚æ£€éªŒ |
### inspectState(检验单状态 / ä¸åˆæ ¼å“çŠ¶æ€ï¼‰
| å€¼ | å«ä¹‰ï¼ˆæ£€éªŒå•) | å«ä¹‰ï¼ˆä¸åˆæ ¼å“ï¼‰ |
|----|---------------|------------------|
| 0 | æœªæäº¤(草稿) | å¾…处理 |
| 1 | å·²æäº¤ | å·²å¤„理 |
### dealResult(不合格品处理结果)
| å€¼ | å«ä¹‰ | é€‚用场景 |
|----|------|----------|
| è¿”ä¿® | è¿”修处理 | ä»…过程/出厂检验 |
| è¿”å·¥ | è¿”工处理(重建生产订单) | ä»…过程/出厂检验 |
| æŠ¥åºŸ | å…¥ä¸åˆæ ¼åº“ | å…¨éƒ¨ç±»åž‹ |
| è®©æ­¥æ”¾è¡Œ | å…¥åˆæ ¼åº“ | å…¨éƒ¨ç±»åž‹ |
### disposalMethod(处理单处置方式)
| å€¼ | å«ä¹‰ |
|----|------|
| 1 | è®©æ­¥æŽ¥æ”¶ |
| 2 | åŽ‚å†…ç»´ä¿® |
| 3 | è¿”厂维修 |
| 4 | æ¢è´§ |
| 5 | é€€è´§ |
| 6 | æŠ¥åºŸ |
### unqualifiedProcess(不合格工序)
| å€¼ | å«ä¹‰ |
|----|------|
| 1 | æ¥æ–™ |
| 2 | åˆ¶ç¨‹ |
| 3 | æˆå“ |
### status(处理单状态)
| å€¼ | å«ä¹‰ |
|----|------|
| 0 | è‰ç¨¿ |
| 1 | å¾…审批 |
| 2 | å®¡æ‰¹ä¸­ |
| 3 | å·²å®Œæˆ |
| 4 | å·²é©³å›ž |
---
## 8. æŠ€æœ¯å®žçŽ°è¦ç‚¹
1. **MyBatis-Plus è‡ªåЍ填充**:create_time、create_user、update_time、update_user、tenant_id、dept_id å‡é€šè¿‡ `@TableField(fill = ...)` è‡ªåŠ¨å¡«å……ã€‚
2. **事务管理**:`QualityInspectServiceImpl` æ ‡æ³¨ `@Transactional(rollbackFor = Exception.class)`,确保新增/修改检验单与参数操作的原子性。
3. **Word æŠ¥å‘Šç”Ÿæˆ**:使用 poi-tl æ¨¡æ¿å¼•擎,模板文件 `/static/report-template.docx`,通过 `HackLoopTableRenderPolicy` æ¸²æŸ“参数列表表格。
4. **Excel å¯¼å‡ºåŠ¨æ€åˆ—**:根据 inspectType åŠ¨æ€éšè—ä¸ç›¸å…³åˆ—ï¼ˆåŽŸææ–™æ£€éªŒéšè— customer/process,出厂检验隐藏 supplier/customer/process)。
5. **月度统计 CTE**:`getMonthlyPassRateStatistics` ä½¿ç”¨ MySQL WITH RECURSIVE ç”Ÿæˆ 1-12 æœˆåºåˆ—,CROSS JOIN 3 ç§äº§å“ç±»åž‹ï¼ŒLEFT JOIN å®žé™…数据,确保每月每类型都有数据行。
6. **不合格品 model å­—段兼容**:model å­—段可能存储 product_model.id(数字)或直接文本,查询时 LEFT JOIN product_model å°è¯•解析,并用 method å­—段标记。
7. **返工工单号生成**:日期前缀 `yyyyMMdd` + 3 ä½è‡ªå¢žåºå·ï¼Œå‰ç¼€ `"FG"`,通过查询当日最大工单号实现自增。
8. **文件管理**:前端传 tempFileIds,后端通过 `TempFileService.migrateTempFilesToFormal()` å°†ä¸´æ—¶æ–‡ä»¶è½¬ä¸ºæ­£å¼æ–‡ä»¶å¹¶ç»‘定到业务ID。
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
@@ -126,7 +126,7 @@
    private Long tenantId;
    /**
     * å®¡æ‰¹ç±»åž‹ 1-公出管理 2-请假管理 3-出差管理 4-报销管理 5-采购审批 6-报价审批 7-发货审批 8-危险作业审批
     * å®¡æ‰¹ç±»åž‹ 1-公出管理 2-请假管理 3-出差管理 4-报销管理 5-采购审批 6-报价审批 7-发货审批 8-危险作业审批 9-办公用品审批 19-销售审批
     */
    private Integer approveType;
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
@@ -39,13 +39,18 @@
import com.ruoyi.project.system.service.ISysNoticeService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.production.mapper.ProductionPlanMapper;
import com.ruoyi.production.pojo.ProductionPlan;
import com.ruoyi.quality.utils.QualityInspectHelper;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.SalesLedgerProductServiceImpl;
import com.ruoyi.staff.mapper.HolidayApplicationMapper;
import com.ruoyi.staff.pojo.HolidayApplication;
import lombok.RequiredArgsConstructor;
@@ -84,6 +89,9 @@
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final StockUtils stockUtils;
    private final SalesQuotationMapper salesQuotationMapper;
    private final SalesLedgerMapper salesLedgerMapper;
    private final SalesLedgerProductServiceImpl salesLedgerProductService;
    private final ProductionPlanMapper productionPlanMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final QualityInspectHelper qualityInspectHelper;
    private final EnterpriseNewsScopeUserMapper enterpriseNewsScopeUserMapper;
@@ -126,6 +134,12 @@
                    ShippingInfo shippingInfo = shippingInfoMapper.selectById(vo.getBusinessId());
                    if (shippingInfo != null) {
                        vo.setShippingNo(shippingInfo.getShippingNo());
                    }
                } else if (TypeEnums.SALES_LEDGER_APPROVAL.getCode().equals(vo.getBusinessType())) {
                    // é”€å”®å®¡æ‰¹ - æŸ¥è¯¢é”€å”®åˆåŒå·
                    SalesLedger salesLedger = salesLedgerMapper.selectById(vo.getBusinessId());
                    if (salesLedger != null) {
                        vo.setQuotationNo(salesLedger.getSalesContractNo());
                    }
                }
            }
@@ -550,6 +564,10 @@
        }
        if (TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode().equals(businessType)) {
            handleNewsApprovalFinished(instance, status);
            return;
        }
        if (TypeEnums.SALES_LEDGER_APPROVAL.getCode().equals(businessType)) {
            handleSalesLedgerApprovalFinished(instance, status);
        }
    }
@@ -667,6 +685,36 @@
        salesQuotationMapper.updateById(salesQuote);
    }
    private void handleSalesLedgerApprovalFinished(ApprovalInstance instance, String status) {
        SalesLedger salesLedger = salesLedgerMapper.selectById(instance.getBusinessId());
        if (salesLedger == null) {
            return;
        }
        if ("APPROVED".equals(status)) {
            salesLedger.setApprovalStatus(2);
            salesLedgerMapper.updateById(salesLedger);
            // å®¡æ‰¹é€šè¿‡åŽåˆ›å»ºç”Ÿäº§ä¸»è®¡åˆ’
            createProductionPlansForApproved(salesLedger);
        } else if ("REJECTED".equals(status)) {
            salesLedger.setApprovalStatus(3);
            salesLedgerMapper.updateById(salesLedger);
        } else if ("PENDING".equals(status)) {
            salesLedger.setApprovalStatus(1);
            salesLedgerMapper.updateById(salesLedger);
        }
    }
    private void createProductionPlansForApproved(SalesLedger salesLedger) {
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(
                new LambdaQueryWrapper<SalesLedgerProduct>()
                        .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId())
                        .eq(SalesLedgerProduct::getIsProduction, true)
        );
        for (SalesLedgerProduct product : products) {
            salesLedgerProductService.addProductionDataForApproved(product, 2);
        }
    }
    private void handleShippingApprovalFinished(ApprovalInstance instance, String status) {
        ShippingInfo shippingInfo = shippingInfoMapper.selectOne(
                new LambdaQueryWrapper<ShippingInfo>()
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
@@ -128,7 +128,7 @@
                new LambdaQueryWrapper<ApprovalTemplate>()
                        .eq(ApprovalTemplate::getDeleted, 0)
                        .eq(ApprovalTemplate::getEnabled, 1)
                        .notIn(ApprovalTemplate::getBusinessType, List.of(5L, 6L, 7L))
                        .notIn(ApprovalTemplate::getBusinessType, List.of(5L, 6L, 7L, 19L))
                        .orderByDesc(ApprovalTemplate::getTemplateType)
                        .orderByDesc(ApprovalTemplate::getId)
        );
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -458,6 +458,8 @@
                return "危险作业审批";
            case 9:
                return "办公用品审批";
            case 19:
                return "销售审批";
        }
        return null;
    }
src/main/java/com/ruoyi/basic/controller/CustomerController.java
@@ -152,4 +152,14 @@
    public R back(@PathVariable("id") Long id) {
        return R.ok(customerService.back(id));
    }
    /**
     * å®¢æˆ·äº¤æŽ¥ï¼šè½¬ç§»ç§æµ·å®¢æˆ·ç»´æŠ¤äºº
     */
    @Log(title = "客户交接", businessType = BusinessType.UPDATE)
    @PostMapping("/handover")
    public R handover(@RequestBody CustomerDto customer) {
        customerService.handoverCustomer(customer);
        return R.ok();
    }
}
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java
@@ -8,11 +8,14 @@
import com.ruoyi.basic.pojo.CustomerReturnVisit;
import com.ruoyi.basic.service.CustomerFollowUpService;
import com.ruoyi.basic.service.CustomerReturnVisitService;
import com.ruoyi.basic.vo.CustomerFollowUpExportVo;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -160,4 +163,15 @@
        return AjaxResult.success();
    }
    /**
     * å¯¼å‡ºæ´½è°ˆè¿›åº¦ï¼ˆæŒ‰è·Ÿè¿›è®°å½•展开)
     */
    @Operation(summary = "导出洽谈进度")
    @PostMapping("/export")
    @Log(title = "洽谈进度-导出", businessType = BusinessType.EXPORT)
    public void export(HttpServletResponse response, String customerName, String customerType) {
        ExcelUtil<CustomerFollowUpExportVo> util = new ExcelUtil<CustomerFollowUpExportVo>(CustomerFollowUpExportVo.class);
        util.exportExcel(response, customerFollowUpService.selectFollowUpExportList(customerName, customerType), "洽谈进度");
    }
}
src/main/java/com/ruoyi/basic/dto/CustomerDto.java
@@ -1,6 +1,7 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -29,4 +30,9 @@
     * å…±äº«ç”¨æˆ·ID列表
     */
    private List<Long> userIds;
    /**
     * ç»´æŠ¤äººId
     */
    private Integer maintainerId;
}
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -76,6 +76,7 @@
    SAFE_ACCIDENT("safe_accident"),
    // Quality
    QUALITY_UNQUALIFIED("quality_unqualified"),
    QUALITY_UNQUALIFIED_ORDER("quality_unqualified_order"),
    QUALITY_TEST_STANDARD_PARAM("quality_test_standard_param"),
    QUALITY_TEST_STANDARD_BINDING("quality_test_standard_binding"),
    QUALITY_TEST_STANDARD("quality_test_standard"),
src/main/java/com/ruoyi/basic/mapper/CustomerFollowUpMapper.java
@@ -2,6 +2,10 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.vo.CustomerFollowUpExportVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <br>
@@ -13,4 +17,10 @@
 * @since 2026/03/04 14:46
 */
public interface CustomerFollowUpMapper extends BaseMapper<CustomerFollowUp> {
    /**
     * æŸ¥è¯¢æ´½è°ˆè¿›åº¦å¯¼å‡ºæ•°æ®ï¼ˆå®¢æˆ·å…³è”跟进记录)
     */
    List<CustomerFollowUpExportVo> selectFollowUpExportList(@Param("customerName") String customerName,
                                                             @Param("customerType") String customerType);
}
src/main/java/com/ruoyi/basic/service/CustomerFollowUpService.java
@@ -5,6 +5,7 @@
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.dto.CustomerFollowUpFileDto;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
import com.ruoyi.basic.vo.CustomerFollowUpExportVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
@@ -59,4 +60,9 @@
     * èŽ·å–è·Ÿè¿›è¯¦æƒ…
     */
    CustomerFollowUpDto getFollowUpWithFiles(Integer id);
    /**
     * æŸ¥è¯¢æ´½è°ˆè¿›åº¦å¯¼å‡ºæ•°æ®
     */
    List<CustomerFollowUpExportVo> selectFollowUpExportList(String customerName, String customerType);
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -97,6 +97,13 @@
    Boolean back(Long id);
    /**
     * å®¢æˆ·äº¤æŽ¥ï¼šå°†ç§æµ·å®¢æˆ·çš„维护人转移给新负责人
     *
     * @param customerDto å®¢æˆ·DTO(包含客户ID和新维护人)
     */
    void handoverCustomer(CustomerDto customerDto);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥åˆ—表
     * @param page
     * @param customerName
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.CustomerFollowUpDto;
import com.ruoyi.basic.dto.CustomerFollowUpFileDto;
import com.ruoyi.basic.vo.CustomerFollowUpExportVo;
import com.ruoyi.basic.mapper.CustomerFollowUpMapper;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
@@ -269,4 +270,9 @@
        return dto;
    }
    @Override
    public List<CustomerFollowUpExportVo> selectFollowUpExportList(String customerName, String customerType) {
        return baseMapper.selectFollowUpExportList(customerName, customerType);
    }
}
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -431,6 +431,24 @@
    }
    @Override
    public void handoverCustomer(CustomerDto customerDto) {
        Customer customer = customerMapper.selectById(customerDto.getId());
        if (customer == null) {
            throw new ServiceException("客户不存在");
        }
        if (customer.getType() != 0) {
            throw new ServiceException("仅私海客户支持交接");
        }
        if (customerDto.getMaintainer() == null || customerDto.getMaintainer().trim().isEmpty()) {
            throw new ServiceException("新维护人不能为空");
        }
        customer.setMaintainer(customerDto.getMaintainer());
        customer.setMaintenanceTime(new Date());
        customer.setCreateUser(customerDto.getMaintainerId());
        customerMapper.updateById(customer);
    }
    @Override
    public IPage<CustomerTransactionsVo> customewTransactions(Page page, String customerName) {
        return customerMapper.customewTransactions(page, customerName);
    }
src/main/java/com/ruoyi/basic/vo/CustomerFollowUpExportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.basic.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * å®¢æˆ·æ´½è°ˆè¿›åº¦å¯¼å‡ºVO
 * æ¯è¡Œä¸€æ¡è·Ÿè¿›è®°å½•,同时包含客户基本信息
 *
 * @author ruoyi
 * @date 2026-06-13
 */
@Data
public class CustomerFollowUpExportVo {
    @Excel(name = "客户名称")
    private String customerName;
    @Excel(name = "客户分类")
    private String customerType;
    @Excel(name = "联系人")
    private String contactPerson;
    @Excel(name = "联系电话")
    private String contactPhone;
    @Excel(name = "维护人")
    private String maintainer;
    @Excel(name = "跟进方式")
    private String followUpMethod;
    @Excel(name = "跟进程度")
    private String followUpLevel;
    @Excel(name = "跟进时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime followUpTime;
    @Excel(name = "跟进人")
    private String followerUserName;
    @Excel(name = "跟进内容")
    private String content;
}
src/main/java/com/ruoyi/common/enums/TypeEnums.java
@@ -24,7 +24,8 @@
    OVERTIME_APPROVAL(15L, "加班审批"),
    TRAVEL_REIMBURSEMENT_APPROVAL(16L, "出差报销审批"),
    EXPENSE_APPROVAL(17L, "费用审批"),
    ENTERPRISE_NEWS_APPROVAL(18L, "企业新闻审批");
    ENTERPRISE_NEWS_APPROVAL(18L, "企业新闻审批"),
    SALES_LEDGER_APPROVAL(19L, "销售审批");
src/main/java/com/ruoyi/production/pojo/ProductionPlan.java
@@ -82,4 +82,7 @@
    @Schema(description = "状态 0未下发 1部分下发 2已下发")
    private Integer status;
    @Schema(description = "审批状态:0-待审批,1-审批中,2-已通过,3-已驳回")
    private Integer approvalStatus;
}
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java
@@ -1,6 +1,9 @@
package com.ruoyi.quality.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
@@ -25,6 +28,8 @@
    @Resource
    private IQualityUnqualifiedService qualityUnqualifiedService;
    @Resource
    private FileUtil fileUtil;
    /**
@@ -37,7 +42,9 @@
    @Log(title = "新增不合格管理", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody QualityUnqualified qualityUnqualified) {
        qualityUnqualified.setInspectState(0);
        return R.ok(qualityUnqualifiedService.save(qualityUnqualified));
        qualityUnqualifiedService.save(qualityUnqualified);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QUALITY_UNQUALIFIED, qualityUnqualified.getId(), qualityUnqualified.getStorageBlobDTOs());
        return R.ok(true);
    }
    /**
@@ -78,7 +85,9 @@
    @Operation(summary = "不合格管理修改")
    @Log(title = "不合格管理修改", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody QualityUnqualified qualityUnqualified) {
        return R.ok(qualityUnqualifiedService.updateById(qualityUnqualified));
        qualityUnqualifiedService.updateById(qualityUnqualified);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QUALITY_UNQUALIFIED, qualityUnqualified.getId(), qualityUnqualified.getStorageBlobDTOs());
        return R.ok(true);
    }
    /**
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedOrderController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.ruoyi.quality.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.quality.pojo.QualityUnqualifiedOrder;
import com.ruoyi.quality.service.IQualityUnqualifiedOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/qualityUnqualifiedOrder")
@Tag(name = "不合格品处理单")
public class QualityUnqualifiedOrderController {
    @Resource
    private IQualityUnqualifiedOrderService orderService;
    @Resource
    private FileUtil fileUtil;
    @PostMapping("/save")
    @Operation(summary = "新增不合格品处理单")
    @Log(title = "新增不合格品处理单", businessType = BusinessType.INSERT)
    public R<?> save(@RequestBody QualityUnqualifiedOrder order) {
        String orderNo = OrderUtils.countTodayByCreateTime(
                orderService.getBaseMapper(), "BHG", "order_no",
                order.getCreateTime() != null ? order.getCreateTime() : LocalDateTime.now());
        order.setOrderNo(orderNo);
        order.setStatus(0);
        orderService.save(order);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QUALITY_UNQUALIFIED_ORDER, order.getId(), order.getStorageBlobDTOs());
        return R.ok(true);
    }
    @PutMapping("/update")
    @Operation(summary = "修改不合格品处理单")
    @Log(title = "修改不合格品处理单", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody QualityUnqualifiedOrder order) {
        orderService.updateById(order);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QUALITY_UNQUALIFIED_ORDER, order.getId(), order.getStorageBlobDTOs());
        return R.ok(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除不合格品处理单")
    @Log(title = "删除不合格品处理单", businessType = BusinessType.DELETE)
    public R<?> delete(@RequestBody List<Long> ids) {
        return R.ok(orderService.removeBatchByIds(ids));
    }
    @GetMapping("/listPage")
    @Operation(summary = "不合格品处理单分页查询")
    @Log(title = "不合格品处理单分页查询", businessType = BusinessType.OTHER)
    public R<?> listPage(Page page, QualityUnqualifiedOrder query) {
        return R.ok(orderService.listPage(page, query));
    }
    @GetMapping("/{id}")
    @Operation(summary = "不合格品处理单详情")
    @Log(title = "不合格品处理单详情", businessType = BusinessType.OTHER)
    public R<?> detail(@PathVariable Long id) {
        return R.ok(orderService.getDetail(id));
    }
}
src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java
@@ -2,6 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.quality.pojo.QualityInspect;
@@ -29,4 +31,9 @@
    private String workOrderNo;
    private String purchaseContractNo;
    /** é™„件列表(新增/编辑时传入) */
    private List<StorageBlobDTO> storageBlobDTOs;
    /** é™„件列表(查询时返回) */
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedOrderMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.quality.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.quality.pojo.QualityUnqualifiedOrder;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface QualityUnqualifiedOrderMapper extends BaseMapper<QualityUnqualifiedOrder> {
    IPage<QualityUnqualifiedOrder> listPage(Page page, @Param("query") QualityUnqualifiedOrder query);
}
src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
@@ -2,6 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -12,6 +14,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
 * è´¨é‡ç®¡ç†--原材料/过程/出厂检验
@@ -62,6 +65,12 @@
     */
    @Excel(name = "工序")
    private String process;
    /**
     * å·¥åºç±»åž‹
     */
    @Excel(name = "工序类型")
    private Integer processType;
    /**
     * æ£€éªŒå‘˜
@@ -162,4 +171,12 @@
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "附件列表(新增/编辑时传入)")
    @TableField(exist = false)
    private List<StorageBlobDTO> storageBlobDTOs;
    @Schema(description = "附件列表(查询时返回)")
    @TableField(exist = false)
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/quality/pojo/QualityTestStandard.java
@@ -69,6 +69,9 @@
    @Schema(description = "工序id")
    private Integer processId;
    @Schema(description = "工序类型")
    private Integer processType;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
@@ -2,6 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -12,6 +14,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
 * è´¨é‡ç®¡ç†--不合格品管理
@@ -111,6 +114,29 @@
    @Excel(name = "处理日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date dealTime;
    /**
     * åŽŸå› åˆ†æž
     */
    @Excel(name = "原因分析")
    private String reasonAnalysis;
    /**
     * é¢„防与纠正措施
     */
    @Excel(name = "预防与纠正措施")
    private String preventiveCorrective;
    /**
     * å·¥æ—¶æŸå¤±
     */
    @Excel(name = "工时损失")
    private String lossWorking;
    /**
     * ææ–™è´¹æŸå¤±
     */
    @Excel(name = "材料费损失")
    private String lossMaterial;
    @Schema(description = "创建时间")
@@ -146,4 +172,12 @@
    @Schema(description = "关联产品型号id")
    private Long productModelId;
    @Schema(description = "附件列表(新增/编辑时传入)")
    @TableField(exist = false)
    private List<StorageBlobDTO> storageBlobDTOs;
    @Schema(description = "附件列表(查询时返回)")
    @TableField(exist = false)
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/quality/pojo/QualityUnqualifiedOrder.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,150 @@
package com.ruoyi.quality.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
 * è´¨é‡ç®¡ç†--不合格品处理单
 * quality_unqualified_order
 */
@TableName(value = "quality_unqualified_order")
@Data
public class QualityUnqualifiedOrder extends DateQueryDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.AUTO)
    private Long id;
    @Schema(description = "处理单编号")
    private String orderNo;
    @Schema(description = "关联不合格品ID")
    private Long unqualifiedId;
    @Excel(name = "项目名称")
    private String projectName;
    @Excel(name = "项目编号")
    private String projectNo;
    @Schema(description = "关联设备ID")
    private Long equipmentId;
    @Excel(name = "设备名称")
    private String equipmentName;
    @Schema(description = "设备图号")
    private String equipmentDrawingNo;
    @Excel(name = "物料/部件名称")
    private String materialName;
    @Schema(description = "关联产品型号ID")
    private Long productModelId;
    @Schema(description = "物料图号")
    private String materialDrawingNo;
    @Excel(name = "型号规格")
    private String specificationModel;
    @Schema(description = "材质")
    private String materialQuality;
    @Excel(name = "总数量")
    private BigDecimal quantity;
    @Excel(name = "不合格数量")
    private BigDecimal unqualifiedQuantity;
    @Excel(name = "不合格工序", readConverterExp = "1=来料,2=制程,3=成品")
    private Integer unqualifiedProcess;
    @Excel(name = "供应商名称")
    private String supplierName;
    @Excel(name = "检验员")
    private String inspectorName;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "检验日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date inspectDate;
    @Schema(description = "责任人")
    private String responsiblePerson;
    @Schema(description = "责任部门")
    private String responsibleDept;
    @Schema(description = "问题描述")
    private String problemDescription;
    @Schema(description = "原因分析及建议")
    private String reasonAnalysis;
    @Schema(description = "纠正措施")
    private String correctionAction;
    @Excel(name = "处置方式", readConverterExp = "1=让步接收,2=厂内维修,3=返厂维修,4=换货,5=退货,6=报废")
    private Integer disposalMethod;
    @Schema(description = "厂内/返厂维修评估")
    private String repairEvaluation;
    @Schema(description = "预防措施")
    private String preventiveAction;
    @Excel(name = "状态", readConverterExp = "0=草稿,1=待审批,2=审批中,3=已完成,4=已驳回")
    private Integer status;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "创建用户")
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    private Integer createBy;
    @Schema(description = "修改用户")
    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    private Integer updateBy;
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @TableLogic
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;
    @Schema(description = "附件列表(新增/编辑时传入)")
    @TableField(exist = false)
    private List<StorageBlobDTO> storageBlobDTOs;
    @Schema(description = "附件列表(查询时返回)")
    @TableField(exist = false)
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedOrderService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.quality.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.quality.pojo.QualityUnqualifiedOrder;
public interface IQualityUnqualifiedOrderService extends IService<QualityUnqualifiedOrder> {
    IPage<QualityUnqualifiedOrder> listPage(Page page, QualityUnqualifiedOrder query);
    QualityUnqualifiedOrder getDetail(Long id);
}
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -9,6 +9,9 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
@@ -56,6 +59,7 @@
    private final StockUtils stockUtils;
    private final StockInventoryService stockInventoryService;
    private final StockInRecordService stockInRecordService;
    private final FileUtil fileUtil;
    private QualityInspectMapper qualityInspectMapper;
    private IQualityInspectParamService qualityInspectParamService;
@@ -78,6 +82,8 @@
            qualityInspectParam.setInspectId(qualityInspect.getId());
        }
        qualityInspectParamService.saveBatch(qualityInspectDto.getQualityInspectParams());
        // ä¿å­˜é™„ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QUALITY_INSPECT, qualityInspect.getId(), qualityInspectDto.getStorageBlobDTOs());
        return 0;
    }
@@ -88,6 +94,8 @@
        QualityInspectDto qualityInspectDto = new QualityInspectDto();
        BeanUtils.copyProperties(qualityInspect, qualityInspectDto);
        qualityInspectDto.setQualityInspectParams(qualityInspectParams);
        // æŸ¥è¯¢é™„ä»¶
        qualityInspectDto.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.QUALITY_INSPECT, qualityInspect.getId()));
        return qualityInspectDto;
    }
@@ -286,7 +294,10 @@
        }
        QualityInspect qualityInspect = new QualityInspect();
        BeanUtils.copyProperties(qualityInspectDto, qualityInspect);
        return qualityInspectMapper.updateById(qualityInspect);
        int result = qualityInspectMapper.updateById(qualityInspect);
        // ä¿å­˜é™„ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QUALITY_INSPECT, qualityInspectDto.getId(), qualityInspectDto.getStorageBlobDTOs());
        return result;
    }
    @Override
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedOrderServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.quality.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.quality.mapper.QualityUnqualifiedOrderMapper;
import com.ruoyi.quality.pojo.QualityUnqualifiedOrder;
import com.ruoyi.quality.service.IQualityUnqualifiedOrderService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@AllArgsConstructor
@Service
public class QualityUnqualifiedOrderServiceImpl extends ServiceImpl<QualityUnqualifiedOrderMapper, QualityUnqualifiedOrder> implements IQualityUnqualifiedOrderService {
    private final QualityUnqualifiedOrderMapper orderMapper;
    private final FileUtil fileUtil;
    @Override
    public IPage<QualityUnqualifiedOrder> listPage(Page page, QualityUnqualifiedOrder query) {
        return orderMapper.listPage(page, query);
    }
    @Override
    public QualityUnqualifiedOrder getDetail(Long id) {
        QualityUnqualifiedOrder order = orderMapper.selectById(id);
        if (order != null) {
            order.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.QUALITY_UNQUALIFIED_ORDER, order.getId()));
        }
        return order;
    }
}
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -5,6 +5,9 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.bean.BeanUtils;
@@ -54,6 +57,7 @@
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final StockUninventoryService stockUninventoryService;
    private final StockInRecordService stockInRecordService;
    private final FileUtil fileUtil;
    @Override
    public IPage<QualityUnqualified> qualityUnqualifiedListPage(Page page, QualityUnqualified qualityUnqualified) {
@@ -117,7 +121,11 @@
    @Override
    public QualityUnqualified getUnqualified(Integer id) {
        return qualityUnqualifiedMapper.getUnqualified(id);
        QualityUnqualified unqualified = qualityUnqualifiedMapper.getUnqualified(id);
        if (unqualified != null) {
            unqualified.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.QUALITY_UNQUALIFIED, unqualified.getId()));
        }
        return unqualified;
    }
    private void createReworkProductionByNewModel(ProductionProductMain sourceMain, BigDecimal quantity) {
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -140,5 +140,9 @@
    @TableField(exist = false)
    private Boolean hasProductionRecord;
    /**
     * å®¡æ‰¹çŠ¶æ€ï¼š0-待审批,1-审批中,2-已通过,3-已驳回
     */
    private Integer approvalStatus;
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -171,7 +171,6 @@
        if (salesLedgerProduct.getId() == null) {
            salesLedgerProduct.setRegisterDate(LocalDateTime.now());
            result = salesLedgerProductMapper.insert(salesLedgerProduct);
            addProductionData(salesLedgerProduct);
        } else {
            //查询原本的产品型号id
            result = salesLedgerProductMapper.updateById(salesLedgerProduct);
@@ -215,6 +214,17 @@
     * æ–°å¢žç”Ÿäº§æ•°æ®
     */
    public void addProductionData(SalesLedgerProduct salesLedgerProduct) {
        addProductionDataInternal(salesLedgerProduct, null);
    }
    /**
     * å®¡æ‰¹é€šè¿‡åŽåˆ›å»ºç”Ÿäº§ä¸»è®¡åˆ’(带审批状态)
     */
    public void addProductionDataForApproved(SalesLedgerProduct salesLedgerProduct, int approvalStatus) {
        addProductionDataInternal(salesLedgerProduct, approvalStatus);
    }
    private void addProductionDataInternal(SalesLedgerProduct salesLedgerProduct, Integer approvalStatus) {
        //先判断该产品是否需要生产
        if (!salesLedgerProduct.getIsProduction()) {
            return;
@@ -230,10 +240,12 @@
        productionPlan.setQtyRequired(salesLedgerProduct.getQuantity());
        productionPlan.setSource("销售");
        productionPlan.setStatus(0);
        productionPlan.setRequiredDate(salesLedger.getDeliveryDate());//需求日期=交货日期
        productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate());//承诺日期=交货日期
        productionPlan.setRequiredDate(salesLedger.getDeliveryDate());
        productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate());
        if (approvalStatus != null) {
            productionPlan.setApprovalStatus(approvalStatus);
        }
        productionPlanMapper.insert(productionPlan);
    }
    /**
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -12,6 +12,10 @@
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.ruoyi.approve.service.ApprovalInstanceService;
import com.ruoyi.approve.service.ApprovalTemplateService;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.CustomerMapper;
@@ -21,6 +25,7 @@
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.enums.TypeEnums;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.EnumUtil;
@@ -125,6 +130,11 @@
    ;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private ApprovalInstanceService approvalInstanceService;
    @Autowired
    private ApprovalTemplateService approvalTemplateService;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
@@ -578,7 +588,11 @@
                contractNo = generateSalesContractNo(salesLedgerDto.getEntryDate());
            }
            salesLedger.setSalesContractNo(contractNo);
            salesLedger.setApprovalStatus(0);
            salesLedgerMapper.insert(salesLedger);
            // æ–°å¢žæ—¶å‘起协同审批
            submitApproval(salesLedger);
        } else {
            salesLedgerMapper.updateById(salesLedger);
        }
@@ -625,8 +639,6 @@
            for (SalesLedgerProduct salesLedgerProduct : insertList) {
                salesLedgerProduct.setType(type.getCode());
                salesLedgerProductMapper.insert(salesLedgerProduct);
                // æ·»åŠ ç”Ÿäº§æ•°æ®
                salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
            }
        }
    }
@@ -770,4 +782,33 @@
            return totalAmount;
        }
    }
    /**
     * æ–°å¢žé”€å”®å°è´¦æ—¶å‘起协同审批
     */
    private void submitApproval(SalesLedger salesLedger) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        ApprovalTemplate template = approvalTemplateService.getOne(
                new LambdaQueryWrapper<ApprovalTemplate>()
                        .eq(ApprovalTemplate::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode())
                        .eq(ApprovalTemplate::getDeleted, 0)
                        .orderByDesc(ApprovalTemplate::getId)
                        .last("limit 1")
        );
        if (template == null) {
            log.warn("销售审批模板不存在,跳过发起审批");
            return;
        }
        ApprovalInstanceDto instanceDto = new ApprovalInstanceDto();
        instanceDto.setTemplateId(template.getId());
        instanceDto.setTemplateName(template.getTemplateName());
        instanceDto.setBusinessId(salesLedger.getId());
        instanceDto.setBusinessType(TypeEnums.SALES_LEDGER_APPROVAL.getCode());
        instanceDto.setCurrentLevel(1);
        instanceDto.setTitle(salesLedger.getSalesContractNo() + "销售审批");
        instanceDto.setApplicantId(loginUser.getUserId());
        instanceDto.setApplicantName(loginUser.getNickName());
        instanceDto.setApplyTime(LocalDateTime.now());
        approvalInstanceService.add(instanceDto);
    }
}
src/main/resources/mapper/basic/CustomerFollowUpMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.basic.mapper.CustomerFollowUpMapper">
    <select id="selectFollowUpExportList" resultType="com.ruoyi.basic.vo.CustomerFollowUpExportVo">
        select
            c.customer_name as customerName,
            c.customer_type as customerType,
            c.contact_person as contactPerson,
            c.contact_phone as contactPhone,
            c.maintainer as maintainer,
            f.follow_up_method as followUpMethod,
            f.follow_up_level as followUpLevel,
            f.follow_up_time as followUpTime,
            f.follower_user_name as followerUserName,
            f.content as content
        from customer_follow_up f
        left join customer c on f.customer_id = c.id
        <where>
            <if test="customerName != null and customerName != ''">
                and c.customer_name like concat('%', #{customerName}, '%')
            </if>
            <if test="customerType != null and customerType != ''">
                and c.customer_type = #{customerType}
            </if>
        </where>
        order by c.id desc, f.follow_up_time desc
    </select>
</mapper>
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
@@ -16,6 +16,10 @@
        qu.deal_result,
        qu.deal_name,
        qu.deal_time,
        qu.reason_analysis,
        qu.preventive_corrective,
        qu.loss_working,
        qu.loss_material,
        CASE
        WHEN qu.model = pm.id THEN pm.model
        ELSE qu.model
@@ -76,6 +80,10 @@
            qu.deal_result,
            qu.deal_name,
            qu.deal_time,
            qu.reason_analysis,
            qu.preventive_corrective,
            qu.loss_working,
            qu.loss_material,
            CASE
                WHEN qu.model = pm.id THEN pm.model
                ELSE qu.model
src/main/resources/mapper/quality/QualityUnqualifiedOrderMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ruoyi.quality.mapper.QualityUnqualifiedOrderMapper">
    <select id="listPage" resultType="com.ruoyi.quality.pojo.QualityUnqualifiedOrder">
        SELECT *
        FROM quality_unqualified_order
        <where>
            <if test="query.status != null">
                AND status = #{query.status}
            </if>
            <if test="query.projectName != null and query.projectName != ''">
                AND project_name LIKE CONCAT('%', #{query.projectName}, '%')
            </if>
            <if test="query.orderNo != null and query.orderNo != ''">
                AND order_no LIKE CONCAT('%', #{query.orderNo}, '%')
            </if>
            <if test="query.entryDateStart != null and query.entryDateStart != ''">
                AND create_time &gt;= #{query.entryDateStart}
            </if>
            <if test="query.entryDateEnd != null and query.entryDateEnd != ''">
                AND create_time &lt;= #{query.entryDateEnd}
            </if>
        </where>
        order by create_time desc
    </select>
</mapper>