gaoluyang
5 小时以前 f4ef6d1f4489e0d4cbba1845792945b924c16228
feat: 客户签订合同统计 + MES 首页质检合格率

- CRM 客户列表实时统计已签订(审核通过)合同数量与金额合计
- 客户列表重点客户(A)排前
- MES 首页新增来料/过程/出货质检合格率与检验数量统计
- 同步业务可视化文档

Co-Authored-By: Claude Code <noreply@anthropic.com>
已修改13个文件
212 ■■■■■ 文件已修改
docs/project-business/data/objects.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/index.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/objects.json
@@ -2,7 +2,7 @@
  "objects": [
    {"name": "物料", "module": "基础主数据", "table": "mdm_item", "doc": "MDM 权威物料/产品/半成品档案,含 SKU、批次属性、价格、安全库存", "upstream": ["MES 物料反向同步"], "downstream": ["报价单", "销售订单", "采购订单", "工单BOM", "库存台账"], "keywords": "物料 产品 原材料 item"},
    {"name": "仓库", "module": "基础主数据", "table": "mdm_warehouse", "doc": "仓库主数据,type: 1原料/2成品/3半成品/4线边;MES 自有 mes_wm_warehouse 承载库区库位", "upstream": [], "downstream": ["库存台账", "出入库单据"], "keywords": "仓库 warehouse 原料仓 成品仓 线边仓"},
    {"name": "客户", "module": "销售管理", "table": "crm_customer", "doc": "客户主档,公海池/锁定/成交状态,负责人数据权限", "upstream": ["线索"], "downstream": ["联系人", "商机", "报价单", "合同", "售后工单", "收款单"], "keywords": "客户 customer 公海"},
    {"name": "客户", "module": "销售管理", "table": "crm_customer", "doc": "客户主档,公海池/锁定/成交状态,负责人数据权限;客户列表按「审核通过」合同实时统计已签订合同份数与金额合计", "upstream": ["线索"], "downstream": ["联系人", "商机", "报价单", "合同", "售后工单", "收款单"], "keywords": "客户 customer 公海"},
    {"name": "线索", "module": "销售管理", "table": "crm_clue", "doc": "原始销售线索,仅一次转化为客户", "upstream": [], "downstream": ["客户"], "keywords": "线索 clue"},
    {"name": "联系人", "module": "销售管理", "table": "crm_contact", "doc": "客户下联系人,与商机多对多", "upstream": ["客户"], "downstream": ["商机"], "keywords": "联系人 contact"},
    {"name": "商机", "module": "销售管理", "table": "crm_business", "doc": "销售机会,DB 配置式阶段推进,结束态: 赢/输/无效", "upstream": ["客户"], "downstream": ["报价单", "合同"], "keywords": "商机 business 赢单 输单 阶段"},
docs/project-business/index.html
@@ -445,7 +445,7 @@
  "objects": [
    {"name": "物料", "module": "基础主数据", "table": "mdm_item", "doc": "MDM 权威物料/产品/半成品档案,含 SKU、批次属性、价格、安全库存", "upstream": ["MES 物料反向同步"], "downstream": ["报价单", "销售订单", "采购订单", "工单BOM", "库存台账"], "keywords": "物料 产品 原材料 item"},
    {"name": "仓库", "module": "基础主数据", "table": "mdm_warehouse", "doc": "仓库主数据,type: 1原料/2成品/3半成品/4线边;MES 自有 mes_wm_warehouse 承载库区库位", "upstream": [], "downstream": ["库存台账", "出入库单据"], "keywords": "仓库 warehouse 原料仓 成品仓 线边仓"},
    {"name": "客户", "module": "销售管理", "table": "crm_customer", "doc": "客户主档,公海池/锁定/成交状态,负责人数据权限", "upstream": ["线索"], "downstream": ["联系人", "商机", "报价单", "合同", "售后工单", "收款单"], "keywords": "客户 customer 公海"},
    {"name": "客户", "module": "销售管理", "table": "crm_customer", "doc": "客户主档,公海池/锁定/成交状态,负责人数据权限;客户列表按「审核通过」合同实时统计已签订合同份数与金额合计", "upstream": ["线索"], "downstream": ["联系人", "商机", "报价单", "合同", "售后工单", "收款单"], "keywords": "客户 customer 公海"},
    {"name": "线索", "module": "销售管理", "table": "crm_clue", "doc": "原始销售线索,仅一次转化为客户", "upstream": [], "downstream": ["客户"], "keywords": "线索 clue"},
    {"name": "联系人", "module": "销售管理", "table": "crm_contact", "doc": "客户下联系人,与商机多对多", "upstream": ["客户"], "downstream": ["商机"], "keywords": "联系人 contact"},
    {"name": "商机", "module": "销售管理", "table": "crm_business", "doc": "销售机会,DB 配置式阶段推进,结束态: 赢/输/无效", "upstream": ["客户"], "downstream": ["报价单", "合同"], "keywords": "商机 business 赢单 输单 阶段"},
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -15,6 +15,7 @@
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -33,10 +34,12 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -56,6 +59,8 @@
    private CrmCustomerService customerService;
    @Resource
    private CrmCustomerPoolConfigService customerPoolConfigService;
    @Resource
    private CrmContractService contractService;
    @Resource
    private DeptApi deptApi;
@@ -139,6 +144,10 @@
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
        // 1.2 获取距离进入公海的时间
        Map<Long, Long> poolDayMap = getPoolDayMap(list);
        // 1.3 获取「已签订合同」的数量与金额合计(按本页客户批量统计,审核通过的合同才算)
        Set<Long> customerIds = convertSet(list, CrmCustomerDO::getId);
        Map<Long, Long> contractCountMap = contractService.getContractCountMapByCustomerIds(customerIds);
        Map<Long, BigDecimal> contractPriceMap = contractService.getContractPriceMapByCustomerIds(customerIds);
        // 2. 转换成 VO
        return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> {
            customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
@@ -153,6 +162,10 @@
            if (customerVO.getOwnerUserId() != null) {
                customerVO.setPoolDay(poolDayMap.get(customerVO.getId()));
            }
            // 2.3 设置已签订合同的数量与金额合计(没有合同的客户:数量 0、金额 null,前端显示「—」)
            customerVO.setContractCount(
                    contractCountMap.getOrDefault(customerVO.getId(), 0L).intValue());
            customerVO.setContractTotalPrice(contractPriceMap.get(customerVO.getId()));
        });
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
@@ -9,6 +9,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 客户 Response VO")
@@ -132,4 +133,12 @@
    @Schema(description = "距离加入公海时间", example = "1")
    private Long poolDay;
    // ========== 签订合同(由 crm_contract 的审核通过合同实时统计,非客户表字段) ==========
    @Schema(description = "已签订合同数量(审批状态为审核通过),没有则为 0", example = "2")
    private Integer contractCount;
    @Schema(description = "已签订合同金额合计(审批状态为审核通过),没有则为 null", example = "3235289.45")
    private BigDecimal contractTotalPrice;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
@@ -13,11 +13,17 @@
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
 * CRM 合同 Mapper
@@ -207,4 +213,45 @@
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    // ========== 按客户批量统计(客户列表展示「签订合同」) ==========
    /**
     * 批量统计各客户「已签订」(审核通过)的合同数量
     *
     * @param customerIds 客户编号集合
     * @return key:客户编号,value:合同数量。没有合同的客户不在 map 中
     */
    default Map<Long, Long> selectContractCountMapByCustomerIds(Collection<Long> customerIds) {
        if (CollUtil.isEmpty(customerIds)) {
            return Collections.emptyMap();
        }
        List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmContractDO>()
                .select("customer_id, COUNT(*) AS total")
                .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus())
                .groupBy("customer_id")
                .in("customer_id", customerIds));
        return convertMap(result, obj -> (Long) obj.get("customer_id"),
                obj -> ((Number) obj.get("total")).longValue());
    }
    /**
     * 批量统计各客户「已签订」(审核通过)的合同金额合计
     *
     * @param customerIds 客户编号集合
     * @return key:客户编号,value:金额合计。没有合同的客户不在 map 中
     */
    default Map<Long, BigDecimal> selectContractPriceMapByCustomerIds(Collection<Long> customerIds) {
        if (CollUtil.isEmpty(customerIds)) {
            return Collections.emptyMap();
        }
        // 金额用 IFNULL 兜底:convertMap 内部是 Collectors.toMap,value 为 null 会抛 NPE
        List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmContractDO>()
                .select("customer_id, IFNULL(SUM(total_price), 0) AS total_price")
                .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus())
                .groupBy("customer_id")
                .in("customer_id", customerIds));
        return convertMap(result, obj -> (Long) obj.get("customer_id"),
                obj -> (BigDecimal) obj.get("total_price"));
    }
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -92,6 +92,8 @@
                throw new IllegalArgumentException("未知联系状态:" + pageReqVO.getContactStatus());
            }
        }
        // 重点客户(A/level=1)默认排在前面
        query.orderByAsc(CrmCustomerDO::getLevel);
        return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
