feat(mes): 种子二维码标签国标四要素合规改造
依据《农作物种子标签二维码编码规则》与 GB 20464-2024,最小销售单元标签
必须包含品种名称、生产经营者名称、单元识别代码、追溯网址四项信息并按序排列。
标签生成:
- 修复标签中文渲染为方框:字体选择改为按字形探测(canDisplay),剔除纯拉丁字体 Arial
- 标签按国标顺序输出四要素,任一缺失即拒绝导出,不再静默跳过空字段
- 新增单元识别代码格式校验(不超过 20 个字符,数字或字母数字组合)
- 修复标签品种被批次品种覆盖的问题(原注释写"优先取袋码品种",实现相反)
数据源统一:
- 新增 mes.operator-name / importer-name / imported 配置项
- 生产经营者名称与追溯网址统一由后端下发,消除前端硬编码
- 新增免登录接口 /public/mes/trace/config,H5 溯源页改为接口获取
- 溯源查询响应新增 codeStatus / operatorName / traceUrl 字段
生命周期与防伪:
- 已作废袋码在溯源侧失效,返回 codeStatus=CANCELED 并提示防伪警示
- 已作废袋码被扫仍记录扫码事件(作废码被扫是重要防伪信号)
- 导出二维码标签排除已作废袋码,避免作废标签误印导致真品被误判为假货
- 非本地环境启动校验追溯网址,内网/占位符地址拒绝启动
其他:
- H5 溯源页展示加工批次与物流销售信息(国标要求追溯网址可查)
- 托盘码/批次码不再展示为「单元识别代码」,避免误导消费者
- 袋序号改为 Redis 原子分配,修复并发生成产生重复序号的竞态
- 修正单元识别代码的错误接口示例值(原示例 21 字符且含连字符,本身违反国标)
- 补充前端联调方案文档 docs/seed_qr_label_gb_compliance_integration.md
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # ç§åäºç»´ç æ ç¾å½æ åè§ - å端èè°æ¹æ¡ |
| | | |
| | | ## èæ¯ |
| | | |
| | | 便®ãåä½ç©ç§åæ ç¾äºç»´ç ç¼ç è§åãä¸ãåä½ç©ç§åæ ç¾éåãï¼GB 20464-2024ï¼ï¼ |
| | | æ¯ä¸ªäºç»´ç 对åºå¯ä¸çæå°éå®åå
ç§åä¸ä¸å¾éå¤ä½¿ç¨ï¼äºç»´ç å¿
é¡»å
å«å项信æ¯å¹¶æé¡ºåºæåï¼ |
| | | |
| | | 1. åç§åç§° |
| | | 2. ç产ç»è¥è
åç§°ï¼è¿å£ç§å为è¿å£ååç§°ï¼ |
| | | 3. åå
è¯å«ä»£ç ï¼ä¸è¶
è¿ 20 个åç¬¦ï¼æ°åæåæ¯æ°åç»åï¼ |
| | | 4. 追溯ç½å |
| | | |
| | | æ¬æ¬¡æ¹é å°ãç产ç»è¥è
åç§°ãä¸ã追溯ç½åãæ¶æä¸ºå端ç»ä¸æ°æ®æºï¼å¹¶è®©å·²ä½åºç追溯ç 对å¤å¤±æã |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - H5 æ«ç 溯æºé¡µ `trace.html`ï¼å·²å®ææ¹é ï¼æ éå端å¦è¡å¤çï¼ |
| | | - 管çåå° - èµç 管ç页ï¼èå `mes/pro/bagCode/index`ï¼å端工ç¨ä¸**å½å缺失**ï¼éæ°å»ºï¼ |
| | | - 管çåå° - æçç 管ç页 / æ¹æ¬¡ç产管ç页ï¼äºç»´ç é¢è§ä¸å¯¼åºï¼å¦æï¼ |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | é´æ | |
| | | |------|------|------|------| |
| | | | GET | `/public/mes/trace/config` | **æ°å¢**ï¼è·å¾æº¯æºé¡µé
ç½®ï¼ç产ç»è¥è
åç§°ã追溯ç½åï¼ | å
ç»å½ | |
| | | | GET | `/public/mes/trace/query` | 追溯æ¥è¯¢ï¼**ååºæ°å¢ 3 ä¸ªåæ®µ** | å
ç»å½ | |
| | | | GET | `/admin-api/mes/pro/bag-code/export-qr` | 导åºäºç»´ç æ ç¾ ZIPï¼**æ°å¢æ ¡éªä¸æç»é»è¾** | `mes:pro-bag-code:export` | |
| | | | GET | `/admin-api/mes/pro/bag-code/export-excel` | 导åºè¢ç Excel | `mes:pro-bag-code:export` | |
| | | |
| | | ### 1. æ°å¢ï¼è·å¾æº¯æºé¡µé
ç½® |
| | | |
| | | `GET /public/mes/trace/config` |
| | | |
| | | **请æ±åæ°ï¼** æ |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "operatorName": "æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸", |
| | | "traceUrl": "http://1.95.78.124:9008/prod/trace.html", |
| | | "imported": false |
| | | }, |
| | | "msg": "" |
| | | } |
| | | ``` |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | operatorName | String | ç产ç»è¥è
åç§°ï¼`imported=true` æ¶ä¸ºè¿å£ååç§° | |
| | | | traceUrl | String | 追溯ç½åï¼ä¸äºç»´ç å
容åç¼å®å
¨ä¸è´ï¼ä¸å« `code` åæ°ï¼ | |
| | | | imported | Boolean | æ¯å¦è¿å£ç§ååºæ¯ï¼ä¸º true æ¶é¡µé¢å段ååºæ¾ç¤ºãè¿å£ååç§°ã | |
| | | |
| | | ### 2. 追溯æ¥è¯¢ååºæ°å¢å段 |
| | | |
| | | `GET /public/mes/trace/query?code=xxx` |
| | | |
| | | **ååºå段æ°å¢ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | codeStatus | String | `VALID`-ææ / `CANCELED`-å·²ä½åº | |
| | | | operatorName | String | ç产ç»è¥è
åç§°ï¼ä¸æ ç¾å°å·å
å®¹åæºï¼ | |
| | | | traceUrl | String | 追溯ç½åï¼ä¸æ ç¾å°å·å
å®¹åæºï¼ | |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "sourceType": "BAG", |
| | | "requestCode": "0286267849370923", |
| | | "codeStatus": "VALID", |
| | | "operatorName": "æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸", |
| | | "traceUrl": "http://1.95.78.124:9008/prod/trace.html", |
| | | "batchBound": false, |
| | | "itemCode": "xxx", |
| | | "itemName": "塿²³4å·", |
| | | "itemUnitMeasureName": "48åå
" |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | ### H5 溯æºé¡µï¼è¿½æº¯æ¥è¯¢åºåï¼ |
| | | |
| | | åè¦ç´ å¿
é¡»æå½æ 顺åºå±ç¤ºï¼å
¶ä½ä¿¡æ¯æåå
¶åï¼ |
| | | |
| | | | é¡ºåº | åæ®µ | å弿¥æº | 说æ | |
| | | |------|------|----------|------| |
| | | | 1 | åç§åç§° | `itemName` + `itemSpecification` | 彿 è¦ç´ â | |
| | | | 2 | ç产ç»è¥è
/ è¿å£ååç§° | `operatorName`ï¼éå `/config` ç `operatorName` | 彿 è¦ç´ â¡ï¼`imported=true` æ¶æ ç¾æ¹ä¸ºãè¿å£ååç§°ã | |
| | | | 3 | åå
è¯å«ä»£ç | `requestCode` å» URL åçç å¼ | 彿 è¦ç´ â¢ã**ä»
è¢ç ï¼`sourceType=BAG`ï¼ä½¿ç¨æ¤åç§°**ï¼æçç /æ¹æ¬¡ç å±ç¤ºä¸ºãæçç ï¼éæå°éå®åå
ï¼ããæ¹æ¬¡ç ï¼éæå°éå®åå
ï¼ãï¼é¿å
误导 | |
| | | | 4 | 追溯ç½å | `traceUrl`ï¼éå `/config` ç `traceUrl` | 彿 è¦ç´ â£ï¼å¯ç¹å»è·³è½¬ | |
| | | | 5+ | 计éåä½ã追溯类åãæ¹æ¬¡å·ãçäº§æ¥æã产线 | åèªå段 | è¾
å©ä¿¡æ¯ | |
| | | |
| | | ### H5 溯æºé¡µï¼ç产æç»æå åºï¼ |
| | | |
| | | 彿 è¦æ±è¿½æº¯ç½ååºå¯æ¥è¯¢ãç§åå å·¥æ¹æ¬¡ãåãç©æµéå®ä¿¡æ¯ãï¼å æ¤ä»¥ä¸åºå**对å¤å±ç¤º**ï¼é»è®¤æå ä¸å±å¼ï¼ï¼ |
| | | |
| | | | åºå | æ°æ®æ¥æº | 说æ | |
| | | |------|----------|------| |
| | | | çäº§æ¹æ¬¡ä¿¡æ¯ | `batchCode`ã`batchStatusName`ã`produceDate`ã`lineName`ã`planQuantity` ç | 对åºå½æ ãç§åå å·¥æ¹æ¬¡ãï¼`batchBound=false`ï¼æªå½å
¥æ¹æ¬¡ï¼æ¶**ä¸å±ç¤ºè¯¥åºå**ï¼é¿å
æç¤ºæ å å·¥æ¹æ¬¡ | |
| | | | ç§åæµåä¿¡æ¯ | `seedFlows`ï¼éå®åºåº bizType=119ï¼æ `salesTargets` | 对åºå½æ ãç©æµéå®ä¿¡æ¯ãï¼å«æ¥æºåå·ã客æ·ãæ°éãä»åº/åºåº | |
| | | | å å·¥æµè½¬ | `processTraces` | å å·¥èç¹æµè½¬è®°å½ | |
| | | |
| | | 以ä¸åºå**ä»
å
é¨å¯è§ï¼ä¸å¯¹å¤å±ç¤º**ï¼å¦éä¸å¹¶å±ç¤ºï¼å° `detailSections(d, false)` æ¹ä¸º `detailSections(d, true)`ï¼ï¼ |
| | | |
| | | | åºå | ä¸å±ç¤ºåå | |
| | | |------|-----------| |
| | | | ç产人å | æ¶åä¸ªäººä¿¡æ¯ | |
| | | | è´¨æ£ä¿¡æ¯ãæçä¿¡æ¯ãè¢ç å表ãéè´æ¥æº | å
鍿ç»ï¼éæ¶è´¹è
å
³å | |
| | | |
| | | ### 管çåå° - èµç 管ç页 |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | åç§åç§° | å表ã详æ
| è¢ç ç»å®çåç§ | |
| | | | åå
è¯å«ä»£ç | å表ã详æ
| å³è¢ç `code` | |
| | | | ç¶æ | å表 | å·²çæ / 已导åºå°å· / å·²ä½åº | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | 导åºäºç»´ç æ ç¾ | åè¦ç´ ä»»ä¸ç¼ºå¤±å**æç»å¯¼åºå¹¶æ¥é**ï¼ä¸åéé»è·³è¿ç©ºå段 | |
| | | | åè¦ç´ 缺失æç¤º | 缺å°åç§åç§° â 1040750021ï¼ç¼ºå°ç产ç»è¥è
â 1040750022ï¼ç¼ºå°åå
è¯å«ä»£ç â 1040750023ï¼æªé
置追溯ç½å â 1040750020 | |
| | | | åå
è¯å«ä»£ç æ ¼å¼ | å¿
须为 1~20 使°åæåæ¯æ°åç»åï¼å«è¿å符çç¬¦å·æ¶æ¥ 1040750024 | |
| | | | æ«ç å½ä¸å·²ä½åºè¢ç | è¿å `codeStatus=CANCELED`ï¼é¡µé¢æ¾ç¤ºã该追溯ç å·²ä½åºï¼éæ¬å
¬å¸ææäº§åï¼è°¨é²ååãï¼ä¸ä¸åå±ç¤ºæº¯æºè¯¦æ
| |
| | | | å·²ä½åºç è¢«æ«æ | **ä»è®°å½æ«ç äºä»¶**ï¼ä½åºç è¢«æ«æ¯éè¦é²ä¼ªä¿¡å·ï¼ï¼ä¸è®¡ä¸ºæ¥è¯¢å¤±è´¥ | |
| | | | æªé
置追溯ç½å | é local ç¯å¢å¯å¨æ¶ç´æ¥å¤±è´¥ï¼local ç¯å¢å¯éè¿ `mes.allow-plain-qr-code=true` å
¼å®¹åå²ç | |
| | | | 导åºäºç»´ç æ ç¾ | **å·²ä½åºçè¢ç ä¸å导åº**ï¼ææ¹æ¬¡/çæç»å¯¼åºæ¶åæé¤ï¼ãä½åºç æ ç¾è¥å°åºå¹¶è´´å°äº§åä¸ï¼æ¶è´¹è
æ«ç ä¼è¢«æç¤ºã该追溯ç å·²ä½åºï¼è°¨é²ååãï¼å¯¼è´çå被误å¤ä¸ºåè´§ | |
| | | |
| | | ## é
置项 |
| | | |
| | | å端é
置项ï¼`application-*.yaml` ç `mes` åç¼ï¼ï¼ |
| | | |
| | | | é
置项 | å¿
å¡« | 说æ | |
| | | |--------|------|------| |
| | | | `mes.trace-base-url` | æ¯ | 追溯ç½åï¼å¿
须为å
¬ä¼å¯è®¿é®å°åãé local ç¯å¢è¥ä¸ºå
ç½ IP / localhost / å ä½ç¬¦å°**æç»å¯å¨** | |
| | | | `mes.operator-name` | æ¯ | ç产ç»è¥è
åç§°ãæªé
ç½®åæ æ³å¯¼åºæ ç¾ï¼å¯å¨æ¶åè¦ï¼ | |
| | | | `mes.importer-name` | å¦ | è¿å£ååç§°ï¼è¿å£ç§ååºæ¯å¡«å | |
| | | | `mes.imported` | å¦ | æ¯å¦è¿å£ç§ååºæ¯ï¼é»è®¤ false | |
| | | | `mes.allow-plain-qr-code` | å¦ | æ¯å¦å
许äºç»´ç ç¼ç 纯ç å¼ï¼ä»
便¬å°å
¼å®¹åå²ç ï¼é»è®¤ false | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - **åç«¯ç¦æ¢å硬ç¼ç ç产ç»è¥è
åç§°ä¸è¿½æº¯ç½å**ï¼å¿
é¡»éè¿ `/public/mes/trace/config` è·åï¼å¦åä¼ä¸äºç»´ç å
容ä¸ä¸è´ã |
| | | - 追溯ç½åå¨äºç»´ç å
容ä¸é¡µé¢å±ç¤ºä¹é´åªæ `mes.trace-base-url` ä¸ä¸ªç弿ºï¼å端å±ç¤ºæ¶ä¸è¦èªè¡æ¼æ¥ `/prod` çè·¯å¾ã |
| | | - å·²ä½åºç çå¤å®è¦çä¸ç§å
¥å£ï¼è¢ç ï¼ç¶æ 30ï¼ãæçç ï¼ç¶æ 20ï¼ãæ¹æ¬¡ç ï¼ç¶æ 40ï¼ï¼ä¸è
çä½åºæä¸¾å¼ä¸åï¼å端ä¸è¦å
±ç¨å¸¸é夿ã |
| | | - **åéæ ç¾é鿰坼åº**ï¼æ¹é å导åºçäºç»´ç å
容已åºåï¼å
¶ä¸è¿½æº¯ç½åå¯è½ä¸ºå
ç½å°å䏿 ç¾ç¼ºå°åè¦ç´ ï¼é鿰坼åºå°å·ã |
| | | - **è¢åºå·æ¹ä¸ºåååé
**ï¼åå
ç±ãæ¥åºå
æå¤§å¼å +1ãå¾å°ï¼å¹¶åçæä¼äº§çéå¤è¢åºå·ï¼è¢åºå·ä¼å°å¨æ ç¾ä¸ï¼ï¼ç°å·²æ¹ä¸º Redis ååèªå¢åé
ï¼å¹¶ç¨åºå
æå¤§å¼æ ¡æ£åºåãåºå·**åªä¿è¯ä¸éå¤ãä¸åéï¼ä¸ä¿è¯è¿ç»**ï¼å¹¶åä¸ä¸åè¯·æ±æ¿å°ä¸ååºæ®µï¼å±æ£å¸¸ï¼ã |
| | | - 管çåå°èµç 管ç页é¢ï¼`mes/pro/bagCode/index`ï¼å¨å端工ç¨ä¸ç¼ºå¤±ï¼ä¸å¡äººåç®åæ æ³èªå©æ¥ç/导åºäºç»´ç ï¼å»ºè®®è¡¥å
ã |
| | |
| | | |
| | | @GetMapping("/get-by-code") |
| | | @Operation(summary = "æ«ç è¿½æº¯ï¼æ ¹æ®è¢ç ï¼äºç»´ç å
容ï¼åæ¥æ¹æ¬¡ã产线ã责任人çä¿¡æ¯") |
| | | @Parameter(name = "code", description = "è¢ç ï¼äºç»´ç å
容ï¼", required = true, example = "PC20260818L01001-0001") |
| | | @Parameter(name = "code", description = "åå
è¯å«ä»£ç ï¼å½æ ï¼ä¸è¶
è¿ 20 个åç¬¦ï¼æ°åæåæ¯æ°åç»åï¼", |
| | | required = true, example = "0286267849370923") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-bag-code:query')") |
| | | public CommonResult<MesProBagCodeTraceRespVO> getBagCodeByCode(@RequestParam("code") String code) { |
| | | MesProBagCodeDO bagCode = bagCodeService.getBagCodeByCode(code); |
| | |
| | | HttpServletResponse response) throws IOException { |
| | | List<MesProBagCodeDO> list; |
| | | String zipName; |
| | | // åªå¯¼åºå¯å°å·çè¢ç ï¼å·²ä½åºè¢ç çæ ç¾ä¸å¾åå°å·ï¼è´´å°äº§åä¸ä¼è¢«æ«ç æç¤º"å·²ä½åºï¼è°¨é²åå"ï¼ |
| | | if (StrUtil.isNotEmpty(uuid)) { |
| | | list = bagCodeService.getBagCodeListByUuid(uuid); |
| | | list = bagCodeService.getPrintableBagCodeListByUuid(uuid); |
| | | zipName = "è¢ç äºç»´ç -" + uuid; |
| | | } else if (batchId != null) { |
| | | list = bagCodeService.getBagCodeListByBatchId(batchId); |
| | | list = bagCodeService.getPrintableBagCodeListByBatchId(batchId); |
| | | zipName = "è¢ç äºç»´ç -" + batchId; |
| | | } else { |
| | | throw exception(PRO_QR_EXPORT_PARAMS_INVALID); |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.pub.trace; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO; |
| | | import cn.iocoder.yudao.module.mes.service.trace.MesProTracePublicService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | return success(service.queryTrace(code)); |
| | | } |
| | | |
| | | @GetMapping("/config") |
| | | @PermitAll |
| | | @Operation(summary = "è·å¾æº¯æºé¡µé
ç½®ï¼ç产ç»è¥è
åç§°ã追溯ç½åï¼") |
| | | public CommonResult<MesProTracePublicConfigRespVO> getConfig() { |
| | | return success(service.getPublicConfig()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.pub.trace.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * H5 æ«ç æº¯æº - 页é¢é
ç½® Response VO |
| | | * <p> |
| | | * å H5 溯æºé¡µä¸åãç产ç»è¥è
åç§°ãä¸ã追溯ç½åãï¼äºè
å为ç§åæ ç¾åè¦ç´ çç»æé¨åã |
| | | * ç±å端ç»ä¸ä¸åï¼ä¿è¯äºç»´ç å
å®¹ãæ ç¾å°å·å
å®¹ä¸ H5 页é¢å±ç¤ºä¸è
ä¸è´ï¼ |
| | | * é¿å
å端硬ç¼ç 导è´çå¤ç弿ºä¸ä¸è´é®é¢ã |
| | | */ |
| | | @Schema(description = "H5 æ«ç æº¯æº - 页é¢é
ç½® Response VO") |
| | | @Data |
| | | public class MesProTracePublicConfigRespVO { |
| | | |
| | | @Schema(description = "ç产ç»è¥è
åç§°ï¼è¿å£ç§ååºæ¯ä¸ºè¿å£ååç§°ï¼", example = "æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸") |
| | | private String operatorName; |
| | | |
| | | @Schema(description = "追溯ç½åï¼ä¸äºç»´ç å
容åç¼ä¸è´ï¼ä¸å« code åæ°ï¼", |
| | | example = "http://1.95.78.124:9008/prod/trace.html") |
| | | private String traceUrl; |
| | | |
| | | @Schema(description = "æ¯å¦è¿å£ç§ååºæ¯ï¼ä¸º true æ¶é¡µé¢æ ç¾åºå±ç¤ºãè¿å£ååç§°ã") |
| | | private Boolean imported; |
| | | |
| | | } |
| | |
| | | @Schema(description = "追溯å
¥å£ç±»åï¼BAG-è¢ç / PALLET-æçç / BATCH-æ¹æ¬¡ç ", example = "BAG") |
| | | private String sourceType; |
| | | |
| | | @Schema(description = "请æ±ç追溯ç ï¼åå§æ«ç å¼ï¼", example = "PC20260828twoITEM_001") |
| | | @Schema(description = "请æ±ç追溯ç ï¼åå§æ«ç å¼ï¼", example = "0286267849370923") |
| | | private String requestCode; |
| | | |
| | | @Schema(description = "è¿½æº¯ç æææ§ï¼VALID-ææ / CANCELED-å·²ä½åºï¼ä½åºç ä¸å¾ä½ä¸ºææäº§åæµéï¼", |
| | | example = "VALID") |
| | | private String codeStatus; |
| | | |
| | | @Schema(description = "æ¯å¦å·²å½å
¥çäº§æ¹æ¬¡ï¼false 表示è¢ç /æçå·²çæä½å°æªå½å
¥æ¹æ¬¡ï¼æ¤æ¶ä»
è¿ååç§ä¿¡æ¯ï¼", example = "true") |
| | | private Boolean batchBound; |
| | | |
| | | // ====== æ ç¾åè¦ç´ åºåï¼ä¸äºç»´ç æ ç¾å°å·å
å®¹ä¿æä¸è´ï¼ ====== |
| | | @Schema(description = "ç产ç»è¥è
åç§°ï¼è¿å£ç§ååºæ¯ä¸ºè¿å£ååç§°ï¼", |
| | | example = "æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸") |
| | | private String operatorName; |
| | | |
| | | @Schema(description = "追溯ç½åï¼ä¸äºç»´ç å
容åç¼ä¸è´ï¼", |
| | | example = "http://1.95.78.124:9008/prod/trace.html") |
| | | private String traceUrl; |
| | | |
| | | // ====== åç§ä¿¡æ¯åºå ====== |
| | | @Schema(description = "åç§ç¼ç ") |
| | | private String itemCode; |
| | |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | |
| | | return selectList(MesProBagCodeDO::getBatchId, batchId); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢çæç»ä¸å¯å°å·çè¢ç ï¼æé¤å·²ä½åºï¼ï¼ç¨äºäºç»´ç æ ç¾å¯¼åº |
| | | * <p> |
| | | * å·²ä½åºè¢ç çæ ç¾ä¸å¾åå°å·ï¼å
¶æ ç¾è¥è´´å°äº§åä¸ï¼æ¶è´¹è
æ«ç ä¼è¢«æç¤ºã该追溯ç å·²ä½åºãï¼ |
| | | * 导è´çå被误å¤ä¸ºåè´§ã |
| | | */ |
| | | default List<MesProBagCodeDO> selectPrintableListByUuid(String uuid) { |
| | | return selectList(new LambdaQueryWrapperX<MesProBagCodeDO>() |
| | | .eq(MesProBagCodeDO::getUuid, uuid) |
| | | .ne(MesProBagCodeDO::getStatus, MesProBagCodeStatusEnum.CANCELED.getStatus()) |
| | | .orderByAsc(MesProBagCodeDO::getBagNo)); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢æ¹æ¬¡ä¸å¯å°å·çè¢ç ï¼æé¤å·²ä½åºï¼ï¼ç¨äºäºç»´ç æ ç¾å¯¼åº |
| | | */ |
| | | default List<MesProBagCodeDO> selectPrintableListByBatchId(Long batchId) { |
| | | return selectList(new LambdaQueryWrapperX<MesProBagCodeDO>() |
| | | .eq(MesProBagCodeDO::getBatchId, batchId) |
| | | .ne(MesProBagCodeDO::getStatus, MesProBagCodeStatusEnum.CANCELED.getStatus()) |
| | | .orderByAsc(MesProBagCodeDO::getBagNo)); |
| | | } |
| | | |
| | | default Long selectCountByBatchId(Long batchId) { |
| | | return selectCount(MesProBagCodeDO::getBatchId, batchId); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢æ¹æ¬¡å
è¢ç æå¤§åºå·ï¼å
å«å·²é»è¾å é¤è®°å½ï¼ä¿è¯åºå·ä¸åéãç å¼å¯ä¸ï¼ |
| | | * æ¥è¯¢å
¨å±æå¤§è¢åºå·ï¼è¢åºå·ä¸ºå
¨å±åºå·ï¼ä¸åºåæ¹æ¬¡ï¼å
å«å·²é»è¾å é¤è®°å½ï¼ä¿è¯åºå·ä¸åéï¼ |
| | | * <p> |
| | | * ä»
ç¨äºä¸º Redis åºå·åé
æä¾åºåå¼ï¼ä¸è¦å¨ä¸å¡ä¸ç´æ¥ç¨ãè¿åå¼ + 1ãä½ä¸ºèµ·å§åºå·ââ |
| | | * 飿¯å
读ååï¼å¹¶åçæä¼äº§çéå¤åºå·ãè¯·ä½¿ç¨ |
| | | * {@code MesCodeRedisDAO#allocateBagNoStart(int, Integer)} åååé
ã |
| | | * |
| | | * @param batchId æ¹æ¬¡ç¼å· |
| | | * @return æå¤§è¢åºå·ï¼æ è®°å½è¿å 0 |
| | | * @return å
¨å±æå¤§è¢åºå·ï¼æ è®°å½è¿å 0 |
| | | */ |
| | | @Select("SELECT IFNULL(MAX(bag_no), 0) FROM mes_pro_bag_code") |
| | | Integer selectMaxBagNo(); |
| | |
| | | import cn.iocoder.yudao.framework.common.util.number.BizNoUtils; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.data.redis.core.StringRedisTemplate; |
| | | import org.springframework.data.redis.core.script.DefaultRedisScript; |
| | | import org.springframework.data.redis.core.script.RedisScript; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * MES ä¸å¡ç¼ç çæç Redis DAOï¼å®ä¾ç ï¼è¢ç / æçç / 临æ¶ç çï¼ã |
| | |
| | | */ |
| | | @Repository |
| | | public class MesCodeRedisDAO { |
| | | |
| | | /** |
| | | * è¢åºå·èªå¢ KEYï¼å
¨å±è¢åºå·ï¼ä¿è¯å䏿¹/è·¨æ¹çææ¶ä¸éå¤ï¼ |
| | | */ |
| | | private static final String BAG_NO_SEQ_KEY = "mes:bag_no:seq"; |
| | | |
| | | /** |
| | | * åååé
è¢åºå·åºé´ç Lua èæ¬ã |
| | | * <p> |
| | | * å
æåºå
æå¤§åºå·æ ¡æ£åºåå¼ï¼Redis 馿¬¡ä½¿ç¨ãæåºå
æ°æ®è¢«ç´æ¥å¯¼å
¥å¯¼è´åºåè½åæ¶ï¼ï¼ |
| | | * åååèªå¢å¹¶è¿åèªå¢åçå¼ãæ´ä¸ªãæ ¡æ£ + èªå¢ãå¨ Redis å线ç¨å
宿ï¼é¿å
å¹¶åä¸äºç¸è¦ç计æ°ã |
| | | * <p> |
| | | * KEYS[1] = åºå· KEYï¼ARGV[1] = æ¬æ¬¡éè¦ç个æ°ï¼ARGV[2] = åºå
å½åæå¤§åºå· |
| | | */ |
| | | private static final RedisScript<Long> ALLOCATE_BAG_NO_SCRIPT = new DefaultRedisScript<>( |
| | | "local cur = redis.call('GET', KEYS[1]) " |
| | | + "local base = tonumber(ARGV[2]) " |
| | | + "if (not cur) or (tonumber(cur) < base) then redis.call('SET', KEYS[1], base) end " |
| | | + "return redis.call('INCRBY', KEYS[1], ARGV[1])", |
| | | Long.class); |
| | | |
| | | @Resource |
| | | private StringRedisTemplate stringRedisTemplate; |
| | |
| | | throw new IllegalStateException("çæå¯ä¸ä¸å¡ç¼ç 失败ï¼è¯·éè¯"); |
| | | } |
| | | |
| | | /** |
| | | * åååé
䏿®µè¿ç»çè¢åºå·ï¼è¿ååºé´èµ·å§å¼ã |
| | | * <p> |
| | | * è¢åºå·åå
ç±ãæ¥åºå
æå¤§å¼å +1ãå¾å°ï¼å¹¶åçææ¶ä¸¤ä¸ªè¯·æ±ä¼è¯»å°åä¸ä¸ªæå¤§å¼ï¼ |
| | | * ä»è产åºéå¤çè¢åºå·ï¼è¢åºå·ä¼å°å¨æ ç¾ä¸ï¼ãæ¤å¤æ¹ä¸º Redis ååèªå¢åé
ï¼ |
| | | * å¹¶ç¨åºå
å½åæå¤§å¼ååºåæ ¡æ£ï¼ä¿è¯åºå·æ¢ä¸éå¤ä¹ä¸åéã |
| | | * |
| | | * @param count æ¬æ¬¡éè¦çè¿ç»åºå·ä¸ªæ°ï¼é¡»å¤§äº 0ï¼ |
| | | * @param dbMaxBagNo æ°æ®åºå½åæå¤§è¢åºå·ï¼å«å·²é»è¾å é¤è®°å½ï¼ä¼ null è§ä¸º 0ï¼ |
| | | * @return åºé´èµ·å§åºå·ï¼å«ï¼ï¼å¯ç¨åºé´ä¸º [start, start + count - 1] |
| | | */ |
| | | public int allocateBagNoStart(int count, Integer dbMaxBagNo) { |
| | | if (count <= 0) { |
| | | throw new IllegalArgumentException("è¢åºå·åé
个æ°å¿
é¡»å¤§äº 0"); |
| | | } |
| | | Long end = stringRedisTemplate.execute(ALLOCATE_BAG_NO_SCRIPT, List.of(BAG_NO_SEQ_KEY), |
| | | String.valueOf(count), String.valueOf(dbMaxBagNo == null ? 0 : dbMaxBagNo)); |
| | | if (end == null) { |
| | | throw new IllegalStateException("åé
è¢åºå·å¤±è´¥ï¼è¯·éè¯"); |
| | | } |
| | | return (int) (end - count + 1); |
| | | } |
| | | |
| | | } |
| | |
| | | ErrorCode PRO_BAG_CODE_PALLET_COUNT_REQUIRED = new ErrorCode(1_040_752_013, "该åç§ææççæè¢ç ï¼æçæ°å¿
é¡»å¤§äº 0"); |
| | | ErrorCode PRO_BAG_CODE_BIND_BATCH_ITEM_MISMATCH = new ErrorCode(1_040_752_014, "å½å
¥æ¹æ¬¡çè¢ç å¿
é¡»å±äºåä¸åç§"); |
| | | |
| | | // ========== MES ç§åæ ç¾å½æ åè¦ç´ ï¼1-040-750-020ï¼ ========== |
| | | ErrorCode PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED = new ErrorCode(1_040_750_020, "æªé
置追溯ç½åï¼æ æ³çæäºç»´ç æ ç¾"); |
| | | ErrorCode PRO_QR_LABEL_ITEM_NAME_MISSING = new ErrorCode(1_040_750_021, "æ ç¾ç¼ºå°åç§åç§°ï¼æ æ³å¯¼åº"); |
| | | ErrorCode PRO_QR_LABEL_OPERATOR_MISSING = new ErrorCode(1_040_750_022, "æ ç¾ç¼ºå°ç产ç»è¥è
åç§°ï¼æ æ³å¯¼åº"); |
| | | ErrorCode PRO_QR_LABEL_UNIT_CODE_MISSING = new ErrorCode(1_040_750_023, "æ ç¾ç¼ºå°åå
è¯å«ä»£ç ï¼æ æ³å¯¼åº"); |
| | | ErrorCode PRO_QR_LABEL_UNIT_CODE_INVALID = new ErrorCode(1_040_750_024, "åå
è¯å«ä»£ç ä¸ç¬¦å彿 ï¼é¿åº¦ä¸è¶
è¿ 20 个å符ï¼ä¸åªè½ä¸ºæ°åæåæ¯æ°åç»å"); |
| | | |
| | | // ========== MES ç产管ç-æçç ï¼1-040-753-000ï¼ ========== |
| | | ErrorCode PRO_PALLET_NOT_EXISTS = new ErrorCode(1_040_753_000, "æçç ä¸åå¨"); |
| | | ErrorCode PRO_PALLET_HAS_BAG_NOT_DELETE = new ErrorCode(1_040_753_001, "æçç å·²ç»å®è¢ç ï¼ä¸å
许å é¤"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.enums.trace; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * è¿½æº¯ç æææ§ç¶ææä¸¾ï¼å
¬å¼æº¯æºæ¥è¯¢ç»æï¼ |
| | | * <p> |
| | | * 便®ãåä½ç©ç§åæ ç¾äºç»´ç ç¼ç è§åãï¼æ¯ä¸ªäºç»´ç 对åºå¯ä¸çæå°éå®åå
ä¸ä¸å¾éå¤ä½¿ç¨ã |
| | | * å·²ä½åºçè¢ç ï¼ç ´ææ¿æ¢åä½åºï¼å¿
须对å¤å¤±æï¼å¦åä½åºç ä»å¯è¢«åç¨éå®ï¼ |
| | | * å æ¤æº¯æºæ¥è¯¢éæç¡®åºåãææãä¸ãå·²ä½åºãã |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum MesTraceCodeStatusEnum { |
| | | |
| | | /** |
| | | * ææï¼ç å¤äºæ£å¸¸æµè½¬ç¶æ |
| | | */ |
| | | VALID("VALID", "ææ"), |
| | | |
| | | /** |
| | | * å·²ä½åºï¼è¢ç å·²è¢«ç ´ææ¿æ¢æµç¨ä½åºï¼ä¸å¾ä½ä¸ºææäº§åæµé |
| | | */ |
| | | CANCELED("CANCELED", "å·²ä½åº"); |
| | | |
| | | /** |
| | | * ç¶æç¼ç ï¼å¯¹å¤è¾åºï¼ |
| | | */ |
| | | private final String status; |
| | | /** |
| | | * ç¶æåç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * H5 溯æºé¡µé¢å°åï¼trace.htmlï¼åºç¡ URLï¼ç¨äºçææºå¸¦è¿½æº¯ç åæ°çäºç»´ç å
容ã |
| | | * 为空æ¶äºç»´ç ä»ç¼ç 纯ç å¼ï¼å
¼å®¹æ§ç ï¼ã |
| | | * <p> |
| | | * 该å¼ä¸ºã追溯ç½åãçå¯ä¸ç弿ºï¼äºç»´ç å
å®¹ä¸ H5 页é¢å±ç¤ºç追溯ç½åååèªæ¤é
ç½®ã |
| | | * å¿
须为å
¬ä¼æ«ç å¯è®¿é®çå°åï¼éæ¬å°ç¯å¢é
置为å
ç½/å ä½ç¬¦å°åæ¶å¯å¨å°ç´æ¥å¤±è´¥ã |
| | | */ |
| | | private String traceBaseUrl = ""; |
| | | |
| | | /** |
| | | * ç产ç»è¥è
åç§°ï¼ç§åæ ç¾åè¦ç´ ä¹ä¸ï¼ã |
| | | * <p> |
| | | * ä½ä¸ºãç产ç»è¥è
åç§°ãçå¯ä¸ç弿ºï¼æ ç¾å°å·ä¸ H5 溯æºé¡µå忤å¼ï¼ç¦æ¢å¨å端硬ç¼ç ã |
| | | */ |
| | | private String operatorName = ""; |
| | | |
| | | /** |
| | | * è¿å£ååç§°ï¼è¿å£ç§ååºæ¯ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼ã |
| | | * <p> |
| | | * è¿å£ç§ååºæ 注è¿å£ååç§°ï¼ä¸ºç©ºæ¶åéä½¿ç¨ {@link #operatorName}ã |
| | | */ |
| | | private String importerName = ""; |
| | | |
| | | /** |
| | | * æ¯å¦å¤äºè¿å£ç§ååºæ¯ï¼ä¸º true æ¶æ ç¾ä¸æº¯æºé¡µå±ç¤ºãè¿å£ååç§°ãèéãç产ç»è¥è
åç§°ã |
| | | */ |
| | | private boolean imported = false; |
| | | |
| | | /** |
| | | * å
许äºç»´ç ç¼ç 纯ç å¼ï¼ä¸å¸¦æº¯æº URLï¼ã |
| | | * <p> |
| | | * ä»
便¬å°/æµè¯ç¯å¢å
¼å®¹åå²ç 使ç¨ï¼ç产ç¯å¢å¿
须为 falseï¼å¦åä¼å°åºä¸å«è¿½æº¯ç½åçæ ç¾ã |
| | | */ |
| | | private boolean allowPlainQrCode = false; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.mes.framework.config; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder; |
| | | import cn.iocoder.yudao.module.mes.framework.qrcode.QrCodeImageUtils; |
| | | import jakarta.annotation.PostConstruct; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.core.env.Environment; |
| | | |
| | | import java.net.URI; |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * MES äºç»´ç ç¸å
³é
ç½®ï¼å¯å¨æ¶å° traceBaseUrl 注å
¥äºç»´ç æ ç¾æå»ºå¨ï¼é¿å
ä¾µå
¥ Controller è°ç¨ç¹ |
| | | * MES äºç»´ç ç¸å
³é
ç½®ï¼å¯å¨æ¶æ ¡éªå¹¶æé
置注å
¥äºç»´ç æ ç¾æå»ºå¨ï¼é¿å
ä¾µå
¥ Controller è°ç¨ç¹ã |
| | | * <p> |
| | | * ç§åæ ç¾å±äºå彿 约æçåè§ç©æï¼ä¸æ¦å°åºå³æ æ³å¬åï¼å æ¤è¿ééåãå¯å¨å³æ ¡éªãä¸åæ³å³æç»å¯å¨ã |
| | | * ççç¥ï¼è䏿¯çå°å¯¼åºæ¶æå¤±è´¥æéé»éçº§ï¼ |
| | | * <ul> |
| | | * <li>追溯ç½åå¿
须为å
¬ä¼å¯è®¿é®å°åï¼ç¦æ¢å
ç½ IP / æ¬æºå°å / æªæ¿æ¢çå ä½ç¬¦ï¼</li> |
| | | * <li>ç产ç»è¥è
åç§°å¿
å¡«ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼ç¼ºå¤±å³ä¸ºä¸åè§æ ç¾ï¼ï¼</li> |
| | | * <li>å½åç¯å¢å¿
é¡»åå¨å¯æ¸²æä¸æçåä½ï¼å¦åæ ç¾ä¸æä¼åææ¹æ¡ï¼ã</li> |
| | | * </ul> |
| | | */ |
| | | @Slf4j |
| | | @Configuration(proxyBeanMethods = false) |
| | | public class MesQrCodeConfig { |
| | | |
| | | /** |
| | | * æ¬å°ç¯å¢ profile åï¼è¯¥ profile 䏿¾å®½æ ¡éªï¼ä¾¿äºæ¬å°å¼åè°è¯ |
| | | */ |
| | | private static final String PROFILE_LOCAL = "local"; |
| | | |
| | | @Resource |
| | | private MesProperties mesProperties; |
| | | |
| | | @Resource |
| | | private Environment environment; |
| | | |
| | | @PostConstruct |
| | | public void init() { |
| | | // 1. 注å
¥é
ç½®ï¼å使 ¡éªç¬ç«äºä¸å¡é
ç½®ï¼æ¬å°ç¯å¢åæ ·å¿
é¡»éè¿ï¼å¦åæ ç¾ä¸æä¸å¯è¯»ï¼ |
| | | MesProQrLabelBuilder.setTraceBaseUrl(mesProperties.getTraceBaseUrl()); |
| | | MesProQrLabelBuilder.setOperatorName(mesProperties.getOperatorName()); |
| | | MesProQrLabelBuilder.setImporterName(mesProperties.getImporterName()); |
| | | MesProQrLabelBuilder.setImported(mesProperties.isImported()); |
| | | MesProQrLabelBuilder.setAllowPlainQrCode(mesProperties.isAllowPlainQrCode()); |
| | | QrCodeImageUtils.assertCjkFontAvailable(); |
| | | |
| | | // 2. 鿬å°ç¯å¢æ§è¡ä¸¥æ ¼æ ¡éª |
| | | if (isLocalProfile()) { |
| | | log.info("[init] å½å为æ¬å°ç¯å¢ï¼{}ï¼ï¼è·³è¿æ ç¾åè§é
ç½®çä¸¥æ ¼æ ¡éªï¼traceBaseUrl={}", |
| | | PROFILE_LOCAL, mesProperties.getTraceBaseUrl()); |
| | | return; |
| | | } |
| | | validateTraceBaseUrl(); |
| | | validateOperatorName(); |
| | | log.info("[init] äºç»´ç æ ç¾é
ç½®æ ¡éªéè¿ï¼traceBaseUrl={}ï¼ç产ç»è¥è
={}", |
| | | mesProperties.getTraceBaseUrl(), MesProQrLabelBuilder.resolveOperatorName()); |
| | | } |
| | | |
| | | private boolean isLocalProfile() { |
| | | return Arrays.asList(environment.getActiveProfiles()).contains(PROFILE_LOCAL); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªè¿½æº¯ç½åï¼å¿
须为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | */ |
| | | private void validateTraceBaseUrl() { |
| | | String url = StrUtil.trim(mesProperties.getTraceBaseUrl()); |
| | | if (StrUtil.isEmpty(url)) { |
| | | throw new IllegalStateException("mes.trace-base-url æªé
ç½®ãäºç»´ç æ ç¾å¿
é¡»å
å«è¿½æº¯ç½åï¼" |
| | | + "请å¨é
ç½®ä¸å¡«åå
¬ä¼æ«ç å¯è®¿é®ç H5 溯æºé¡µå°åã"); |
| | | } |
| | | if (StrUtil.containsAnyIgnoreCase(url, "your-domain", "example.com", "localhost")) { |
| | | throw new IllegalStateException("mes.trace-base-url ä»ä¸ºå ä½ç¬¦ææ¬æºå°åï¼" + url |
| | | + "ï¼å°åºçæ ç¾æ¶è´¹è
å°æ æ³è®¿é®ï¼è¯·å¡«åçå®çå¤ç½å°åã"); |
| | | } |
| | | String host = extractHost(url); |
| | | if (StrUtil.isEmpty(host)) { |
| | | throw new IllegalStateException("mes.trace-base-url æ ¼å¼éæ³ï¼æ æ³è§£æä¸»æºåï¼" + url); |
| | | } |
| | | if (isPrivateHost(host)) { |
| | | throw new IllegalStateException("mes.trace-base-url æåå
ç½å°åï¼" + host + "ï¼ï¼" + url |
| | | + "ï¼å
¬ä¼æ«ç æ æ³è®¿é®ï¼è¯·å¡«åå¤ç½å¯è®¿é®çå°åã"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªç产ç»è¥è
åç§°ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼ç¼ºå¤±å³ä¸ºä¸åè§æ ç¾ |
| | | * <p> |
| | | * æ¤å¤ä»
åè¦ä¸é»æå¯å¨ï¼ç产ç»è¥è
åç§°æä¸»ä½åºåï¼é¨åç¯å¢å¯è½å°æªé
ç½®ï¼ |
| | | * 强å¶å¯å¨å¤±è´¥ä¼å½±åæ å
³åè½ãçæ£çæ¦æªç¹å¨æ ç¾æå»ºå¤ï¼ç¼ºå¤±æ¶æç»å¯¼åºï¼ï¼ |
| | | * å æ¤ä¸ä¼åºç°"é
置缺失å´å°åºä¸åè§æ ç¾"çæ
åµã |
| | | */ |
| | | private void validateOperatorName() { |
| | | if (StrUtil.isBlank(MesProQrLabelBuilder.resolveOperatorName())) { |
| | | log.warn("[init] mes.operator-nameï¼ç产ç»è¥è
åç§°ï¼æªé
ç½®ãè¯¥åæ®µä¸ºç§åæ ç¾åè¦ç´ ä¹ä¸ï¼" |
| | | + "缺失æ¶å°æ æ³å¯¼åºäºç»´ç æ ç¾ï¼è¯·å°½å¿«å¨é
ç½®ä¸è¡¥å
ã"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä» URL 䏿å主æºå |
| | | */ |
| | | private String extractHost(String url) { |
| | | try { |
| | | URI uri = URI.create(url); |
| | | return uri.getHost(); |
| | | } catch (IllegalArgumentException ex) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 夿æ¯å¦ä¸ºæ¬æº / å
ç½ / é¾è·¯æ¬å°å°å |
| | | */ |
| | | private boolean isPrivateHost(String host) { |
| | | if ("localhost".equalsIgnoreCase(host) || host.endsWith(".local")) { |
| | | return true; |
| | | } |
| | | String[] parts = host.split("\\."); |
| | | if (parts.length != 4) { |
| | | return false; // ååå½¢å¼ï¼äº¤ç± DNS è§£æï¼ä¸å¨æ¤å¤æ¦æª |
| | | } |
| | | int a, b; |
| | | try { |
| | | a = Integer.parseInt(parts[0]); |
| | | b = Integer.parseInt(parts[1]); |
| | | } catch (NumberFormatException ex) { |
| | | return false; |
| | | } |
| | | // 127.0.0.0/8 æ¬æºã10.0.0.0/8ã172.16.0.0/12ã192.168.0.0/16 ç§ç½ã169.254.0.0/16 é¾è·¯æ¬å° |
| | | return a == 127 |
| | | || a == 10 |
| | | || (a == 172 && b >= 16 && b <= 31) |
| | | || (a == 192 && b == 168) |
| | | || (a == 169 && b == 254); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | import cn.hutool.core.date.LocalDateTimeUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ErrorCode; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.Map; |
| | | import java.util.regex.Pattern; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_ITEM_NAME_MISSING; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_OPERATOR_MISSING; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_UNIT_CODE_INVALID; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_UNIT_CODE_MISSING; |
| | | |
| | | /** |
| | | * MES äºç»´ç æ ç¾æå»ºå¨ï¼ç»ä¸"äºç»´ç ç¼ç å
容 + å¾çå å å¯è¯»ææ¬"çææ¬ç»è£
è§åï¼ |
| | | * ä¿è¯æ¹æ¬¡ãæçãè¢ç ä¸ç§æ ç¾çå
³é®ä¿¡æ¯ä¿çä¸è´ã |
| | | * MES äºç»´ç æ ç¾æå»ºå¨ï¼ç»ä¸"äºç»´ç ç¼ç å
容 + å¾çå å å¯è¯»ææ¬"çææ¬ç»è£
è§åã |
| | | * <p> |
| | | * é
ç½®äº {@code mes.trace-base-url} æ¶ï¼äºç»´ç å
容为 H5 溯æºå°åï¼å¸¦ code åæ°ï¼ï¼å¦ååé为纯ç å¼ï¼å
¼å®¹æ§ç ï¼ |
| | | * ç§åæ ç¾åãåä½ç©ç§åæ ç¾äºç»´ç ç¼ç è§åãä¸ãåä½ç©ç§åæ ç¾éåãï¼GB 20464-2024ï¼çº¦æï¼ |
| | | * æå°éå®åå
çæ ç¾å¿
é¡»å
å«å项信æ¯ä¸æåºå®é¡ºåºæåï¼ |
| | | * <ol> |
| | | * <li>åç§åç§°</li> |
| | | * <li>ç产ç»è¥è
åç§°ï¼è¿å£ç§å为è¿å£ååç§°ï¼</li> |
| | | * <li>åå
è¯å«ä»£ç ï¼ä¸è¶
è¿ 20 个åç¬¦ï¼æ°åæåæ¯æ°åç»åï¼</li> |
| | | * <li>追溯ç½å</li> |
| | | * </ol> |
| | | * åè¦ç´ ç¼ºå¤±å³ææä¸åè§æ ç¾ï¼å æ¤è¿ééåãç¼ºå¤±å³æå¼å¸¸ãæç»å¯¼åºãççç¥ï¼ç»ä¸éé»è·³è¿ââ |
| | | * éé»è·³è¿æ£æ¯æ ç¾ç¼ºé¡¹æµå
¥å°å·ç¯èçæ ¹å ã |
| | | * <p> |
| | | * äºç»´ç å
容å¨é
ç½®äº {@code mes.trace-base-url} æ¶ä¸ºå¸¦ code åæ°çæº¯æº URLï¼å¦åæ |
| | | * {@code mes.allow-plain-qr-code} å³å®æ¯å¦é级为纯ç å¼ï¼ä»
鿬å°å
¼å®¹åå²ç ï¼ã |
| | | */ |
| | | public final class MesProQrLabelBuilder { |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * H5 溯æºé¡µé¢åºç¡å°åï¼ç± {@code MesQrCodeConfig} å¯å¨æ¶æ³¨å
¥ï¼ä¸ºç©ºæ¶äºç»´ç ç¼ç 纯ç å¼ |
| | | * åå
è¯å«ä»£ç æ ¼å¼ï¼1~20 使°åæåæ¯æ°åç»åï¼å½æ è¦æ±ï¼ä¸å«è¿å符ç符å·ï¼ |
| | | */ |
| | | private static final Pattern UNIT_CODE_PATTERN = Pattern.compile("^[0-9A-Za-z]{1,20}$"); |
| | | |
| | | /** H5 溯æºé¡µé¢åºç¡å°åï¼ç± {@code MesQrCodeConfig} å¯å¨æ¶æ³¨å
¥ï¼ä¸ºç©ºæ¶äºç»´ç ç¼ç 纯ç å¼ */ |
| | | private static volatile String traceBaseUrl = ""; |
| | | /** ç产ç»è¥è
åç§°ï¼ç± {@code MesQrCodeConfig} å¯å¨æ¶æ³¨å
¥ */ |
| | | private static volatile String operatorName = ""; |
| | | /** è¿å£ååç§°ï¼è¿å£ç§ååºæ¯ä½¿ç¨ */ |
| | | private static volatile String importerName = ""; |
| | | /** æ¯å¦è¿å£ç§ååºæ¯ */ |
| | | private static volatile boolean imported = false; |
| | | /** æ¯å¦å
许äºç»´ç é级ç¼ç 纯ç å¼ï¼ä»
鿬å°å
¼å®¹åå²ç ï¼ */ |
| | | private static volatile boolean allowPlainQrCode = false; |
| | | |
| | | public static void setTraceBaseUrl(String traceBaseUrl) { |
| | | MesProQrLabelBuilder.traceBaseUrl = StrUtil.blankToDefault(traceBaseUrl, ""); |
| | | } |
| | | |
| | | public static void setOperatorName(String operatorName) { |
| | | MesProQrLabelBuilder.operatorName = StrUtil.blankToDefault(operatorName, ""); |
| | | } |
| | | |
| | | public static void setImporterName(String importerName) { |
| | | MesProQrLabelBuilder.importerName = StrUtil.blankToDefault(importerName, ""); |
| | | } |
| | | |
| | | public static void setImported(boolean imported) { |
| | | MesProQrLabelBuilder.imported = imported; |
| | | } |
| | | |
| | | public static void setAllowPlainQrCode(boolean allowPlainQrCode) { |
| | | MesProQrLabelBuilder.allowPlainQrCode = allowPlainQrCode; |
| | | } |
| | | |
| | | /** |
| | | * ç»è£
äºç»´ç å
容ï¼é
ç½®äºæº¯æºå°ååæ¼ URLï¼å¦åè¿å纯ç å¼ |
| | | * å½ååºæ 注çç产ç»è¥è
åç§°ï¼è¿å£ç§ååºæ¯ä¼å
åè¿å£ååç§°ï¼å¦ååç产ç»è¥è
åç§° |
| | | * |
| | | * @return åç§°ï¼æªé
ç½®æ¶è¿å空串 |
| | | */ |
| | | public static String resolveOperatorName() { |
| | | if (imported && StrUtil.isNotBlank(importerName)) { |
| | | return importerName; |
| | | } |
| | | return operatorName; |
| | | } |
| | | |
| | | /** |
| | | * 追溯ç½åï¼å¯¹å¤å±ç¤ºç¨ï¼ä¸å« code åæ°ï¼ |
| | | */ |
| | | public static String resolveTraceUrl() { |
| | | return traceBaseUrl; |
| | | } |
| | | |
| | | /** |
| | | * æ¯å¦è¿å£ç§ååºæ¯ï¼ä¸º true æ¶æ ç¾ä¸æº¯æºé¡µåºå±ç¤ºãè¿å£ååç§°ã |
| | | */ |
| | | public static boolean isImported() { |
| | | return imported; |
| | | } |
| | | |
| | | /** |
| | | * ç»è£
äºç»´ç å
容ï¼é
ç½®äºæº¯æºå°ååæ¼ URLï¼å¦åæå¼å
³å³å®æ¯å¦é级为纯ç å¼ |
| | | * <p> |
| | | * æªé
置溯æºå°å䏿ªå¼å¯é级å¼å
³æ¶ç´æ¥æ¥éï¼é¿å
å°åºä¸å«è¿½æº¯ç½åçæ ç¾ã |
| | | */ |
| | | private static String buildQrCodeContent(String code) { |
| | | if (StrUtil.isEmpty(traceBaseUrl)) { |
| | | if (allowPlainQrCode) { |
| | | return code; |
| | | } |
| | | throw exception(PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED); |
| | | } |
| | | return traceBaseUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8); |
| | | } |
| | |
| | | keyValues.put("å·²è£
è¢æ°", String.valueOf(pallet.getBagCount())); |
| | | } |
| | | if (batch != null) { |
| | | // åç§åç¬åæ¹æ¬¡åç§ï¼fillBatchKeyValues å·²ä¸ååå
¥åç§ï¼é¿å
ä¸è¢ç åç§äºç¸è¦ç |
| | | MesMdmItemDO batchItem = itemMap.get(batch.getItemId()); |
| | | if (batchItem != null && StrUtil.isNotEmpty(batchItem.getName())) { |
| | | keyValues.put("åç§", batchItem.getName()); |
| | | } |
| | | fillBatchKeyValues(keyValues, batch, itemMap, userMap); |
| | | } |
| | | return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build(); |
| | | } |
| | | |
| | | /** |
| | | * è¢ç äºç»´ç æ ç¾ |
| | | * è¢ç äºç»´ç æ ç¾ï¼æå°éå®åå
ï¼ |
| | | * <p> |
| | | * æå½æ è¦æ±ï¼åè¦ç´ ç½®äºæ ç¾æåå¹¶æåºå®é¡ºåºæåï¼åç§åç§° â ç产ç»è¥è
â åå
è¯å«ä»£ç â 追溯ç½åï¼ |
| | | * è¢åºå·ãæ¹æ¬¡å·ã产线ã责任人çç产信æ¯ä½ä¸ºè¾
å©å
容æåå¨å
¶åãåè¦ç´ ä»»ä¸ç¼ºå¤±å³æå¼å¸¸ï¼æç»å¯¼åºã |
| | | */ |
| | | public static QrLabelInfo buildBagCodeLabel(MesProBagCodeDO bagCode, |
| | | MesProBatchDO batch, |
| | | Map<Long, MesMdmItemDO> itemMap, |
| | | Map<Long, AdminUserRespDTO> userMap) { |
| | | LinkedHashMap<String, String> keyValues = new LinkedHashMap<>(); |
| | | keyValues.put("è¢ç ", bagCode.getCode()); |
| | | keyValues.put("è¢åºå·", bagCode.getBagNo() == null ? "" : String.valueOf(bagCode.getBagNo())); |
| | | |
| | | // ===== åè¦ç´ ï¼å½æ è¦æ±ï¼é¡ºåºåºå®ï¼ç¼ºå¤±å³é»æå¯¼åºï¼===== |
| | | putRequired(keyValues, "åç§åç§°", resolveItemName(bagCode, batch, itemMap), |
| | | PRO_QR_LABEL_ITEM_NAME_MISSING); |
| | | putRequired(keyValues, imported ? "è¿å£ååç§°" : "ç产ç»è¥è
åç§°", resolveOperatorName(), |
| | | PRO_QR_LABEL_OPERATOR_MISSING); |
| | | putRequired(keyValues, "åå
è¯å«ä»£ç ", resolveUnitCode(bagCode.getCode()), |
| | | PRO_QR_LABEL_UNIT_CODE_MISSING); |
| | | putRequired(keyValues, "追溯ç½å", resolveTraceUrl(), PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED); |
| | | |
| | | // ===== 以ä¸ä¸ºè¾
å©çäº§ä¿¡æ¯ ===== |
| | | if (bagCode.getBagNo() != null) { |
| | | keyValues.put("è¢åºå·", String.valueOf(bagCode.getBagNo())); |
| | | } |
| | | if (StrUtil.isNotEmpty(bagCode.getBatchCode())) { |
| | | keyValues.put("æ¹æ¬¡å·", bagCode.getBatchCode()); |
| | | } |
| | | if (StrUtil.isNotEmpty(bagCode.getPalletCode())) { |
| | | keyValues.put("ç»å®æç", bagCode.getPalletCode()); |
| | | } |
| | | // åç§ä¼å
åè¢ç èªèº«ï¼æ°æ®å
çææ¶ç»å®ï¼æªå½æ¹æ¬¡ä¹å¯æå°ï¼ï¼åå²è¢ç åéæ¹æ¬¡åç§ |
| | | if (StrUtil.isNotEmpty(bagCode.getItemName())) { |
| | | keyValues.put("åç§", bagCode.getItemName()); |
| | | } |
| | | if (batch != null) { |
| | | fillBatchKeyValues(keyValues, batch, itemMap, userMap); |
| | | } |
| | | return QrLabelInfo.builder().fileName(bagCode.getCode()).code(buildQrCodeContent(bagCode.getCode())).keyValues(keyValues).build(); |
| | | return QrLabelInfo.builder() |
| | | .fileName(bagCode.getCode()) |
| | | .code(buildQrCodeContent(bagCode.getCode())) |
| | | .keyValues(keyValues) |
| | | .build(); |
| | | } |
| | | |
| | | /** |
| | | * å¡«å
æ¹æ¬¡å
Œ
±å¯è¯»ææ¬ï¼åç§ãè§æ ¼ã产线ãçäº§æ¥æãè´£ä»»äººï¼ |
| | | * åå
¥å¿
å¡«åæ®µï¼å¼ä¸ºç©ºæ¶æåºå¯¹åºå¼å¸¸ï¼ç»ä¸éé»è·³è¿ |
| | | */ |
| | | private static void putRequired(LinkedHashMap<String, String> keyValues, String label, |
| | | String value, ErrorCode errorCode) { |
| | | if (StrUtil.isBlank(value)) { |
| | | throw exception(errorCode); |
| | | } |
| | | keyValues.put(label, value.trim()); |
| | | } |
| | | |
| | | /** |
| | | * è§£æåç§åç§°ï¼ä¼å
åè¢ç èªèº«ç»å®çåç§ï¼æ°æ®å
çææ¶åå
¥ï¼æªå½æ¹æ¬¡ä¹å¯å°å·ï¼ï¼ |
| | | * è¢ç æªç»å®åç§æ¶åéæ¹æ¬¡åç§ã |
| | | */ |
| | | private static String resolveItemName(MesProBagCodeDO bagCode, MesProBatchDO batch, |
| | | Map<Long, MesMdmItemDO> itemMap) { |
| | | if (StrUtil.isNotBlank(bagCode.getItemName())) { |
| | | return bagCode.getItemName(); |
| | | } |
| | | if (batch == null) { |
| | | return null; |
| | | } |
| | | MesMdmItemDO item = itemMap.get(batch.getItemId()); |
| | | return item == null ? null : item.getName(); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªå¹¶è¿ååå
è¯å«ä»£ç ï¼å½æ ï¼ä¸è¶
è¿ 20 个åç¬¦ï¼æ°åæåæ¯æ°åç»åï¼ |
| | | */ |
| | | private static String resolveUnitCode(String code) { |
| | | if (StrUtil.isBlank(code)) { |
| | | return null; |
| | | } |
| | | if (!UNIT_CODE_PATTERN.matcher(code).matches()) { |
| | | throw exception(PRO_QR_LABEL_UNIT_CODE_INVALID); |
| | | } |
| | | return code; |
| | | } |
| | | |
| | | /** |
| | | * å¡«å
æ¹æ¬¡å
Œ
±å¯è¯»ææ¬ï¼è§æ ¼ã产线ãçäº§æ¥æãè´£ä»»äººï¼ |
| | | * <p> |
| | | * 注æï¼æ¤å¤ä¸ååå
¥ãåç§ãï¼åç§åç§°ç»ä¸ç± {@link #resolveItemName} è§£æï¼ |
| | | * é¿å
æ¹æ¬¡åç§è¦çè¢ç åç§å¯¼è´æ ç¾ä¸æº¯æºé¡µå±ç¤ºä¸ä¸è´ã |
| | | */ |
| | | private static void fillBatchKeyValues(LinkedHashMap<String, String> keyValues, MesProBatchDO batch, |
| | | Map<Long, MesMdmItemDO> itemMap, Map<Long, AdminUserRespDTO> userMap) { |
| | | MesMdmItemDO item = itemMap.get(batch.getItemId()); |
| | | if (item != null) { |
| | | keyValues.put("åç§", item.getName()); |
| | | if (item != null && StrUtil.isNotEmpty(item.getSpecification())) { |
| | | keyValues.put("è§æ ¼", item.getSpecification()); |
| | | } |
| | | if (StrUtil.isNotEmpty(batch.getLineName()) || StrUtil.isNotEmpty(batch.getLineCode())) { |
| | | keyValues.put("产线", StrUtil.join(" / ", batch.getLineName(), batch.getLineCode())); |
| | | keyValues.put("çäº§æ¥æ", batch.getProduceDate() == null ? "" |
| | | : LocalDateTimeUtil.format(batch.getProduceDate(), "yyyy-MM-dd HH:mm")); |
| | | } |
| | | if (batch.getProduceDate() != null) { |
| | | keyValues.put("çäº§æ¥æ", LocalDateTimeUtil.format(batch.getProduceDate(), "yyyy-MM-dd HH:mm")); |
| | | } |
| | | AdminUserRespDTO user = userMap.get(batch.getOwnerUserId()); |
| | | if (user != null) { |
| | | keyValues.put("责任人", user.getNickname()); |
| | |
| | | /** |
| | | * äºç»´ç æ ç¾å¾çå·¥å
·ç±» |
| | | * <p> |
| | | * çæ"äºç»´ç + å³ä¾§å¯è¯»ææ¬"çåæå¾çï¼äºç»´ç åªç¼ç 纯ç å¼ï¼ææ¬å å å
³é®ä¿¡æ¯ï¼ |
| | | * çæ"äºç»´ç + å³ä¾§å¯è¯»ææ¬"çåæå¾çï¼äºç»´ç ç¼ç å
容ç±è°ç¨æ¹ä¼ å
¥ï¼é
ç½®äºæº¯æºå°åæ¶ä¸º |
| | | * 带 code åæ°çæº¯æº URLï¼æªé
ç½®æ¶ä¸ºçº¯ç å¼ï¼ï¼å³ä¾§å å å
³é®ä¿¡æ¯ææ¬ï¼ |
| | | * å³ä½¿æ°æ®åºä¸¢å¤±ä¹è½ä»æå°å¾çè¿åå
³é®ä¿¡æ¯ã |
| | | */ |
| | | @Slf4j |
| | |
| | | /** åæ®µé´è· */ |
| | | private static final int FIELD_GAP = 6; |
| | | |
| | | /** 䏿åä½åéï¼æä¼å
级ï¼ï¼é¿å
Linux headless 䏿ååæ¹æ¡ */ |
| | | /** |
| | | * 䏿åä½åéï¼æä¼å
级ï¼ã |
| | | * <p> |
| | | * åªå
许æ¶å½å¯æ¸²æä¸æçåä½ï¼çº¯æä¸åä½ï¼å¦ Arialï¼ä¸æ¦å
¥éï¼ä¸æä¼å
¨é¨æ¸²æææ¹æ¡ï¼ |
| | | * ä¸å¯¼åºè¿ç¨ä¸æ¥éï¼å±äºæéè½çä¸ç±»äºæ
ã |
| | | */ |
| | | private static final String[] FONT_CANDIDATES = { |
| | | "Noto Sans CJK SC", "WenQuanYi Zen Hei", "Microsoft YaHei", "SimSun", "Arial" |
| | | "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Zen Hei", "PingFang SC", |
| | | "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", "SimSun" |
| | | }; |
| | | |
| | | /** 䏿åå½¢æ¢éå符ï¼ç¨äºæ¢æµå使¯å¦å
·å¤ä¸æåå½¢ï¼ */ |
| | | private static final char CJK_PROBE = 'è¢'; |
| | | |
| | | /** è§£æåºç䏿å使åï¼ä¸º null 表示å½åç¯å¢æ²¡æä»»ä½å¯æ¸²æä¸æçåä½ */ |
| | | private static final String FONT_NAME = resolveFontName(); |
| | | |
| | | /** |
| | |
| | | * @return åæå¾ç |
| | | */ |
| | | public static BufferedImage createQrLabel(String code, LinkedHashMap<String, String> keyValues) { |
| | | // æ 䏿å使¶ç´æ¥å¤±è´¥ï¼é¿å
导åºä¸æä¸ºæ¹æ¡çæ ç¾ |
| | | assertCjkFontAvailable(); |
| | | QrConfig config = new QrConfig(); |
| | | config.setErrorCorrection(ErrorCorrectionLevel.H); // æå°æ ç¾æç£¨æï¼ç¨é«å®¹é |
| | | config.setMargin(1); // ä¿è¯éåºï¼æ«ç ç |
| | |
| | | |
| | | // ==================== ç§ææ¹æ³ ==================== |
| | | |
| | | /** |
| | | * è§£æå½åç¯å¢å¯ç¨ç䏿å使åã |
| | | * <p> |
| | | * ä¸è½æå使åå精确å¹é
ï¼åä¸åä½å¨ä¸åç³»ç»ä¸çæåå¯è½ä¸åï¼ä¾å¦ Windows ä¸å®é
注å为 |
| | | * {@code Microsoft YaHei UI} èé {@code Microsoft YaHei}ï¼ï¼ç²¾ç¡®å¹é
è½ç©ºåä¼ä¸è·¯éå°çº¯æä¸åä½ï¼ |
| | | * 䏿峿¸²æä¸ºæ¹æ¡ãå æ¤è¿éç»ä¸ç¨ {@link Font#canDisplay(char)} æ¢æµä¸æåå½¢ã |
| | | * |
| | | * @return 坿¸²æä¸æçå使åï¼æ å¯ç¨å使¶è¿å {@code null} |
| | | */ |
| | | private static String resolveFontName() { |
| | | Set<String> families = Set.of(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()); |
| | | for (String candidate : FONT_CANDIDATES) { |
| | | if (families.contains(candidate)) { |
| | | if (families.contains(candidate) && canDisplayCjk(candidate)) { |
| | | return candidate; |
| | | } |
| | | } |
| | | // é»è¾åä½ç± JDK è´è´£åå½¢åéï¼å¤æ°ç¯å¢å¯æ¸²æä¸æ |
| | | if (canDisplayCjk(Font.SANS_SERIF)) { |
| | | return Font.SANS_SERIF; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * æ¢æµæå®å使è½å¦æ¸²æä¸æ |
| | | */ |
| | | private static boolean canDisplayCjk(String family) { |
| | | return new Font(family, Font.PLAIN, FONT_SIZE).canDisplay(CJK_PROBE); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªå½åç¯å¢åå¨å¯æ¸²æä¸æçåä½ï¼å¦åæåºå¼å¸¸ã |
| | | * <p> |
| | | * ä¾å¯å¨æè°ç¨ï¼å®å¯å¨å¯å¨æ¶å¤±è´¥ï¼ä¹ä¸è½æä¸ææ¸²æææ¹æ¡çæ ç¾äº¤ä»å°å·ã |
| | | */ |
| | | public static void assertCjkFontAvailable() { |
| | | if (FONT_NAME == null) { |
| | | throw new IllegalStateException("å½åç¯å¢æ²¡æä»»ä½å¯æ¸²æä¸æçåä½ï¼äºç»´ç æ ç¾ç䏿尿¾ç¤ºä¸ºæ¹æ¡ï¼" |
| | | + "æç»çææ ç¾ãè¯·å¨æå¡å¨å®è£
䏿åä½ï¼å¦ fonts-noto-cjkï¼åéå¯ã"); |
| | | } |
| | | } |
| | | |
| | | private static int measureTextHeight(LinkedHashMap<String, String> keyValues, Font labelFont, Font valueFont) { |
| | | BufferedImage scratch = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); |
| | |
| | | List<MesProBagCodeDO> getBagCodeListByBatchId(Long batchId); |
| | | |
| | | /** |
| | | * è·å¾ææ¹æ¬¡ä¸å¯å°å·çè¢ç åè¡¨ï¼æé¤å·²ä½åºè¢ç ï¼æè¢åºå·ååºï¼ï¼ç¨äºæ¹é导åºäºç»´ç æ ç¾ |
| | | * <p> |
| | | * å·²ä½åºçè¢ç ï¼ç ´ææ¿æ¢åä½åºï¼ä¸å¾åå°å·ï¼å
¶æ ç¾è¥è´´å°äº§åä¸ï¼æ¶è´¹è
æ«ç ä¼è¢«æç¤º |
| | | * ã该追溯ç å·²ä½åºï¼è°¨é²ååãï¼å¯¼è´çå被误å¤ä¸ºåè´§ã |
| | | * |
| | | * @param batchId æ¹æ¬¡ç¼å· |
| | | * @return å¯å°å·çè¢ç å表 |
| | | */ |
| | | List<MesProBagCodeDO> getPrintableBagCodeListByBatchId(Long batchId); |
| | | |
| | | /** |
| | | * è·å¾æçæç»ä¸å¯å°å·çè¢ç åè¡¨ï¼æé¤å·²ä½åºè¢ç ï¼ï¼ç¨äºæ¹é导åºäºç»´ç æ ç¾ |
| | | * |
| | | * @param uuid çæç» UUID |
| | | * @return å¯å°å·çè¢ç å表 |
| | | */ |
| | | List<MesProBagCodeDO> getPrintableBagCodeListByUuid(String uuid); |
| | | |
| | | /** |
| | | * æ è®°è¢ç 为已åºåºï¼è®°å½éå®åºåºåç¼å·ååºåºæ¶é´ï¼ |
| | | * |
| | | * @param id è¢ç ç¼å· |
| | |
| | | } |
| | | palletMapper.insertBatch(pallets); |
| | | } |
| | | int startNo = bagCodeMapper.selectMaxBagNo() + 1; |
| | | // è¢åºå·åååé
ï¼å
ååºå
æå¤§åºå·ä½åºåï¼åç± Redis ååèªå¢ï¼é¿å
å¹¶åçæäº§çéå¤è¢åºå· |
| | | int startNo = codeRedisDao.allocateBagNoStart(totalCount, bagCodeMapper.selectMaxBagNo()); |
| | | List<MesProBagCodeDO> bagCodes = new ArrayList<>(totalCount); |
| | | for (int i = 0; i < totalCount; i++) { |
| | | int bagNo = startNo + i; |
| | |
| | | // åå§è¢ç ä¸ä¾èµè®¡éåä½ç±»å䏿çï¼ä»
ææ°éçæï¼è¢ç 带åç§åä½ï¼åç»å¯æ£å¸¸å½å
¥æ¹æ¬¡/ç»å®æç |
| | | MesMdmItemDO item = mdmItemService.validateItemExists(generateReqVO.getItemId()); |
| | | String uuid = IdUtil.fastSimpleUUID(); |
| | | int startNo = bagCodeMapper.selectMaxBagNo() + 1; |
| | | // è¢åºå·åååé
ï¼å
ååºå
æå¤§åºå·ä½åºåï¼åç± Redis ååèªå¢ï¼é¿å
å¹¶åçæäº§çéå¤è¢åºå· |
| | | int startNo = codeRedisDao.allocateBagNoStart(generateReqVO.getCount(), bagCodeMapper.selectMaxBagNo()); |
| | | List<MesProBagCodeDO> bagCodes = new ArrayList<>(generateReqVO.getCount()); |
| | | for (int i = 0; i < generateReqVO.getCount(); i++) { |
| | | int bagNo = startNo + i; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<MesProBagCodeDO> getPrintableBagCodeListByBatchId(Long batchId) { |
| | | return bagCodeMapper.selectPrintableListByBatchId(batchId); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesProBagCodeDO> getPrintableBagCodeListByUuid(String uuid) { |
| | | return bagCodeMapper.selectPrintableListByUuid(uuid); |
| | | } |
| | | |
| | | @Override |
| | | public void markOutbound(Long id, Long salesId) { |
| | | bagCodeMapper.markOutbound(id, salesId, LocalDateTime.now()); |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace; |
| | | |
| | | import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO; |
| | | |
| | | /** |
| | |
| | | */ |
| | | MesProTracePublicRespVO queryTrace(String code); |
| | | |
| | | /** |
| | | * è·å¾ H5 溯æºé¡µé
ç½®ï¼ç产ç»è¥è
åç§°ã追溯ç½åï¼ |
| | | * <p> |
| | | * 两项å为ç§åæ ç¾åè¦ç´ çç»æé¨åï¼ç±å端ç»ä¸ä¸åï¼ä¿è¯äºç»´ç å
å®¹ãæ ç¾å°å·å
å®¹ä¸ |
| | | * H5 页é¢å±ç¤ºä¸è´ã |
| | | * |
| | | * @return 页é¢é
ç½® |
| | | */ |
| | | MesProTracePublicConfigRespVO getPublicConfig(); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchWorkerTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceCodeStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; |
| | |
| | | if (entry.batchId == null && entry.bagItemId == null) { |
| | | throw exception(MES_TRACE_CODE_NOT_FOUND); |
| | | } |
| | | // 2. èåå
¨ç¨ä¿¡æ¯ |
| | | MesProTracePublicRespVO result = buildRespVO(rawCode, traceCode, entry.sourceType, entry.batchId, entry.bagItemId); |
| | | // 2. èåå
¨ç¨ä¿¡æ¯ï¼ä½åºç 仿£å¸¸è¿åå¹¶è®°å½æ«ç ï¼ä½åºç è¢«æ«æ¯éè¦çé²ä¼ªä¿¡å·ï¼ä¸åºè®°ä¸ºæ¥è¯¢å¤±è´¥ï¼ |
| | | MesProTracePublicRespVO result = buildRespVO(rawCode, traceCode, entry); |
| | | recordConsumerScan(rawCode, traceCode, entry.sourceType, entry.batchId, true); |
| | | return result; |
| | | } catch (RuntimeException ex) { |
| | |
| | | entry == null ? null : entry.batchId, false); |
| | | throw ex; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public MesProTracePublicConfigRespVO getPublicConfig() { |
| | | MesProTracePublicConfigRespVO respVO = new MesProTracePublicConfigRespVO(); |
| | | respVO.setOperatorName(MesProQrLabelBuilder.resolveOperatorName()); |
| | | respVO.setTraceUrl(MesProQrLabelBuilder.resolveTraceUrl()); |
| | | respVO.setImported(MesProQrLabelBuilder.isImported()); |
| | | return respVO; |
| | | } |
| | | |
| | | /** |
| | |
| | | // â è¢ç |
| | | MesProBagCodeDO bag = bagCodeService.getBagCodeByCode(traceCode); |
| | | if (bag != null) { |
| | | return new TraceEntry("BAG", bag.getBatchId(), bag.getItemId()); |
| | | // å·²ä½åºçè¢ç ï¼ç ´ææ¿æ¢åä½åºï¼å¿
须对å¤å¤±æï¼å½æ è¦æ±äºç»´ç ä¸å¾éå¤ä½¿ç¨ï¼ |
| | | // è¥ä½åºç ä»è¿åæææº¯æºä¿¡æ¯ï¼ä½åºç å³å¯è¢«åç¨éå® |
| | | if (MesProBagCodeStatusEnum.CANCELED.getStatus().equals(bag.getStatus())) { |
| | | return new TraceEntry("BAG", bag.getBatchId(), bag.getItemId(), |
| | | MesTraceCodeStatusEnum.CANCELED.getStatus()); |
| | | } |
| | | return TraceEntry.valid("BAG", bag.getBatchId(), bag.getItemId()); |
| | | } |
| | | // â¡ æçç |
| | | MesProPalletDO pallet = palletService.getPalletByCode(traceCode); |
| | |
| | | // æªå½å
¥æ¹æ¬¡çæçåæçå
ä»»ä¸è¢ç åç§ï¼ä¿è¯ä»è½å±ç¤ºåç§ä¿¡æ¯ |
| | | Long itemId = pallet.getBatchId() == null |
| | | ? firstItemId(bagCodeService.getBagCodeListByPalletId(pallet.getId())) : null; |
| | | return new TraceEntry("PALLET", pallet.getBatchId(), itemId); |
| | | if (MesProPalletStatusEnum.CANCELED.getStatus().equals(pallet.getStatus())) { |
| | | return new TraceEntry("PALLET", pallet.getBatchId(), itemId, |
| | | MesTraceCodeStatusEnum.CANCELED.getStatus()); |
| | | } |
| | | return TraceEntry.valid("PALLET", pallet.getBatchId(), itemId); |
| | | } |
| | | // â¢ æ¹æ¬¡ç ï¼å«ä¸´æ¶æ¹æ¬¡ç ï¼getBatchByCode å
é¨å
¼å®¹ï¼ |
| | | MesProBatchDO batch = batchService.getBatchByCode(traceCode); |
| | | if (batch != null) { |
| | | return new TraceEntry("BATCH", batch.getId(), null); |
| | | if (MesProBatchStatusEnum.CANCELED.getStatus().equals(batch.getStatus())) { |
| | | return new TraceEntry("BATCH", batch.getId(), null, |
| | | MesTraceCodeStatusEnum.CANCELED.getStatus()); |
| | | } |
| | | return new TraceEntry("", null, null); |
| | | return TraceEntry.valid("BATCH", batch.getId(), null); |
| | | } |
| | | return new TraceEntry("", null, null, MesTraceCodeStatusEnum.VALID.getStatus()); |
| | | } |
| | | |
| | | // ==================== èåæå»º ==================== |
| | | |
| | | private MesProTracePublicRespVO buildRespVO(String requestCode, String traceCode, String sourceType, Long batchId, Long bagItemId) { |
| | | private MesProTracePublicRespVO buildRespVO(String requestCode, String traceCode, TraceEntry entry) { |
| | | String sourceType = entry.sourceType(); |
| | | Long batchId = entry.batchId(); |
| | | Long bagItemId = entry.bagItemId(); |
| | | MesProBatchDO batch = batchId == null ? null : batchService.getBatch(batchId); |
| | | if (batchId != null && batch == null) { |
| | | throw exception(MES_TRACE_CODE_NOT_FOUND); |
| | |
| | | MesProTracePublicRespVO resp = new MesProTracePublicRespVO(); |
| | | resp.setSourceType(sourceType); |
| | | resp.setRequestCode(requestCode); |
| | | // è¿½æº¯ç æææ§ + æ ç¾åè¦ç´ ä¸çç产ç»è¥è
ä¸è¿½æº¯ç½åï¼ä¿è¯ä¸æ ç¾å°å·å
容ä¸è´ |
| | | resp.setCodeStatus(entry.codeStatus()); |
| | | resp.setOperatorName(MesProQrLabelBuilder.resolveOperatorName()); |
| | | resp.setTraceUrl(MesProQrLabelBuilder.resolveTraceUrl()); |
| | | // åç§åºå |
| | | fillItem(resp, batch, bagItemId); |
| | | if (batch == null) { |
| | |
| | | }; |
| | | } |
| | | |
| | | private record TraceEntry(String sourceType, Long batchId, Long bagItemId) { |
| | | /** |
| | | * 追溯å
¥å£è§£æç»æ |
| | | * |
| | | * @param sourceType å
¥å£ç±»åï¼BAG / PALLET / BATCHï¼æªå¹é
å°æ¶ä¸ºç©ºä¸² |
| | | * @param batchId çäº§æ¹æ¬¡ç¼å· |
| | | * @param bagItemId è¢ç ç»å®çåç§ç¼å·ï¼æªå½æ¹æ¬¡æ¶ç¨äºå±ç¤ºåç§ï¼ |
| | | * @param codeStatus è¿½æº¯ç æææ§ï¼{@link MesTraceCodeStatusEnum#VALID} / {@link MesTraceCodeStatusEnum#CANCELED} |
| | | */ |
| | | private record TraceEntry(String sourceType, Long batchId, Long bagItemId, String codeStatus) { |
| | | |
| | | static TraceEntry valid(String sourceType, Long batchId, Long bagItemId) { |
| | | return new TraceEntry(sourceType, batchId, bagItemId, MesTraceCodeStatusEnum.VALID.getStatus()); |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | |
| | | server: |
| | | port: 48080 |
| | | |
| | | # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å
容åç¼ï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | # ç§åæ ç¾ä¸ H5 溯æºé¡µé
ç½® |
| | | mes: |
| | | # 追溯ç½åï¼äºç»´ç å
容åç¼ï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | trace-base-url: http://192.168.1.7:48080/trace.html |
| | | # ç产ç»è¥è
åç§°ï¼ç§åæ ç¾åè¦ç´ ä¹ä¸ï¼æ ç¾ä¸æº¯æºé¡µç»ä¸åæ¤å¼ï¼æªé
ç½®åæ æ³å¯¼åºæ ç¾ï¼ |
| | | operator-name: æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸ |
| | | |
| | | --- #################### æ°æ®åºç¸å
³é
ç½® #################### |
| | | spring: |
| | |
| | | server: |
| | | port: 48080 |
| | | |
| | | # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å
容åç¼ï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | # ç§åæ ç¾ä¸ H5 溯æºé¡µé
ç½® |
| | | mes: |
| | | # 追溯ç½åï¼äºç»´ç å
容åç¼ï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | trace-base-url: http://1.95.78.124:9008/prod/trace.html |
| | | # ç产ç»è¥è
åç§°ï¼ç§åæ ç¾åè¦ç´ ä¹ä¸ï¼æ ç¾ä¸æº¯æºé¡µç»ä¸åæ¤å¼ï¼ |
| | | operator-name: æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸ |
| | | |
| | | --- #################### æ°æ®åºç¸å
³é
ç½® #################### |
| | | spring: |
| | |
| | | # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å
容åç¼ï¼å½¢å¦ http://åå:端å£/trace.htmlï¼ï¼ |
| | | # é为å
¬ä¼æ«ç å¯è®¿é®çå°åï¼å profile å¯è¦çãç空åäºç»´ç ç¼ç 纯ç å¼ï¼å
¼å®¹æ§ç ï¼ã |
| | | # ç§åæ ç¾ä¸ H5 溯æºé¡µé
ç½®ï¼å profile å¯è¦çï¼ |
| | | # 注æï¼trace-base-url ä¸ operator-name æ¯äºç»´ç æ ç¾åè¦ç´ ä¸çã追溯ç½åããç产ç»è¥è
åç§°ãï¼ |
| | | # é local ç¯å¢ä¸ trace-base-url è¥ä¸ºå
ç½/å ä½ç¬¦å°åå°æç»å¯å¨ï¼é¿å
å°åºæ¶è´¹è
æ«ä¸å¼çæ ç¾ã |
| | | mes: |
| | | # 追溯ç½åï¼äºç»´ç å
容åç¼ï¼å½¢å¦ http://åå:端å£/trace.htmlï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | trace-base-url: http://localhost:48080/trace.html |
| | | # ç产ç»è¥è
åç§°ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼æ ç¾ä¸æº¯æºé¡µç»ä¸åæ¤å¼ï¼æªé
ç½®åæ æ³å¯¼åºæ ç¾ï¼ |
| | | operator-name: '' |
| | | # è¿å£ååç§°ï¼è¿å£ç§ååºæ¯å¡«åï¼imported=true æ¶æ ç¾ä¸æº¯æºé¡µå±ç¤ºè¿å£ååç§°ï¼ |
| | | importer-name: '' |
| | | # æ¯å¦è¿å£ç§ååºæ¯ |
| | | imported: false |
| | | # æ¯å¦å
许äºç»´ç ç¼ç 纯ç å¼ï¼ä¸å¸¦æº¯æº URLï¼ï¼ä»
便¬å°å
¼å®¹åå²ç ï¼ç产ç¯å¢å¿
须为 false |
| | | allow-plain-qr-code: false |
| | | |
| | | spring: |
| | | application: |
| | |
| | | (function () { |
| | | 'use strict'; |
| | | |
| | | // ========== 页é¢å¯é
置项 ========== |
| | | // ç产ç»è¥è
ï¼è¿½æº¯æ¥è¯¢åºåå±ç¤ºï¼ |
| | | var COMPANY_NAME = 'æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸'; |
| | | // 追溯ç½åï¼å¤ç½è®¿é®å°åï¼ä¸å
è£
/äºç»´ç ä¸å°å¶çä¸è´ï¼ |
| | | var TRACE_URL = 'http://1.95.78.124:9008/'; |
| | | // ========== 页é¢é
ç½® ========== |
| | | // ç产ç»è¥è
åç§°ä¸è¿½æº¯ç½åæ¯ç§åæ ç¾åè¦ç´ çç»æé¨åï¼å¿
é¡»ä¸äºç»´ç /æ ç¾å°å·å
容ä¸è´ï¼ |
| | | // å æ¤ç»ä¸ç±å端ä¸åï¼/public/mes/trace/configï¼ï¼ç¦æ¢å¨æ¤ç¡¬ç¼ç ã |
| | | var COMPANY_NAME = ''; |
| | | var TRACE_URL = ''; |
| | | var IS_IMPORTED = false; |
| | | |
| | | // æå溯æºé¡µé
ç½®ï¼å¤±è´¥æ¶ä¸é»ææ¥è¯¢ï¼æ¥è¯¢ç»ææ¬èº«ä¹å¸¦ç产ç»è¥è
ä¸è¿½æº¯ç½åï¼ |
| | | function loadTraceConfig() { |
| | | return fetch('/public/mes/trace/config') |
| | | .then(function (resp) { return resp.json(); }) |
| | | .then(function (res) { |
| | | if (res && res.code === 0 && res.data) { |
| | | COMPANY_NAME = res.data.operatorName || ''; |
| | | TRACE_URL = res.data.traceUrl || ''; |
| | | IS_IMPORTED = !!res.data.imported; |
| | | } |
| | | }) |
| | | .catch(function () { /* é
ç½®æå失败ï¼éåä½¿ç¨æ¥è¯¢ç»æä¸çåååæ®µ */ }); |
| | | } |
| | | |
| | | var STATE = document.getElementById('state'); |
| | | var CODE_INPUT = document.getElementById('codeInput'); |
| | |
| | | renderState('<div class="empty-tip error">æªæ¥è¯¢å°è¯¥è¿½æº¯ç ï¼è¯·æ ¸å¯¹äºç»´ç æèç³»å®æ¹</div>'); |
| | | return; |
| | | } |
| | | // å·²ä½åºç追溯ç ï¼ç ´ææ¿æ¢åä½åºï¼ä¸å¾ä½ä¸ºææäº§åæµéï¼ç´æ¥ç»åºè¦ç¤ºå¹¶åæ¢å±ç¤ºæº¯æºè¯¦æ
|
| | | if (d.codeStatus === 'CANCELED') { |
| | | renderState('<div class="empty-tip error">该追溯ç å·²ä½åºï¼éæ¬å
¬å¸ææäº§åï¼è°¨é²ååã<br>' |
| | | + '妿çé®è¯·èç³»å®æ¹æ ¸å®ã</div>'); |
| | | return; |
| | | } |
| | | var html = ''; |
| | | html += sec('é²ä¼ªæ¥è¯¢', null, fakePanel(d), null); |
| | | html += sec('追溯æ¥è¯¢', null, tracePanel(d), null); |
| | | // ç产æç»æå åºï¼æ¹æ¬¡/è´¨æ£/人å/æç/è¢ç /éè´æ¥æº/ç§åæµå/å å·¥æµè½¬ï¼æéæ±ä¸å±ç¤ºï¼ |
| | | // detailSections ä¿ç以便åç»æéå¼å¯ |
| | | // ç产æç»æå åºï¼å½æ è¦æ±è¿½æº¯ç½å坿¥è¯¢ãç§åå å·¥æ¹æ¬¡ãåãç©æµéå®ä¿¡æ¯ãï¼æ
对å¤å±ç¤º |
| | | // ï¼å å·¥æ¹æ¬¡ãç§åæµåãå å·¥æµè½¬ï¼æå ä¸å±å¼ï¼ãè´¨æ£/人å/æç/è¢ç /éè´æ¥æºçå
鍿ç»ä¸å¯¹å¤å±ç¤ºï¼ |
| | | // å¦éä¸å¹¶å±ç¤ºï¼æ¹ä¸º detailSections(d, true) å³å¯ã |
| | | html += detailSections(d, false); |
| | | STATE.innerHTML = html || '<div class="empty-tip">ææ è¿½æº¯æ°æ®</div>'; |
| | | window.scrollTo({ top: 0, behavior: 'smooth' }); |
| | | } |
| | |
| | | } |
| | | |
| | | // åºå2ï¼è¿½æº¯æ¥è¯¢ï¼æªå½å
¥æ¹æ¬¡æ¶åªå±ç¤ºåç§ä¿¡æ¯ + æç¤ºï¼ |
| | | // åè¦ç´ ï¼åç§åç§° â ç产ç»è¥è
â åå
è¯å«ä»£ç â 追溯ç½åï¼æå½æ é¡ºåºæåï¼å
¶ä½ä¿¡æ¯æå¨å
¶å |
| | | function tracePanel(d) { |
| | | var code = resolveCode(d); |
| | | var name = [d.itemName, d.itemSpecification].filter(Boolean).join(' '); |
| | | // ç产ç»è¥è
ä¸è¿½æº¯ç½åä¼å
åæ¥è¯¢ç»æï¼ä¸æ ç¾å°å·å
å®¹åæºï¼ï¼éå页é¢é
ç½® |
| | | var operatorName = d.operatorName || COMPANY_NAME; |
| | | var traceUrl = d.traceUrl || TRACE_URL; |
| | | var operatorLabel = IS_IMPORTED ? 'è¿å£ååç§°' : 'ç产ç»è¥è
'; |
| | | // åªæè¢ç ææ¯ãæå°éå®åå
ãï¼å
¶ç å¼æç§°ãåå
è¯å«ä»£ç ãï¼ |
| | | // æçç /æ¹æ¬¡ç ä¸å±äºæå°éå®åå
æ è¯ï¼æå®é
ç±»åå±ç¤ºï¼é¿å
误导æ¶è´¹è
|
| | | var codeLabel = d.sourceType === 'BAG' |
| | | ? 'åå
è¯å«ä»£ç ' |
| | | : (SOURCE_TYPE[d.sourceType] ? SOURCE_TYPE[d.sourceType] + 'ï¼éæå°éå®åå
ï¼' : '追溯ç '); |
| | | var unbound = d.batchBound === false; |
| | | var batchRows = unbound |
| | | ? '<div class="notice">该追溯ç å°æªå½å
¥çäº§æ¹æ¬¡ï¼æ¹æ¬¡å·ãçäº§æ¥æã产线çä¿¡æ¯å¾
å®åï¼å½åå±ç¤ºè¯¥ç 对åºçåç§ä¿¡æ¯ã</div>' |
| | |
| | | kv('çäº§æ¥æ', fmtDate(d.produceDate)) + |
| | | kv('产线', text(d.lineName || d.lineCode)); |
| | | return kv('åç§åç§°', text(name)) + |
| | | kv(operatorLabel, text(operatorName)) + |
| | | kv(codeLabel, text(code || '-')) + |
| | | kv('追溯ç½å', traceUrl |
| | | ? '<a class="link" href="' + esc(traceUrl) + '">' + esc(traceUrl) + '</a>' |
| | | : '-') + |
| | | kv('计éåä½', text(d.itemUnitMeasureName)) + |
| | | kv('ç产ç»è¥è
', esc(COMPANY_NAME)) + |
| | | kv('åå
è¯å«ä»£ç ', text(code || '-')) + |
| | | kv('追溯ç½å', '<a class="link" href="' + esc(TRACE_URL) + '">' + esc(TRACE_URL) + '</a>') + |
| | | kv('追溯类å', esc(SOURCE_TYPE[d.sourceType] || '追溯ç ')) + |
| | | batchRows; |
| | | } |
| | | |
| | | // åºå3~ï¼ç产æç»ï¼æå ï¼ææ°æ®æå±ç¤ºï¼ |
| | | function detailSections(d) { |
| | | // full=true æ¶å±ç¤ºå
¨é¨æç»ï¼è´¨æ£/人å/æç/è¢ç /éè´æ¥æºï¼ï¼ |
| | | // é»è®¤åªå±ç¤ºå½æ è¦æ±çãå å·¥æ¹æ¬¡ãä¸ãç©æµéå®ï¼ç§åæµåï¼ãä¿¡æ¯ï¼å
¶ä½åºåä¸å¯¹å¤å±ç¤ºã |
| | | function detailSections(d, full) { |
| | | var html = ''; |
| | | |
| | | // çäº§æ¹æ¬¡ä¿¡æ¯ |
| | | // çäº§æ¹æ¬¡ä¿¡æ¯ï¼å½æ è¦æ±ï¼è¿½æº¯ç½ååºå¯æ¥è¯¢ç§åå å·¥æ¹æ¬¡ï¼ |
| | | // æªå½å
¥æ¹æ¬¡æ¶ä¸å±ç¤ºç©ºåºåï¼é¿å
ç»åº"æ å å·¥æ¹æ¬¡ä¿¡æ¯"çé误æç¤º |
| | | if (d.batchBound !== false) { |
| | | var batchBody = |
| | | kv('æ¹æ¬¡å·', text(d.batchCode)) + |
| | | kv('æ¹æ¬¡ç¶æ', d.batchStatusName ? statusBadge({ v: [d.batchStatusName, 'blue'] }, 'v') : text(d.batchStatus)) + |
| | | kv('çäº§æ¥æ', fmtDate(d.produceDate)) + |
| | | kv('产线', text(d.lineName || d.lineCode)) + |
| | | kv('è®¡åæ°é', fmtNum(d.planQuantity)) + |
| | | kv('å·²çæè¢ç ', fmtNum(d.bagQuantity)) + |
| | | kv('å·²çææç', fmtNum(d.palletQuantity)) + |
| | | kv('å
¥åºæ¶é´', fmtDate(d.inboundTime)) + |
| | | kv('ä¸´æ¶æ¹æ¬¡ç ', text(d.tempCode)) + |
| | | kv('夿³¨', text(d.remark)); |
| | | html += sec('çäº§æ¹æ¬¡ä¿¡æ¯', null, batchBody, { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // è´¨æ£ä¿¡æ¯ |
| | | if (d.quality) { |
| | | // è´¨æ£ä¿¡æ¯ï¼ä»
宿´æ¨¡å¼å±ç¤ºï¼ |
| | | if (full && d.quality) { |
| | | var q = d.quality; |
| | | var qBody = |
| | | kv('è´¨æ£ç»è®º', q.resultName ? '<span class="badge ' + (q.result === 1 ? 'green' : 'red') + '">' + esc(q.resultName) + '</span>' : '-') + |
| | |
| | | html += sec('è´¨æ£ä¿¡æ¯', null, qBody, { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // ç产人å |
| | | if (!isEmptyList(d.workers)) { |
| | | // ç产人åï¼ä»
宿´æ¨¡å¼å±ç¤ºï¼æ¶å个人信æ¯ï¼ä¸å¯¹å¤å
¬å¼ï¼ |
| | | if (full && !isEmptyList(d.workers)) { |
| | | var wBody = d.workers.map(function (w) { |
| | | return li('<span class="li-ttl">' + text(w.userName) + '</span>' + |
| | | (w.workerTypeName ? '<span class="badge gray">' + esc(w.workerTypeName) + '</span>' : ''), |
| | |
| | | html += sec('ç产人å', d.workers.length + ' 人', '<div class="scroll-list">' + wBody + '</div>', { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // æçä¿¡æ¯ |
| | | if (!isEmptyList(d.pallets)) { |
| | | // æçä¿¡æ¯ï¼ä»
宿´æ¨¡å¼å±ç¤ºï¼ |
| | | if (full && !isEmptyList(d.pallets)) { |
| | | var pRows = d.pallets.map(function (p) { |
| | | return [text(p.code), fmtNum(p.palletNo), fmtNum(p.bagCount), fmtNum(p.capacity)]; |
| | | }); |
| | |
| | | { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // è¢ç å表 |
| | | if (!isEmptyList(d.bags)) { |
| | | // è¢ç å表ï¼ä»
宿´æ¨¡å¼å±ç¤ºï¼ |
| | | if (full && !isEmptyList(d.bags)) { |
| | | var bBody = d.bags.map(function (b) { |
| | | return li('<span class="li-ttl">' + text(b.code) + '</span>' + |
| | | (b.statusName ? '<span class="badge gray">' + esc(b.statusName) + '</span>' : ''), |
| | |
| | | html += sec('è¢ç å表', bagCountText, '<div class="scroll-list">' + bBody + '</div>', { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // éè´æ¥æº |
| | | if (!isEmptyList(d.purchaseSources)) { |
| | | // éè´æ¥æºï¼ä»
宿´æ¨¡å¼å±ç¤ºï¼ |
| | | if (full && !isEmptyList(d.purchaseSources)) { |
| | | var psRows = d.purchaseSources.map(function (s) { |
| | | return [text(s.receiptCode), text(s.vendorName), nw(fmtNum(s.receivedQuantity)), nw(fmtDay(s.receiptDate))]; |
| | | }); |
| | |
| | | }); |
| | | })(); |
| | | |
| | | // å
æå页é¢é
ç½®ï¼ç产ç»è¥è
åç§°ã追溯ç½åï¼ï¼åæ§è¡æ¥è¯¢ï¼æ¥è¯¢ç»æä¸åæ ·å¸¦æè¿ä¸¤é¡¹ï¼äºä¸ºå
åº |
| | | loadTraceConfig(); |
| | | |
| | | var initialCode = new URLSearchParams(location.search).get('code') || ''; |
| | | CODE_INPUT.value = initialCode; |
| | | if (initialCode) { |