@@ -12,6 +12,7 @@
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import jakarta.validation.Valid;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -188,6 +189,26 @@
    Long getContractCountByCustomerId(Long customerId);
    /**
     * 批量统计各客户「已签订」的合同数量
     *
     * 「已签订」= 审批状态为审核通过的合同
     *
     * @param customerIds 客户编号集合
     * @return key:客户编号,value:合同数量。没有已签订合同的客户不在 map 中
     */
    Map<Long, Long> getContractCountMapByCustomerIds(Collection<Long> customerIds);
    /**
     * 批量统计各客户「已签订」的合同金额合计
     *
     * 「已签订」= 审批状态为审核通过的合同
     *
     * @param customerIds 客户编号集合
     * @return key:客户编号,value:金额合计。没有已签订合同的客户不在 map 中
     */
    Map<Long, BigDecimal> getContractPriceMapByCustomerIds(Collection<Long> customerIds);
    /**
     * 根据商机编号,获取关联客户的合同数量
     *
     * @param businessId 商机编号
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -737,6 +737,16 @@
    }
    @Override
    public Map<Long, Long> getContractCountMapByCustomerIds(Collection<Long> customerIds) {
        return contractMapper.selectContractCountMapByCustomerIds(customerIds);
    }
    @Override
    public Map<Long, BigDecimal> getContractPriceMapByCustomerIds(Collection<Long> customerIds) {
        return contractMapper.selectContractPriceMapByCustomerIds(customerIds);
    }
    @Override
    public List<CrmContractProductDO> getContractProductListByContractId(Long contactId) {
        return contractProductMapper.selectListByContractId(contactId);
    }
yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerAutoPoolTest.java
@@ -11,6 +11,7 @@
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.controller.admin.customer.CrmCustomerController;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -91,9 +92,17 @@
                }));
        var config = CrmCustomerPoolConfigService.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class<?>[]{CrmCustomerPoolConfigService.class}, (proxy, method, args) -> null));
        // 客户列表拼接会顺带统计「已签订合同」,这里桩掉,返回空 Map 表示本用例的客户没有合同
        var contracts = CrmContractService.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class<?>[]{CrmContractService.class}, (proxy, method, args) ->
                        switch (method.getName()) {
                            case "getContractCountMapByCustomerIds", "getContractPriceMapByCustomerIds" -> Map.of();
                            default -> java.lang.reflect.InvocationHandler.invokeDefault(proxy, method, args);
                        }));
        ReflectionTestUtils.setField(controller, "adminUserApi", users);
        ReflectionTestUtils.setField(controller, "deptApi", departments);
        ReflectionTestUtils.setField(controller, "customerPoolConfigService", config);
        ReflectionTestUtils.setField(controller, "contractService", contracts);
        var customer = controller.buildCustomerDetailList(List.of(new CrmCustomerDO().setId(1L)
                .setOwnerUserId(10L).setAutoPoolEnabled(true))).getFirst();
        assertEquals(10L, customer.getOwnerUserId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesHomeSummaryRespVO.java
@@ -36,6 +36,24 @@
    @Schema(description = "今日不良品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "34")
    private BigDecimal todayUnqualifiedQuantity;
    @Schema(description = "来料检验合格率(%),无检验数据时为 null", example = "99.93")
    private BigDecimal iqcQualifiedRate;
    @Schema(description = "过程检验合格率(%),无检验数据时为 null", example = "99.97")
    private BigDecimal ipqcQualifiedRate;
    @Schema(description = "出货检验合格率(%),无检验数据时为 null", example = "100.00")
    private BigDecimal oqcQualifiedRate;
    @Schema(description = "来料检验的检验数量合计(合格率的分母),无数据时为 0", example = "16782")
    private Long iqcCheckQuantity;
    @Schema(description = "过程检验的检验数量合计(合格率的分母),无数据时为 0", example = "12485")
    private Long ipqcCheckQuantity;
    @Schema(description = "出货检验的检验数量合计(合格率的分母),无数据时为 0", example = "2389")
    private Long oqcCheckQuantity;
    // ========== 设备统计 ==========
    @Schema(description = "设备总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java
@@ -55,6 +55,15 @@
    Long selectRepairActiveCount();
    /**
     * 按检验类型统计质检单的检验量与合格量
     *
     * 不过滤单据状态(草稿与已完成都算),也不做时间范围限制(全量累计)。
     *
     * @return [{ "type": "IQC", "checkQuantity": 16782, "qualifiedQuantity": 16770 }, ...]
     */
    List<Map<String, Object>> selectQcQualifiedSummary();
    /**
     * 按天聚合报工产量(近 N 天)
     *
     * @param beginTime >= 开始时间
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java
@@ -11,6 +11,7 @@
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -90,6 +91,23 @@
        summary.setAndonActiveCount(homeStatisticsMapper.selectAndonActiveCount());
        summary.setRepairActiveCount(homeStatisticsMapper.selectRepairActiveCount());
        // 6. 质检合格率(来料 / 过程 / 出货)
        //    数量口径 = 合格数量 / 检验数量;不过滤单据状态,也不做时间范围限制(全量累计)
        Map<String, Map<String, BigDecimal>> qcSummary = new HashMap<>();
        for (Map<String, Object> row : homeStatisticsMapper.selectQcQualifiedSummary()) {
            Map<String, BigDecimal> metrics = new HashMap<>();
            metrics.put("checkQuantity", getDecimalFromObj(row.get("checkQuantity")));
            metrics.put("qualifiedQuantity", getDecimalFromObj(row.get("qualifiedQuantity")));
            qcSummary.put(String.valueOf(row.get("type")), metrics);
        }
        summary.setIqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("IQC")));
        summary.setIpqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("IPQC")));
        summary.setOqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("OQC")));
        // 三个环节各自的检验数量合计(合格率的分母),供卡片副行展示样本量
        summary.setIqcCheckQuantity(calcCheckQuantity(qcSummary.get("IQC")));
        summary.setIpqcCheckQuantity(calcCheckQuantity(qcSummary.get("IPQC")));
        summary.setOqcCheckQuantity(calcCheckQuantity(qcSummary.get("OQC")));
        return summary;
    }
@@ -165,4 +183,35 @@
        return new BigDecimal(obj.toString());
    }
    /**
     * 计算质检合格率(百分比,保留两位小数)
     *
     * 检验数量为 0(即没有已完成的质检单)时返回 null,
     * 让页面显示「—」,而不是误导性的 0%。
     */
    private BigDecimal calcQcQualifiedRate(Map<String, BigDecimal> qcSummary) {
        if (qcSummary == null) {
            return null;
        }
        BigDecimal checkedQuantity = getDecimal(qcSummary, "checkQuantity");
        if (checkedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        return getDecimal(qcSummary, "qualifiedQuantity")
                .multiply(BigDecimal.valueOf(100))
                .divide(checkedQuantity, 2, RoundingMode.HALF_UP);
    }
    /**
     * 取该检验环节的检验数量合计(合格率的分母,即卡片副行展示的样本量)
     *
     * 该环节没有任何质检单时返回 0。
     */
    private Long calcCheckQuantity(Map<String, BigDecimal> qcSummary) {
        if (qcSummary == null) {
            return 0L;
        }
        return getDecimal(qcSummary, "checkQuantity").longValue();
    }
}
yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
@@ -50,6 +50,27 @@
                        ${@cn.iocoder.yudao.module.mes.enums.dv.MesDvRepairStatusEnum@APPROVING.status})
    </select>
    <!-- 按检验类型统计质检单的检验量与合格量(不过滤状态,无时间范围限制) -->
    <select id="selectQcQualifiedSummary" resultType="java.util.Map">
        SELECT 'IQC'                               AS type,
               IFNULL(SUM(check_quantity), 0)      AS checkQuantity,
               IFNULL(SUM(qualified_quantity), 0)  AS qualifiedQuantity
        FROM mes_qc_iqc
        WHERE deleted = 0
        UNION ALL
        SELECT 'IPQC',
               IFNULL(SUM(check_quantity), 0),
               IFNULL(SUM(qualified_quantity), 0)
        FROM mes_qc_ipqc
        WHERE deleted = 0
        UNION ALL
        SELECT 'OQC',
               IFNULL(SUM(check_quantity), 0),
               IFNULL(SUM(qualified_quantity), 0)
        FROM mes_qc_oqc
        WHERE deleted = 0
    </select>
    <!-- 按天聚合报工产量 -->
    <select id="selectDailyFeedbackTrend" resultType="java.util.Map">
        SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d')    AS date,