feat(mes): 新增防伪防窜货功能模块
- 添加消费者扫码访问事件、防窜货规则配置、窜货预警记录、数据完整性校验记录四张数据表
- 新增防伪防窜货相关错误码常量定义
- 扩展出库单数据结构,增加经销商名称快照、实际出库时间和操作人字段
- 在批次服务中集成数据完整性校验,在创建和更新时生成完整性摘要
- 完善追溯码公开接口,增加消费者扫码事件记录和防伪验证信息展示
- 实现出库单页面查询和展示优化,支持按出库时间范围筛选
- 移除过期的测试页面配置文件
已删除190个文件
已添加52个文件
已修改17个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # é²ä¼ªé²çªè´§ç®¡ç - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - çªè´§é¢è¦ç®¡çé¡µï¼æ°å¢èåï¼é²ä¼ªé²çªè´§ç®¡ç â çªè´§é¢è¦ï¼ |
| | | - æ«ç å°çåå¸é¡µï¼å¤ç¨æ¶è´¹è
æ«ç ç»è®¡æ¥å£ï¼ |
| | | - æ°æ®å®æ´æ§æ ¡éªé¡µï¼æ°å¢èåï¼é²ä¼ªé²çªè´§ç®¡ç â æ°æ®å®æ´æ§æ ¡éªï¼ |
| | | - æ¶è´¹è
æº¯æº H5 页é¢ï¼æ£åéªè¯ä¿¡æ¯å±ç¤ºå¢å¼ºï¼ |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. **çªè´§é¢è¦ç产ç**ï¼æ¶è´¹è
æ«ç æº¯æºæååï¼å端èªå¨æ¯å¯¹ãæ«ç IP å½å±å°ãä¸ãè¢ç åºåºç»éåå°åºãï¼ä¸ä¸è´æ¶èªå¨çæé¢è¦è®°å½ãé¢è¦æ éå端触åï¼åªéæä¾æ¥è¯¢ãå¤çãç»è®¡å
¥å£ã |
| | | 2. **é¢è¦ â æç»**ï¼çªè´§é¢è¦å页å表ä¸ï¼å¯æ¥çé¢è¦æ¶åçè¢ç /æçç ãæ¹æ¬¡å·ãç»éåãæ«ç å°åºãé¢è¦ç±»åä¸ç¶æã |
| | | 3. **宿´æ§æ ¡éªçå
¥å£**ï¼æ°æ®å®æ´æ§æ ¡éªé¡µè¾å
¥ãä¸å¡ç±»å + ä¸å¡ç¼å·ãåèµ·æ ¡éªãä¸å¡ç¼å·å¯ä»å¯¹åºä¸å¡åè¡¨ï¼æ¹æ¬¡ / åºåºå / æ«ç äºä»¶ï¼å¸¦å
¥ï¼é¿å
æå·¥å¡«åã |
| | | 4. **H5 æ£åéªè¯**ï¼æº¯æºæ¥å£ååºæ°å¢ `antiFake` åºåï¼å±ç¤ºè¯¥è¿½æº¯ç 累计æ«ç 次æ°ã馿¬¡/æè¿æ«ç æ¶é´ï¼å¸®å©æ¶è´¹è
è¯å«è¢«å¤æ¬¡æ«æççä¼¼å¤å¶ç ã |
| | | |
| | | ## API |
| | | |
| | | ### 1. çªè´§é¢è¦ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | æé | |
| | | |------|------|------|------| |
| | | | GET | `/mes/trace-cross-region/page` | çªè´§é¢è¦å页 | `mes:trace-cross-region:query` | |
| | | | PUT | `/mes/trace-cross-region/handle` | å¤çé¢è¦ï¼å·²å¤ç/误æ¥ï¼ | `mes:trace-cross-region:handle` | |
| | | | GET | `/mes/trace-cross-region/summary` | é¢è¦æ±æ»ï¼æ»æ°/å¾
å¤ç/è·¨ç/è·¨å¸ï¼ | `mes:trace-cross-region:query` | |
| | | | GET | `/mes/trace-cross-region/statistics` | é¢è¦åå¸ç»è®¡ï¼æç份/ç»éå/ç©æçåç»ï¼ | `mes:trace-cross-region:query` | |
| | | | GET | `/mes/trace-cross-region/config` | æ¥è¯¢å¤å®è§åé
ç½® | `mes:trace-cross-region:query` | |
| | | | PUT | `/mes/trace-cross-region/config` | æ´æ°å¤å®è§åï¼ç/å¸ç²åº¦åæ¢ï¼ | `mes:trace-cross-region:handle` | |
| | | |
| | | **GET /page 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | status | Integer | å¦ | ç¶æï¼10 å¾
å¤ç / 20 å·²å¤ç / 30 è¯¯æ¥ | |
| | | | warningType | String | å¦ | é¢è¦ç±»åï¼CROSS_PROVINCE / CROSS_CITY | |
| | | | batchId | Long | å¦ | æ¹æ¬¡ç¼å· | |
| | | | batchCode | String | å¦ | æ¹æ¬¡å·ï¼æ¨¡ç³ï¼ | |
| | | | bagCode | String | å¦ | è¢ç ï¼æ¨¡ç³ï¼ | |
| | | | clientId | Long | å¦ | ç»éåç¼å· | |
| | | | clientName | String | å¦ | ç»éååç§°ï¼æ¨¡ç³ï¼ | |
| | | | eventTime[] | LocalDateTime[] | å¦ | é¢è¦äº§çæ¶é´èå´ | |
| | | | pageNo / pageSize | - | å¦ | å页忰 | |
| | | |
| | | **GET /page ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | warningNo | String | é¢è¦ç¼å· | |
| | | | bagCode / palletCode | String | è¢ç / æçç | |
| | | | batchCode | String | æ¹æ¬¡å· | |
| | | | itemName | String | ç©æåç§° | |
| | | | salesCode | String | åºåºåå· | |
| | | | clientName | String | ç»éååç§° | |
| | | | dealerProvince / dealerCity | String | ç»éåç份 / åå¸ | |
| | | | scanProvince / scanCity | String | æ«ç ç份 / åå¸ | |
| | | | warningType / warningTypeName | String | é¢è¦ç±»å / ç±»ååç§° | |
| | | | status / statusName | Integer / String | ç¶æ / ç¶æåç§° | |
| | | | handleRemark | String | å¤ç夿³¨ | |
| | | | handleUserName | String | å¤ç人å§å | |
| | | | handleTime / eventTime | LocalDateTime | å¤çæ¶é´ / é¢è¦äº§çæ¶é´ | |
| | | |
| | | **PUT /handle 请æ±ä½ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | é¢è¦ç¼å· | |
| | | | status | Integer | æ¯ | å¤çç¶æï¼20 å·²å¤ç / 30 è¯¯æ¥ | |
| | | | handleRemark | String | å¦ | å¤ç夿³¨ | |
| | | |
| | | **GET /summary 请æ±åæ°ï¼** `startTime`ã`endTime`ï¼å¯éï¼LocalDateTimeï¼æ ¼å¼ `yyyy-MM-dd HH:mm:ss`ï¼ |
| | | |
| | | **GET /summary ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | total / pending / handled / falseAlarm | Long | æ»æ° / å¾
å¤ç / å·²å¤ç / è¯¯æ¥ | |
| | | | crossProvince / crossCity | Long | è·¨çé¢è¦æ° / è·¨å¸é¢è¦æ° | |
| | | |
| | | **GET /statistics 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | groupBy | String | æ¯ | åç»ç»´åº¦ï¼scanProvince / scanCity / clientId / itemId / batchId | |
| | | | startTime / endTime | LocalDateTime | å¦ | æ¶é´èå´ | |
| | | |
| | | **GET /statistics ååºå段ï¼** `name`ï¼åç»åï¼ã`count`ï¼æ°éï¼ï¼ææ°éååºã |
| | | |
| | | **GET /config ååºå段ï¼** `warningLevel`ï¼PROVINCE è·¨ç / CITY è·¨å¸ï¼ã`remark`ã |
| | | |
| | | **PUT /config 请æ±åæ°ï¼** `warningLevel`ï¼å¿
å¡«ï¼ä»
æ¯æ PROVINCE / CITYï¼ã |
| | | |
| | | ### 2. æ°æ®å®æ´æ§æ ¡éª |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | æé | |
| | | |------|------|------|------| |
| | | | GET | `/mes/trace-integrity/verify` | æ ¡éªæ°æ®å®æ´æ§ï¼éç®æè¦æ¯å¯¹ï¼ | `mes:trace-integrity:verify` | |
| | | | GET | `/mes/trace-integrity/page` | 宿´æ§è®°å½å页 | `mes:trace-integrity:query` | |
| | | |
| | | **GET /verify 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | bizType | String | æ¯ | ä¸å¡ç±»åï¼BATCH æ¹æ¬¡ / SALES éå®åºåº / SCAN æ¶è´¹è
æ«ç | |
| | | | bizId | Long | æ¯ | ä¸å¡è®°å½ç¼å· | |
| | | |
| | | **GET /verify ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | bizType / bizId / bizCode | - | ä¸å¡ç±»å / ç¼å· / ç¼ç | |
| | | | matched | Boolean | æ¯å¦æ ¡éªéè¿ | |
| | | | storedHash | String | åå¨çæè¦ SHA-256 | |
| | | | currentHash | String | å½åæ°æ®éç®æè¦ SHA-256 | |
| | | | bagSetHash | String | ä»
BATCH æ ¡éªè¿åï¼æ¹æ¬¡å½åè¢ç éåæè¦ï¼ä¾äººå·¥æ¯å¯¹è¢ç é²ç¯¡æ¹ï¼ | |
| | | | message | String | æ ¡éªè¯´æ | |
| | | |
| | | **GET /page 请æ±åæ°ï¼** `bizType`ã`bizId`ã`bizCode`ï¼åå¯éï¼ã |
| | | |
| | | **GET /page ååºå段ï¼** `id`ã`bizType`ã`bizId`ã`bizCode`ã`contentHash`ã`eventTime`ã |
| | | |
| | | ### 3. æ¶è´¹è
æº¯æº H5 ååºæ°å¢åºå |
| | | |
| | | æº¯æºæ¥å£ï¼`/public/mes/trace/query`ï¼ååºæ°å¢ `antiFake` åºåï¼ææ«ç è®°å½æè¿åï¼ï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | antiFake.scanCount | Long | 该追溯ç 累计æ«ç æ¬¡æ° | |
| | | | antiFake.firstScanTime | LocalDateTime | 馿¬¡æ«ç æ¶é´ | |
| | | | antiFake.lastScanTime | LocalDateTime | æè¿æ«ç æ¶é´ | |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | é¢è¦ç±»å | çªè´§é¢è¦å表ã详æ
| CROSS_PROVINCE=è·¨ç / CROSS_CITY=è·¨å¸ | |
| | | | ç¶æ | çªè´§é¢è¦å表ã详æ
| å¾
å¤ç / å·²å¤ç / 误æ¥ï¼å表建议æç¶æçè² | |
| | | | ç»éå vs æ«ç å°åº | çªè´§é¢è¦è¯¦æ
| 两å对æ¯å±ç¤ºï¼å¼å¸¸å段é«äº® | |
| | | | å¤ç夿³¨ | çªè´§é¢è¦è¯¦æ
| å¤çæ¶å¯å¡«å | |
| | | | matched | æ°æ®å®æ´æ§æ ¡éªé¡µ | æ ¡éªç»æï¼éè¿æ¾ç¤ºç»¿è²ãä¸éè¿æ¾ç¤ºçº¢è²å¹¶ç»åºä¸¤ä¸ªæè¦å¼ | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | å¤å®ç²åº¦ | 管ç端å¯å¨ `PROVINCE`ï¼è·¨çï¼ä¸ `CITY`ï¼è·¨å¸ï¼é´åæ¢ï¼`CITY` ç²åº¦ä¸è¥æ«ç å¸ä¸ç»éåå¸é½ä¸ºç©ºåéåççº§å¤æ | |
| | | | ä¸äº§çé¢è¦çæ
åµ | æ¹æ¬¡ç å
¥å£æ«ç ãæ«ç æ å°åºï¼IP è§£æå¤±è´¥ï¼ãè¢ç æªåºåºãç»éåæ å°åºä¿¡æ¯ | |
| | | | é¢è¦å¤ç | ä»
ãå¾
å¤çãç¶æå¯å¤ç为ãå·²å¤çãæã误æ¥ãï¼å¤çåä¸å¯å次å¤ç | |
| | | | 宿´æ§è¦çèå´ | åªæ ¡éªä¸å¯åæ ¸å¿åæ®µï¼æ£å¸¸ä¸å¡æµè½¬ï¼è稿âç产ä¸â宿ãåºåºåç¶ææ´æ°ï¼ä¸ä¼å¯¼è´æ ¡éªè¯¯æ¥ | |
| | | | æªçææè¦ | è¥ä¸å¡æ°æ®æªçæè¿æè¦ï¼å¦å岿°æ®ï¼ï¼`verify` è¿å"æ°æ®å®æ´æ§è®°å½ä¸åå¨"é误ç | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - çªè´§é¢è¦å页ç `eventTime` ä¼ ä¸¤ä¸ªå¼ï¼`eventTime[0]` èµ·ã`eventTime[1]` æ¢ï¼è¡¨ç¤ºæ¶é´èå´ã |
| | | - `statistics` ç `groupBy` åªè½ä¼ ç½ååå
ç维度ï¼ä¼ å
¶ä»å¼åç«¯ä¼æç»ã |
| | | - æ°æ®å®æ´æ§æ ¡éªçä¸å¡ç¼å·å»ºè®®ä»å¯¹åºå表带å
¥ï¼SCAN ç±»åç bizId 为æ¶è´¹è
æ«ç äºä»¶ä¸»é®ï¼å¯å¨æ«ç ç»è®¡è®°å½ä¸æ¥çã |
| | | - æ¶è´¹è
H5 端 `antiFake` åºåæ æ«ç è®°å½æ¶ä¸è¿åè¯¥åæ®µï¼å端éå空å¼å
åºã |
| | | - çªè´§é¢è¦ã宿´æ§æ ¡éªèåéå端æ§è¡èå SQL åæä¼å¨ç®¡çåå°å¯è§ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ¶è´¹è
æ«ç 访é®äºä»¶ä¸ç»è®¡ - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - H5 æ¶è´¹è
æº¯æºæ¥è¯¢é¡µï¼ä»
ç±å端èªå¨è®°å½è®¿é®äºä»¶ï¼ |
| | | - MES 管ç端æ¶è´¹è
æ«ç 访é®ç»è®¡é¡µ |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. æ¶è´¹è
è®¿é® H5 æº¯æºæ¥è¯¢æ¥å£æ¶ï¼å端使ç¨åå§æ«ç å
容解æè¿½æº¯ç ã |
| | | 2. æ¥è¯¢æååè®°å½å
¥å£ç±»ååçäº§æ¹æ¬¡ç¼å·ï¼æ¥è¯¢å¤±è´¥æ¶è®°å½å¤±è´¥äºä»¶å失败ç¼ç ã |
| | | 3. äºä»¶è®°å½å¤±è´¥ä¸ä¼å½±å H5 æº¯æºæ¥è¯¢ç»æã |
| | | 4. 管ç端ç»è®¡æ¥å£æè®¿é®æ¶é´çéï¼å叿¥å£ä»
æ¯æ `sourceType`ã`success`ã`channel`ã`province`ã`city`ã`batchId` å
个åºå®ç»´åº¦ï¼ä¸å
è®¸ä¼ å
¥ä»»ææ°æ®åºå段ã |
| | | 5. `channel` æ ¹æ® User-Agent å½ç±»ä¸ºå¾®ä¿¡ãæ¯ä»å®ææµè§å¨ï¼`province`ã`city` æ ¹æ®å®¢æ·ç«¯ IP å½å±å°è§£æï¼è§£æä¸å°æ¶å±ç¤ºâæªç¥âã |
| | | |
| | | ## API |
| | | |
| | | ### H5 æº¯æºæ¥è¯¢ï¼å·²ææ¥å£ï¼æ°å¢äºä»¶è®°å½ï¼ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |---|---|---| |
| | | | GET | `/public/mes/trace/query` | æ¥è¯¢è¿½æº¯ä¿¡æ¯ï¼åæ¶è®°å½æåæå¤±è´¥è®¿é®äºä»¶ | |
| | | |
| | | 请æ±åæ° `code` ä¸ºè¿½æº¯ç æå
å« `code` åæ°çäºç»´ç URLãäºä»¶è®°å½å¤±è´¥æ¶æ¥å£ä»æåæè§åè¿åæ¥è¯¢ç»ææä¸å¡é误ã |
| | | |
| | | ### 管çç«¯æ±æ» |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æé | |
| | | |---|---|---| |
| | | | GET | `/mes/trace-consumer-scan/summary` | `mes:trace-consumer-scan:query` | |
| | | |
| | | å¯éåæ°ï¼`startTime`ã`endTime`ï¼æ ¼å¼ä¸º `yyyy-MM-dd HH:mm:ss`ï¼ç»è®¡èå´ä¸ºå¼å§æ¶é´ï¼å«ï¼è³ç»ææ¶é´ï¼ä¸å«ï¼ã |
| | | |
| | | ååºå段ï¼`total` æ»è®¿é®æ¬¡æ°ã`success` æå次æ°ã`failed` 失败次æ°ã`uniqueBatchCount` æ¶é´èå´å
`batch_id` é空çå»éæ¹æ¬¡æ°éã该å£å¾ä¸ `batchId` åå¸ä¸çéâæªç¥âåç»æ°éä¸è´ã |
| | | |
| | | ### 管ç端è¶å¿ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æé | |
| | | |---|---|---| |
| | | | GET | `/mes/trace-consumer-scan/trend` | `mes:trace-consumer-scan:query` | |
| | | |
| | | å¯éåæ°åæ±æ»ãååºä¸ºæ°ç»ï¼å段å
æ¬ `date`ã`total`ã`success`ã`failed`ã |
| | | |
| | | ### 管ç端åå¸ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æé | |
| | | |---|---|---| |
| | | | GET | `/mes/trace-consumer-scan/distribution` | `mes:trace-consumer-scan:query` | |
| | | |
| | | 请æ±åæ°ï¼ |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | å
è®¸å¼ | 说æ | |
| | | |---|---|---|---|---| |
| | | | `groupBy` | String | æ¯ | `sourceType`ã`success`ã`channel`ã`province`ã`city`ã`batchId` | åç»ç»´åº¦ï¼å端éç¨åºå®ç½ååæ å°ï¼éæ³å¼æç» | |
| | | | `startTime` | DateTime | å¦ | `yyyy-MM-dd HH:mm:ss` | å¼å§æ¶é´ï¼å«ï¼ | |
| | | | `endTime` | DateTime | å¦ | `yyyy-MM-dd HH:mm:ss` | ç»ææ¶é´ï¼ä¸å«ï¼ | |
| | | |
| | | ååºæ°ç»å段ï¼`name` åç»åç§°ã`count` è®¿é®æ¬¡æ°ã空å¼åç»åç§°è¿åâæªç¥âã |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |---|---|---| |
| | | | `total`ã`success`ã`failed`ã`uniqueBatchCount` | æ±æ»å¡ç | å±ç¤ºè®¿é®æ»éãæå/失败éåéç©ºæ¹æ¬¡å»éæ°é | |
| | | | `date`ã`total`ã`success`ã`failed` | è¶å¿å¾ | åç«¯å·²è¡¥é½æ¥è¯¢åºé´å
æ æ°æ®æ¥æï¼æ°å¼ä¸º 0 | |
| | | | `name`ã`count` | åå¸å¾/è¡¨æ ¼ | æ ¹æ®éæ©çåºå®åç»ç»´åº¦å±ç¤º | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |---|---| |
| | | | æåæ¥è¯¢ | è®°å½ `success=1`ãå
¥å£ç±»åãæ¸ éãç份ãåå¸åæ¹æ¬¡ç¼å· | |
| | | | 追溯ç 为空æä¸åå¨ | è®°å½ `success=0` å失败ç¼ç | |
| | | | è®°å½äºä»¶å¼å¸¸ | ä»
è®°å½å端æ¥å¿ï¼ä¸æ¹åæº¯æºæ¥å£è¿åç»æ | |
| | | | IP ä¸ User-Agent | IP ä¸ User-Agent ä»
ä¿å SHA-256 æè¦ï¼æ¸ éãç份ãåå¸åªä¿åå½ç±»/å½å±å°åç§° | |
| | | | åç»åæ° | åç«¯åªæ¥ååºå®ç½ååå¼ï¼éæ³å¼è¿ååæ°é误 | |
| | | |
| | | ## åç«¯é¡µé¢æ¥å
¥ |
| | | |
| | | ### èåä¸è·¯ç± |
| | | |
| | | - èåå·²åå§åï¼`sql/mysql/mes_trace_consumer_scan_menu.sql`ï¼ï¼æå¨ MES ç³»ç»(5100) ä¸ï¼é¡µé¢è·¯å¾ `mes/trace-consumer-scan/index`ï¼ç»ä»¶å建议 `MesTraceConsumerScan`ã |
| | | - 页颿¥è¯¢æéæ è¯ï¼`mes:trace-consumer-scan:query`ãç»å½ç¨æ·é被æäºè¯¥æéæè½çå°èåå¹¶è°ç¨æ¥å£ã |
| | | - ç»è®¡æ¥å£ç»ä¸è·¯å¾åç¼ `/mes/trace-consumer-scan`ï¼å
±ä¸ä¸ªï¼`/summary`ã`/trend`ã`/distribution`ã |
| | | |
| | | ### 页é¢åºå建议 |
| | | |
| | | | åºå | æ°æ®æ¥æº | å±ç¤ºå»ºè®® | |
| | | |---|---|---| |
| | | | æ±æ»å¡ç | `/summary` | æ»è®¿é®ãæåã失败ãéç©ºæ¹æ¬¡å»éæ° | |
| | | | è¶å¿å¾ | `/trend` | ææ¥æçº¿/æ±ç¶ï¼å±ç¤ºæ»é䏿å/失败ï¼åç«¯å·²è¡¥é½æ æ°æ®æ¥æä¸º 0 | |
| | | | åå¸å¾ | `/distribution` | æ `sourceType/success/channel/province/city/batchId` 忢åç»ï¼å±ç¤ºåç§°ä¸è®¿é®é | |
| | | |
| | | ### æ¶é´èå´äº¤äº |
| | | |
| | | - ä¸ä¸ªæ¥å£åå¯éä¼ `startTime`/`endTime`ï¼æ ¼å¼ `yyyy-MM-dd HH:mm:ss`ï¼ï¼èå´å·¦éå³å¼ã |
| | | - 页颿ä¾é»è®¤æ¶é´èå´ï¼å¦è¿ 30 天ï¼ï¼ç±å端å¨è¯·æ±æ¶ä¼ å
¥ï¼ä¸ä¼ åå端æå·²ææ°æ®åºé´ç»è®¡ã |
| | | |
| | | ### ç©ºæ°æ®å¤ç |
| | | |
| | | - `trend` ç©ºæ°æ®æ¶è¿åè¡¥ 0 çæ¥æåºåï¼åç«¯ç´æ¥æ¸²æå³å¯ã |
| | | - `distribution` ç©ºæ°æ®æ¶è¿å空æ°ç»ï¼é¡µé¢å±ç¤º"ææ æ«ç æ°æ®"ã |
| | | - `province`/`city` è§£æä¸å°æ¶åç»å为"æªç¥"ã |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - éè¦å
æ§è¡ `sql/mysql/mes_trace_consumer_scan_event.sql` å建äºä»¶è¡¨ã |
| | | - éè¦æ§è¡ `sql/mysql/mes_trace_consumer_scan_menu.sql` åå§åèåæéï¼è¥ä½¿ç¨ `ruoyi-vue-pro` çå
¶å®åºï¼æ³¨æåæ¥è¡¨ç»æï¼ã |
| | | - 管ç端页é¢éè¦æ ¹æ®æéæ§å¶èå忥å£è®¿é®ã |
| | | - ç»è®¡æ¥å£çæ¶é´åæ°åºä½¿ç¨åç«¯çº¦å®æ ¼å¼ï¼ç»ææ¶é´ä¸å
å«è¾¹çæ¶å»ã |
| | | - ä¸éè¦æ°å¢å端ä¸ä¼ æäºä»¶åå
¥æ¥å£ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # 管çç«¯ãæ¥çäºç»´ç ãé¢è§ç»ä¸ä¸º H5 龿¥ - å端èè°æ¹æ¡ |
| | | |
| | | ## èæ¯ |
| | | |
| | | 管çç«¯æ¹æ¬¡/è¢ç /æç页é¢çãæ¥çäºç»´ç ãå¼¹çªï¼äºç»´ç é¢è§æ¤åç´æ¥ç¨**ç å¼ç¼å·**ï¼å¦ `PB-00000102`ã`BAG-00000102`ï¼çæï¼èæå°/å¯¼åºæ ç¾çäºç»´ç å
å®¹æ¯ **H5 溯æºé¾æ¥**ï¼`http://host:port/trace.html?code=xxx`ï¼ã两è
ä¸ä¸è´ï¼å¯¼è´æ«ç ææä¸åï¼æé æè¯¯è§£ã |
| | | |
| | | æ¬æ¬¡æ¹å¨è®©ç®¡çç«¯ãæ¥çäºç»´ç ãé¢è§/ä¸è½½çäºç»´ç å
容䏿尿 ç¾å®å
¨ä¸è´ï¼å为 H5 龿¥ï¼ã |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - çäº§æ¹æ¬¡ç®¡çï¼æ¹æ¬¡ãæ¥çäºç»´ç ãï¼ |
| | | - è¢ç 管çï¼è¢ç ãæ¥çäºç»´ç ãï¼ |
| | | - æç管çï¼æçãæ¥çäºç»´ç ãï¼ |
| | | |
| | | 以ä¸ä¸å¤å
±ç¨ç»ä»¶ `src/views/mes/pro/components/qr-trace-modal.vue`ï¼ä»
éæ¹å¨è¯¥å
±äº«ç»ä»¶ä¸æ¬¡ã |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | GET | /admin-api/mes/trace-config/qr-config | è·å¾æº¯æº H5 页é¢åºç¡å°åï¼äºç»´ç å
容åç¼ï¼ | |
| | | |
| | | **请æ±åæ°ï¼** æ ï¼ç»å½æå³å¯è®¿é®ï¼ |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "traceBaseUrl": "http://192.168.0.10:48080/trace.html" |
| | | }, |
| | | "msg": "" |
| | | } |
| | | ``` |
| | | |
| | | **åæ®µè¯´æï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | traceBaseUrl | String | æº¯æº H5 页é¢åºç¡å°åï¼å¯¹åºå端é
ç½® `mes.trace-base-url`ï¼ä¸ºç©ºæ¶å端äºç»´ç åé为纯ç å¼ | |
| | | |
| | | ## å端修æ¹ç¹ |
| | | |
| | | ### 1. æ°å¢ API 模å |
| | | |
| | | æ°å¢ `src/api/mes/trace/config.ts`ï¼å°è£
`getQrConfig()` è°ç¨ `/mes/trace-config/qr-config`ã |
| | | |
| | | ### 2. å
±äº«ç»ä»¶ qr-trace-modal.vue |
| | | |
| | | - å¼¹çªæå¼æ¶æå䏿¬¡ `traceBaseUrl`ï¼ä¼è¯å
ç¼åï¼é¿å
éå¤è¯·æ±ï¼ã |
| | | - äºç»´ç å±ç¤ºå
容ç±ãç å¼ç¼å·ãæ¹ä¸ºãH5 龿¥ãï¼`traceBaseUrl + "?code=" + encodeURIComponent(ç å¼)`ã |
| | | - ä¸è½½ / æå°äºç»´ç 使ç¨äºç»´ç å¾ç base64ï¼å
容éä¹å为 H5 龿¥ï¼ä¸å端 `QrCodeImageUtils` 导åºçæ ç¾ä¸è´ã |
| | | - è¾å
¥æ¡åæ¥å
¼å®¹ï¼æ«ææªæ«å°æå°æ ç¾ç宿´ H5 龿¥æ¶ï¼èªå¨ä»é¾æ¥ä¸æå `code` åæ°åè¿è¡è¿½æº¯æ¥è¯¢ï¼ç´æ¥è¾å
¥ç å¼ç¼å·åæ ·å¯ç¨ã |
| | | - `traceBaseUrl` ä¸ºç©ºï¼æªé
ç½® `mes.trace-base-url` çç¯å¢ï¼æ¶ï¼äºç»´ç åé为纯ç å¼ï¼è¡ä¸ºä¸å端ä¸è´ï¼ä¸æ¥éã |
| | | |
| | | ## æ°æ®å¸¦å
¥è¯´æ |
| | | |
| | | - ãæ¥çäºç»´ç ãå
¥å£æ²¿ç¨ç°æé»è¾ï¼æ¹æ¬¡ç¨ `code`ï¼æ£å¼æ¹æ¬¡å·ï¼ä¼å
ãå
¶æ¬¡ `tempCode`ï¼è¢ç /æçç åçãå¼¹çªå
å±ç¤ºç追溯信æ¯ä»æç å¼ç¼å·æ¥è¯¢ï¼äºç»´ç å±ç¤ºå
å®¹ææ¼æ¥ä¸º H5 龿¥ã |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | 1. å端é**éæ°ç¼è¯å¹¶éå¯**忿便°æ¥å£ `/mes/trace-config/qr-config`ï¼éå¯åå端è°ç¨è¿å 404ï¼`traceBaseUrl` 为空ï¼äºç»´ç 仿¾ç¤ºçº¯ç å¼ï¼é级å¯ç¨ï¼ä¸å½±ååæåæ¥åè½ï¼ã |
| | | 2. äºç»´ç å
容ç H5 龿¥ä¸ `mes.trace-base-url` é
置强ç¸å
³ï¼ä¸åç¯å¢ï¼local/test/ç产ï¼å°åä¸åï¼å端ä¸ç¡¬ç¼ç ã |
| | | 3. äºç»´ç é«å®¹é级å«ã尺寸çå±ç¤ºæ ·å¼ä¸åã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # åºåºæµé管ç - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - éå®åºåºåå表页 |
| | | - éå®åºåºåæ°å¢/ç¼è¾é¡µ |
| | | - éå®åºåºæ£è´§é¡µ |
| | | - éå®åºåºå详æ
页 |
| | | - æçç /è¢ç æµå追踪页ï¼å¯å¨éå®åºåºæç»åºåå®ç°ï¼ |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. CRM 客æ·ä½ä¸ºç»éåä¸»æ°æ®ï¼å建éå®åºåºåæ¶éè¿ `clientId` éæ©ç»éåã |
| | | 2. åè´§éç¥åçæéå®åºåºåæ¶ï¼æ²¿ç¨åè´§éç¥åç `clientId`ï¼ä½ä¸ºæ¬æ¬¡åºåºçç»éåã |
| | | 3. éå®åºåºåæäº¤åè¿å
¥å¾
æ£è´§ç¶æï¼æ«ææçç æè¢ç è°ç¨æ«ç æ¥å£ï¼ç³»ç»èªå¨è§£æç ç±»åãå®ä½çäº§æ¹æ¬¡ååæ ¼åºåã |
| | | 4. æ«ç æååçæéå®åºåºæç»ï¼è®°å½æçç æè¢ç ãæ¹æ¬¡ãä»åºãåºåºãåºä½åæ£è´§æ°éã |
| | | 5. æ§è¡æ£è´§åå ç¨åºåï¼æ§è¡æç»åºåºæ¶æ£ååºåå¹¶çæåºåäºå¡æµæ°´ã |
| | | 6. åºåºå®ææ¶åºåç»éååç§°ãå®é
åºåºæ¶é´åæ§è¡åºåºæä½äººï¼ä½ä¸ºå岿µåå¿«ç
§ã |
| | | 7. å端æ¥è¯¢éå®åºåºæç»æ¶ï¼éè¿æçç æè¢ç æç»å
³èéå®åºåºåï¼å³å¯åæ¥å·¥åãæ¹æ¬¡ãç»éååå®é
åºåºä¿¡æ¯ã |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/mes/wm/product-sales/create` | å建éå®åºåºå | |
| | | | PUT | `/mes/wm/product-sales/update` | ä¿®æ¹è稿éå®åºåºå | |
| | | | PUT | `/mes/wm/product-sales/submit` | æäº¤éå®åºåºå | |
| | | | POST | `/mes/wm/product-sales/scan` | æ«ææçç æè¢ç çææ£è´§æç» | |
| | | | PUT | `/mes/wm/product-sales/stock` | æ§è¡æ£è´§å¹¶å ç¨åºå | |
| | | | PUT | `/mes/wm/product-sales/shipping` | å¡«åæ¿è¿ååè¿è¾åå· | |
| | | | PUT | `/mes/wm/product-sales/finish` | æ§è¡æç»åºåºãæ£ååºåå¹¶åºåæµåä¿¡æ¯ | |
| | | | GET | `/mes/wm/product-sales/page` | å页æ¥è¯¢éå®åºåºåï¼æ¯æå®é
åºåºæ¶é´èå´çé | |
| | | | GET | `/mes/wm/product-sales/get` | æ¥è¯¢éå®åºåºå详æ
| |
| | | | GET | `/mes/wm/product-sales-detail/list-by-line` | æ¥è¯¢æå®éå®åºåºè¡çæç/è¢ç æç» | |
| | | | GET | `/mes/wm/transaction/page` | æ¥è¯¢åºåºåºåæµæ°´ | |
| | | |
| | | ### å建/ä¿®æ¹è¯·æ±åæ° |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | clientId | Long | æ¯ | CRM 客æ·ç¼å·ï¼åæ¶ä½ä¸ºç»éåç¼å· | |
| | | | salesDate | DateTime | æ¯ | 计åæä¸å¡åºåºæ¥æ | |
| | | | noticeId | Long | å¦ | æ¥æºåè´§éç¥åç¼å· | |
| | | | contactName | String | å¦ | æ¶è´§äºº | |
| | | | contactTelephone | String | å¦ | èç³»çµè¯ | |
| | | | contactAddress | String | å¦ | æ¶è´§å°å | |
| | | | carrier | String | å¦ | æ¿è¿å | |
| | | | shippingNumber | String | å¦ | è¿è¾åå· | |
| | | |
| | | ### æ«ç 请æ±åæ° |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | salesId | Long | æ¯ | éå®åºåºåç¼å· | |
| | | | scanContent | String | æ¯ | æçç ãè¢ç æå¯è§£æç追溯ç | |
| | | | quantity | Decimal | å¦ | æ¬æ¬¡æ£è´§æ°éï¼ä¸ä¼ æ¶æéå®åºåºè¡æ°éå¤ç | |
| | | |
| | | ### æ«ç ååºéç¹å段 |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | scanType | String | `PALLET` æçç ï¼`BAG` è¢ç | |
| | | | scanContent | String | åå§æ«æå
容 | |
| | | | itemId | Long | ç©æç¼å· | |
| | | | batchId | Long | ä»åºæ¹æ¬¡ç¼å· | |
| | | | batchCode | String | ä»åºæ¹æ¬¡å· | |
| | | | materialStockId | Long | åºåè®°å½ç¼å· | |
| | | | warehouseId | Long | ä»åºç¼å· | |
| | | | locationId | Long | åºåºç¼å· | |
| | | | areaId | Long | åºä½ç¼å· | |
| | | | scanQuantity | Decimal | æ¬æ¬¡æ«ææ°é | |
| | | | alreadyScanned | Boolean | æ¯å¦å·²æ«æè¿ | |
| | | | detailId | Long | éå®åºåºæç»ç¼å· | |
| | | |
| | | ### éå®åºåºåååºæ°å¢å段 |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | clientName | String | ç»éååç§°å¿«ç
§ï¼åºåºå®ææ¶åºå | |
| | | | outboundTime | DateTime | å®é
æ§è¡åºåºæ¶é´ï¼æªå®ææ¶ä¸ºç©º | |
| | | | outboundUserId | Long | æ§è¡æç»åºåºæä½äººçç¨æ·ç¼å·ï¼æªå®ææ¶ä¸ºç©º | |
| | | | outboundUserName | String | æ§è¡æç»åºåºæä½äººçåç§°ï¼æªå®ææç¨æ·å·²å 餿¶ä¸ºç©º | |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | clientName | åºåºåå表ã详æ
ãæµå追踪 | å±ç¤ºåºåºå®ææ¶åºåçç»éååç§°ï¼è稿å¯å±ç¤º CRM å½ååç§° | |
| | | | outboundTime | åºåºåå表ã详æ
| ä»
已宿忮å±ç¤ºå®é
åºåºæ¶é´ | |
| | | | outboundUserId | åºåºå详æ
| æä½äººç¨æ·ç¼å· | |
| | | | outboundUserName | åºåºåå表ã详æ
ãæµå追踪 | å端已éè¿ç³»ç»ç¨æ·æ¥å£è½¬æ¢ä¸ºæä½äººåç§°ï¼åç«¯ç´æ¥å±ç¤º | |
| | | | scanType | æ«ç ç»æãåºåºæç» | åºåæçç åè¢ç | |
| | | | scanContent | æ«ç ç»æãåºåºæç» | å±ç¤ºå®é
æ«æç追溯ç | |
| | | | batchCode | åºåºæç»ãæµå追踪 | å±ç¤ºçäº§æ¹æ¬¡å· | |
| | | | warehouseName | åºåºæç»ãæµå追踪 | å±ç¤ºåºåºä»åº | |
| | | | quantity | åºåºæç»ãåºåæµæ°´ | å±ç¤ºæ£è´§æ°éï¼æµæ°´åºåºæ°éä¸ºè´æ° | |
| | | | bizCode | åºåæµæ°´ | å
³èéå®åºåºåå· | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | ç»éåæ¥æº | `clientId` å¤ç¨ CRM 客æ·ç¼å·ï¼ä¸æ°å»ºéå¤çç»éåä¸»æ°æ® | |
| | | | æçç æ«æ | æçå¿
须已ç»å®çäº§æ¹æ¬¡ï¼å¦åä¸å
许éå®åºåº | |
| | | | è¢ç æ«æ | è¢ç å¿
é¡»è½å®ä½çäº§æ¹æ¬¡ååæ ¼åºå | |
| | | | é夿«æ | è¿å `alreadyScanned=true`ï¼å端æç¤ºå·²æ«æï¼ä¸éå¤çææç» | |
| | | | åºåä¸è¶³ | æ«ç ææ£è´§æ ¡éªå¤±è´¥æ¶ï¼ä¸çæåºåºæç»æåºåå ç¨ | |
| | | | æ£è´§ | æ£è´§é¶æ®µåªå ç¨åºåï¼ä¸åå°å®é
å¨åºæ°é | |
| | | | æç»åºåº | ä»
å¾
åºåºç¶æå
许æ§è¡ï¼æå忣ååºåå¹¶åå
¥åºåæµæ°´ | |
| | | | åºåºå¿«ç
§ | æç»åºåºæ¶ä¿åç»éååç§°ãå®é
åºåºæ¶é´åæä½äººç¼å·ï¼åç» CRM æ¹åä¸å½±åå岿µå | |
| | | | æµå追踪 | éè¿éå®åºåºæç»ä¸ç `palletCodeId` æ `bagCodeId` 忥åºåºåãæ¹æ¬¡ãä»åºåç»éå | |
| | | | åæ¶åæ® | æªå®æåæ®å¯åæ¶å¹¶éæ¾åºåå ç¨ï¼å·²å®æåæ®ä¸å¯åæ¶ | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - é¨ç½²åéæ§è¡ `sql/mysql/mes_wm_sales_outbound_traceability.sql`ï¼ä¸ºéå®åºåºä¸»è¡¨å¢å æµåå¿«ç
§å段ï¼èæ¬åºå¨ç¡®è®¤ç®æ æ°æ®åºåéæ¡æ§è¡ã |
| | | - 该è¿ç§»èæ¬ä¸å
å« `tenant_id` åæ®µï¼ç¬¦åå½åé¡¹ç®æ°æ®åºè§èã |
| | | - å½å代ç è®°å½çæ¯æä½äººç¨æ·ç¼å·ï¼å端åºéè¿ç³»ç»ç¨æ·ä¿¡æ¯æ¥å£æ¾ç¤ºæä½äººåç§°ã |
| | | - `/mes/wm/product-sales/finish` æ¯å®é
æ£åºåå产çåºåæµæ°´çæ¶ç¹ï¼æ«ç æåä¸ä»£è¡¨å·²ç»å®æåºåºã |
| | | - æ¥è¯¢æçç æè¢ç æµåæ¶ï¼åºä¼å
使ç¨åºåºæç»ä¸çç ç¼å·åéå®åºåºåç¶æè¿æ»¤ï¼é¿å
å°å·²åæ¶åæ®ä½ä¸ºæææµåã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- MES ç»éå管çèååå§å |
| | | -- ç»éåå¤ç¨ CRM 客æ·ä¸»æ°æ®ï¼ä¸æ°å¢ä¸å¡è¡¨åå端æéã |
| | | -- æ§è¡å请确认å½åæ°æ®åºä¸ºé¡¹ç®å®é
ä¸å¡åºã |
| | | |
| | | SET @dealer_menu_id = (SELECT id FROM system_menu |
| | | WHERE parent_id = 5780 AND path = 'dealer' AND deleted = b'0' |
| | | ORDER BY id LIMIT 1); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6740, 'ç»éå管ç', '', 2, 11, 5780, 'dealer', 'ep:user', 'crm/customer/index', 'MesWmDealer', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE @dealer_menu_id IS NULL |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6740); |
| | | |
| | | SET @dealer_menu_id = COALESCE(@dealer_menu_id, (SELECT id FROM system_menu WHERE id = 6740 AND deleted = b'0')); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6741, 'æ¥è¯¢ç»éå', 'crm:customer:query', 3, 1, @dealer_menu_id, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = @dealer_menu_id AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6741); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6742, 'å建ç»éå', 'crm:customer:create', 3, 2, @dealer_menu_id, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = @dealer_menu_id AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6742); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6743, 'æ´æ°ç»éå', 'crm:customer:update', 3, 3, @dealer_menu_id, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = @dealer_menu_id AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6743); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6744, 'å é¤ç»éå', 'crm:customer:delete', 3, 4, @dealer_menu_id, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = @dealer_menu_id AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6744); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6745, '导åºç»éå', 'crm:customer:export', 3, 5, @dealer_menu_id, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = @dealer_menu_id AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6745); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6746, '导å
¥ç»éå', 'crm:customer:import', 3, 6, @dealer_menu_id, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = @dealer_menu_id AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6746); |
| | | |
| | | SET @dealer_menu_id = NULL; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- é²ä¼ªé²çªè´§ç®¡çï¼çªè´§é¢è¦ + æ°æ®å®æ´æ§æ ¡éª å»ºè¡¨èæ¬ï¼ä¸ä½¿ç¨ tenant_idï¼ |
| | | -- æ§è¡åè¯·ç¡®è®¤ç®æ æ°æ®åºä¸ºé¡¹ç®å®é
ä¸å¡åºï¼å¹¶éæ¡æ§è¡ã |
| | | |
| | | -- 1. é²çªè´§è§åé
置表ï¼key-value 飿 ¼ï¼é¢ç½®å¤å®ç²åº¦é
ç½®ï¼ |
| | | CREATE TABLE IF NOT EXISTS mes_trace_anti_config ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | config_key VARCHAR(64) NOT NULL COMMENT 'é
置项Key', |
| | | config_value VARCHAR(255) NOT NULL COMMENT 'é
ç½®å¼', |
| | | remark VARCHAR(255) DEFAULT NULL COMMENT 'é
置说æ', |
| | | create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | creator VARCHAR(64) DEFAULT NULL COMMENT 'å建è
', |
| | | updater VARCHAR(64) DEFAULT NULL COMMENT 'æ´æ°è
', |
| | | deleted BIT(1) NOT NULL DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (id), |
| | | KEY idx_anti_config_key (config_key) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='é²çªè´§è§åé
ç½®'; |
| | | |
| | | -- é¢ç½®å¤å®ç²åº¦ï¼PROVINCE è·¨ç / CITY è·¨å¸ |
| | | INSERT INTO mes_trace_anti_config (config_key, config_value, remark) |
| | | SELECT 'warning_level', 'PROVINCE', 'çªè´§å¤å®ç²åº¦ï¼PROVINCE è·¨ç / CITY è·¨å¸' |
| | | WHERE NOT EXISTS (SELECT 1 FROM mes_trace_anti_config WHERE config_key = 'warning_level' AND deleted = b'0'); |
| | | |
| | | -- 2. çªè´§é¢è¦è®°å½è¡¨ |
| | | CREATE TABLE IF NOT EXISTS mes_trace_cross_region_warning ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | warning_no VARCHAR(64) DEFAULT NULL COMMENT 'é¢è¦ç¼å·', |
| | | bag_code_id BIGINT DEFAULT NULL COMMENT 'è¢ç ç¼å·', |
| | | bag_code VARCHAR(64) DEFAULT NULL COMMENT 'è¢ç ', |
| | | pallet_code_id BIGINT DEFAULT NULL COMMENT 'æçç ç¼å·', |
| | | pallet_code VARCHAR(64) DEFAULT NULL COMMENT 'æçç ', |
| | | batch_id BIGINT DEFAULT NULL COMMENT 'çäº§æ¹æ¬¡ç¼å·', |
| | | batch_code VARCHAR(64) DEFAULT NULL COMMENT 'æ¹æ¬¡å·', |
| | | item_id BIGINT DEFAULT NULL COMMENT 'ç©æç¼å·', |
| | | item_code VARCHAR(64) DEFAULT NULL COMMENT 'ç©æç¼ç ', |
| | | item_name VARCHAR(128) DEFAULT NULL COMMENT 'ç©æåç§°', |
| | | sales_id BIGINT DEFAULT NULL COMMENT 'éå®åºåºåç¼å·', |
| | | sales_code VARCHAR(64) DEFAULT NULL COMMENT 'åºåºåå·', |
| | | client_id BIGINT DEFAULT NULL COMMENT 'ç»éåç¼å·', |
| | | client_name VARCHAR(128) DEFAULT NULL COMMENT 'ç»éååç§°', |
| | | dealer_province VARCHAR(64) DEFAULT NULL COMMENT 'ç»éåç份', |
| | | dealer_city VARCHAR(64) DEFAULT NULL COMMENT 'ç»éååå¸', |
| | | scan_province VARCHAR(64) DEFAULT NULL COMMENT 'æ«ç ç份', |
| | | scan_city VARCHAR(64) DEFAULT NULL COMMENT 'æ«ç åå¸', |
| | | warning_type VARCHAR(32) DEFAULT NULL COMMENT 'é¢è¦ç±»åï¼CROSS_PROVINCE / CROSS_CITY', |
| | | scan_event_id BIGINT DEFAULT NULL COMMENT 'å
³èæ¶è´¹è
æ«ç äºä»¶ç¼å·', |
| | | status TINYINT NOT NULL DEFAULT 10 COMMENT 'ç¶æï¼10 å¾
å¤ç / 20 å·²å¤ç / 30 误æ¥', |
| | | handle_user_id BIGINT DEFAULT NULL COMMENT 'å¤ç人ç¼å·', |
| | | handle_time DATETIME DEFAULT NULL COMMENT 'å¤çæ¶é´', |
| | | handle_remark VARCHAR(255) DEFAULT NULL COMMENT 'å¤ç夿³¨', |
| | | event_time DATETIME NOT NULL COMMENT 'é¢è¦äº§çæ¶é´', |
| | | create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | creator VARCHAR(64) DEFAULT NULL COMMENT 'å建è
', |
| | | updater VARCHAR(64) DEFAULT NULL COMMENT 'æ´æ°è
', |
| | | deleted BIT(1) NOT NULL DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (id), |
| | | KEY idx_cross_region_status (status), |
| | | KEY idx_cross_region_batch (batch_id), |
| | | KEY idx_cross_region_bag (bag_code_id), |
| | | KEY idx_cross_region_sales (sales_id), |
| | | KEY idx_cross_region_event_time (event_time), |
| | | KEY idx_cross_region_scan_province (scan_province) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='çªè´§é¢è¦è®°å½'; |
| | | |
| | | -- 3. æ°æ®å®æ´æ§æ ¡éªè®°å½è¡¨ |
| | | CREATE TABLE IF NOT EXISTS mes_trace_integrity_record ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | biz_type VARCHAR(32) NOT NULL COMMENT 'ä¸å¡ç±»åï¼BATCH æ¹æ¬¡ / SALES éå®åºåº / SCAN æ¶è´¹è
æ«ç ', |
| | | biz_id BIGINT NOT NULL COMMENT 'ä¸å¡è®°å½ç¼å·', |
| | | biz_code VARCHAR(64) DEFAULT NULL COMMENT 'ä¸å¡ç¼ç ï¼æ¹æ¬¡å·/åºåºåå·/追溯ç ï¼', |
| | | content_hash CHAR(64) NOT NULL COMMENT 'å
容æè¦ SHA-256', |
| | | event_time DATETIME NOT NULL COMMENT 'æè¦çææ¶é´', |
| | | create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | creator VARCHAR(64) DEFAULT NULL COMMENT 'å建è
', |
| | | updater VARCHAR(64) DEFAULT NULL COMMENT 'æ´æ°è
', |
| | | deleted BIT(1) NOT NULL DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_integrity_biz (biz_type, biz_id), |
| | | KEY idx_integrity_event_time (event_time) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æ°æ®å®æ´æ§æ ¡éªè®°å½'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- é²ä¼ªé²çªè´§ç®¡çèå䏿鮿éåå§å |
| | | -- ç¬ç«ä¸çº§èåï¼parent_id=0ï¼type=1ï¼ï¼éç¨ WHERE NOT EXISTS å¹çæå
¥ã |
| | | -- æ§è¡åè¯·ç¡®è®¤ç®æ æ°æ®åºä¸ºé¡¹ç®å®é
ä¸å¡åºï¼å¹¶éæ¡æ§è¡ã |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6070, 'é²ä¼ªé²çªè´§ç®¡ç', '', 1, 21, 0, 'trace-anti', 'ep:warning', '', '', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6070 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6071, 'çªè´§é¢è¦', '', 2, 1, 6070, 'cross-region', 'ep:location-danger', 'mes/trace/crossregion/index', 'MesTraceCrossRegion', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6071 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6072, 'çªè´§é¢è¦æ¥è¯¢', 'mes:trace-cross-region:query', 3, 1, 6071, '', '', '', '', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6072 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6073, 'çªè´§é¢è¦å¤ç', 'mes:trace-cross-region:handle', 3, 2, 6071, '', '', '', '', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6073 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6074, 'æ«ç å°çåå¸', '', 2, 2, 6070, 'scan-distribution', 'ep:map-location', 'mes/trace-consumer-scan/index', 'MesConsumerScanDistribution', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6074 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6075, 'æ«ç å叿¥è¯¢', 'mes:trace-consumer-scan:query', 3, 1, 6074, '', '', '', '', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6075 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6076, 'æ°æ®å®æ´æ§æ ¡éª', '', 2, 3, 6070, 'integrity', 'ep:lock', 'mes/trace/integrity/index', 'MesTraceIntegrity', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6076 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6077, '宿´æ§è®°å½æ¥è¯¢', 'mes:trace-integrity:query', 3, 1, 6076, '', '', '', '', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6077 AND deleted = b'0'); |
| | | |
| | | INSERT INTO system_menu |
| | | (id, name, permission, type, sort, parent_id, path, icon, component, component_name, |
| | | status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 6078, 'æ°æ®å®æ´æ§æ ¡éª', 'mes:trace-integrity:verify', 3, 2, 6076, '', '', '', '', |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6078 AND deleted = b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- æ¶è´¹è
æ«ç 访é®äºä»¶è¡¨ï¼ä¸ä½¿ç¨ tenant_idï¼ |
| | | CREATE TABLE IF NOT EXISTS mes_trace_consumer_scan_event ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | trace_code VARCHAR(255) NOT NULL COMMENT 'æ«ç åå§å
容ï¼å¿
è¦æ¶æªæï¼', |
| | | normalized_code VARCHAR(128) DEFAULT NULL COMMENT 'è§£æåç追溯ç ', |
| | | source_type VARCHAR(32) DEFAULT NULL COMMENT 'å
¥å£ç±»åï¼BAG/PALLET/BATCH', |
| | | channel VARCHAR(32) DEFAULT NULL COMMENT 'æ«ç å
¥å£æ¸ éï¼å¾®ä¿¡/æ¯ä»å®/æµè§å¨', |
| | | province VARCHAR(64) DEFAULT NULL COMMENT 'ç份ï¼IP å½å±å°ï¼', |
| | | city VARCHAR(64) DEFAULT NULL COMMENT 'åå¸ï¼IP å½å±å°ï¼', |
| | | batch_id BIGINT DEFAULT NULL COMMENT 'çäº§æ¹æ¬¡ç¼å·', |
| | | success TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'æ¯å¦æ¥è¯¢æå', |
| | | error_code VARCHAR(64) DEFAULT NULL COMMENT '失败ç¼ç ', |
| | | ip_hash CHAR(64) DEFAULT NULL COMMENT '客æ·ç«¯ IP SHA-256 æè¦', |
| | | user_agent_hash CHAR(64) DEFAULT NULL COMMENT 'User-Agent SHA-256 æè¦', |
| | | event_time DATETIME NOT NULL COMMENT 'è®¿é®æ¶é´', |
| | | create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | creator VARCHAR(64) DEFAULT NULL COMMENT 'å建è
', |
| | | updater VARCHAR(64) DEFAULT NULL COMMENT 'æ´æ°è
', |
| | | deleted BIT(1) NOT NULL DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (id), |
| | | KEY idx_trace_consumer_event_time (event_time), |
| | | KEY idx_trace_consumer_batch_id (batch_id), |
| | | KEY idx_trace_consumer_success (success), |
| | | KEY idx_trace_consumer_source_type (source_type), |
| | | KEY idx_trace_consumer_channel (channel), |
| | | KEY idx_trace_consumer_province (province) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æ¶è´¹è
æ«ç 访é®äºä»¶'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- MES æ¶è´¹è
æ«ç 访é®ç»è®¡ èååå§å |
| | | -- æ°å»ºé¡¶çº§ç®å½ã溯æºåæãï¼å¨å
¶ä¸æãæ¶è´¹è
æ«ç ç»è®¡ãé¡µé¢ |
| | | -- 页é¢è·¯ç± /trace/consumer-scan/indexï¼æéæ è¯ mes:trace-consumer-scan:query |
| | | -- 管ç端æ¥å£è·¯å¾ /mes/trace-consumer-scan/* |
| | | -- ç®æ åºï¼mom-thzyï¼application-local.yaml é»è®¤åºï¼ |
| | | -- ============================================= |
| | | |
| | | -- 顶级ç®å½ï¼æº¯æºåæï¼type=1 ç®å½ï¼parent_id=0ï¼path=/traceï¼ |
| | | INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES (602634, '溯æºåæ', '', 1, 24, 0, '/trace', 'ep:data-analysis', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'); |
| | | |
| | | -- æ¶è´¹è
æ«ç ç»è®¡ï¼äºçº§é¡µé¢ï¼æå¨ã溯æºåæãç®å½ä¸ï¼path ä¸å«ç¶çº§åç¼ï¼ |
| | | INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES (602632, 'æ¶è´¹è
æ«ç ç»è®¡', '', 2, 1, 602634, 'consumer-scan/index', 'ep:data-line', 'mes/trace-consumer-scan/index', 'MesConsumerScanStatistics', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'); |
| | | |
| | | -- æ¶è´¹è
æ«ç ç»è®¡ - æé®æé |
| | | INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES |
| | | (602633, 'æ¶è´¹è
æ«ç ç»è®¡æ¥è¯¢', 'mes:trace-consumer-scan:query', 3, 1, 602632, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'); |
| | |
| | | -- åºåºæµé管çï¼éå®åºåºä¸è¢ç /æçç å
³èï¼æ ç§æ·åæ®µï¼ |
| | | -- æ§è¡åè¯·ç¡®è®¤ç®æ æ°æ®åºä¸º mom-thzyï¼å¹¶éæ¡æ§è¡ã |
| | | |
| | | ALTER TABLE mes_wm_product_sales |
| | | ADD COLUMN client_name varchar(255) DEFAULT NULL COMMENT 'ç»éååç§°å¿«ç
§' AFTER client_id; |
| | | ALTER TABLE mes_wm_product_sales |
| | | ADD COLUMN outbound_time datetime DEFAULT NULL COMMENT 'å®é
åºåºæ¶é´' AFTER sales_date; |
| | | ALTER TABLE mes_wm_product_sales |
| | | ADD COLUMN outbound_user_id bigint DEFAULT NULL COMMENT 'æ§è¡åºåºæä½äººID' AFTER outbound_time; |
| | | ALTER TABLE mes_wm_product_sales |
| | | ADD INDEX idx_product_sales_outbound_time (outbound_time); |
| | | |
| | | ALTER TABLE mes_wm_product_sales_detail |
| | | ADD COLUMN bag_code_id bigint DEFAULT NULL COMMENT 'è¢ç ç¼å·' AFTER area_id; |
| | | ALTER TABLE mes_wm_product_sales_detail |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.vo.MesTraceQrConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.framework.config.MesProperties; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | /** |
| | | * 管çåå° - MES 溯æºé
ç½® |
| | | * <p> |
| | | * å管ç端å端æ´é²æº¯æº H5 页é¢åºç¡å°åï¼ç¨äº"æ¥çäºç»´ç "é¢è§/ä¸è½½ä¸æå°æ ç¾ä¿æä¸è´çäºç»´ç å
容ã |
| | | */ |
| | | @Tag(name = "管çåå° - MES 溯æºé
ç½®") |
| | | @RestController |
| | | @RequestMapping("/mes/trace-config") |
| | | @Validated |
| | | public class MesTraceConfigController { |
| | | |
| | | @Resource |
| | | private MesProperties mesProperties; |
| | | |
| | | @GetMapping("/qr-config") |
| | | @Operation(summary = "è·å¾æº¯æº H5 页é¢åºç¡å°åï¼äºç»´ç å
容åç¼ï¼") |
| | | public CommonResult<MesTraceQrConfigRespVO> getQrConfig() { |
| | | MesTraceQrConfigRespVO respVO = new MesTraceQrConfigRespVO(); |
| | | respVO.setTraceBaseUrl(mesProperties.getTraceBaseUrl()); |
| | | return success(respVO); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO; |
| | | import cn.iocoder.yudao.module.mes.service.trace.consumer.MesTraceConsumerScanStatisticsService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Tag(name = "管çåå° - MES æ¶è´¹è
æ«ç 访é®ç»è®¡") |
| | | @RestController |
| | | @RequestMapping("/mes/trace-consumer-scan") |
| | | @Validated |
| | | public class MesTraceConsumerScanController { |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanStatisticsService statisticsService; |
| | | |
| | | @GetMapping("/summary") |
| | | @Operation(summary = "æ¶è´¹è
æ«ç è®¿é®æ±æ»") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-consumer-scan:query')") |
| | | public CommonResult<MesTraceConsumerScanSummaryRespVO> getSummary( |
| | | @RequestParam(value = "startTime", required = false) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime startTime, |
| | | @RequestParam(value = "endTime", required = false) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime endTime) { |
| | | return success(statisticsService.getSummary(startTime, endTime)); |
| | | } |
| | | |
| | | @GetMapping("/trend") |
| | | @Operation(summary = "æ¶è´¹è
æ«ç 访é®è¶å¿") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-consumer-scan:query')") |
| | | public CommonResult<List<MesTraceConsumerScanTrendRespVO>> getTrend( |
| | | @RequestParam(value = "startTime", required = false) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime startTime, |
| | | @RequestParam(value = "endTime", required = false) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime endTime) { |
| | | return success(statisticsService.getTrend(startTime, endTime)); |
| | | } |
| | | |
| | | @GetMapping("/distribution") |
| | | @Operation(summary = "æ¶è´¹è
æ«ç 访é®åå¸") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-consumer-scan:query')") |
| | | public CommonResult<List<MesTraceConsumerScanDistributionRespVO>> getDistribution( |
| | | @Valid MesTraceConsumerScanDistributionReqVO reqVO) { |
| | | return success(statisticsService.getDistribution(reqVO)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Data |
| | | @Schema(description = "æ¶è´¹è
æ«ç 访é®å叿¥è¯¢åæ°") |
| | | public class MesTraceConsumerScanDistributionReqVO { |
| | | |
| | | @Schema(description = "åç»ç»´åº¦ï¼sourceType/success/channel/province/city/batchId", example = "sourceType") |
| | | @NotBlank(message = "åç»ç»´åº¦ä¸è½ä¸ºç©º") |
| | | private String groupBy; |
| | | |
| | | @Schema(description = "å¼å§æ¶é´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime startTime; |
| | | |
| | | @Schema(description = "ç»ææ¶é´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime endTime; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(description = "æ¶è´¹è
æ«ç 访é®åå¸") |
| | | public class MesTraceConsumerScanDistributionRespVO { |
| | | private String name; |
| | | private Long count; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(description = "æ¶è´¹è
æ«ç è®¿é®æ±æ»") |
| | | public class MesTraceConsumerScanSummaryRespVO { |
| | | private Long total; |
| | | private Long success; |
| | | private Long failed; |
| | | private Long uniqueBatchCount; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(description = "æ¶è´¹è
æ«ç 访é®è¶å¿") |
| | | public class MesTraceConsumerScanTrendRespVO { |
| | | private String date; |
| | | private Long total; |
| | | private Long success; |
| | | private Long failed; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceAntiConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionHandleReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningRespVO; |
| | | import cn.iocoder.yudao.module.mes.service.trace.crossregion.MesTraceCrossRegionService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PutMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Tag(name = "管çåå° - MES çªè´§é¢è¦") |
| | | @RestController |
| | | @RequestMapping("/mes/trace-cross-region") |
| | | @Validated |
| | | public class MesTraceCrossRegionController { |
| | | |
| | | @Resource |
| | | private MesTraceCrossRegionService crossRegionService; |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾çªè´§é¢è¦å页") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-cross-region:query')") |
| | | public CommonResult<PageResult<MesTraceCrossRegionWarningRespVO>> getWarningPage(@Valid MesTraceCrossRegionWarningPageReqVO reqVO) { |
| | | return success(crossRegionService.getWarningPage(reqVO)); |
| | | } |
| | | |
| | | @PutMapping("/handle") |
| | | @Operation(summary = "å¤ççªè´§é¢è¦") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-cross-region:handle')") |
| | | public CommonResult<Boolean> handleWarning(@Valid @RequestBody MesTraceCrossRegionHandleReqVO reqVO) { |
| | | crossRegionService.handleWarning(reqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/summary") |
| | | @Operation(summary = "è·å¾çªè´§é¢è¦æ±æ»") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-cross-region:query')") |
| | | public CommonResult<MesTraceCrossRegionSummaryRespVO> getWarningSummary( |
| | | @RequestParam(value = "startTime", required = false) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime startTime, |
| | | @RequestParam(value = "endTime", required = false) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime endTime) { |
| | | return success(crossRegionService.getWarningSummary(startTime, endTime)); |
| | | } |
| | | |
| | | @GetMapping("/statistics") |
| | | @Operation(summary = "è·å¾çªè´§é¢è¦åå¸ç»è®¡") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-cross-region:query')") |
| | | public CommonResult<List<MesTraceCrossRegionStatisticsRespVO>> getWarningStatistics(@Valid MesTraceCrossRegionStatisticsReqVO reqVO) { |
| | | return success(crossRegionService.getWarningStatistics(reqVO)); |
| | | } |
| | | |
| | | @GetMapping("/config") |
| | | @Operation(summary = "è·å¾é²çªè´§è§åé
ç½®") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-cross-region:query')") |
| | | public CommonResult<MesTraceAntiConfigRespVO> getConfig() { |
| | | return success(crossRegionService.getConfig()); |
| | | } |
| | | |
| | | @PutMapping("/config") |
| | | @Operation(summary = "æ´æ°é²çªè´§è§åé
ç½®") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-cross-region:handle')") |
| | | public CommonResult<Boolean> updateConfig(@RequestParam("warningLevel") @Parameter(description = "å¤å®ç²åº¦ï¼PROVINCE è·¨ç / CITY è·¨å¸", example = "PROVINCE") String warningLevel) { |
| | | crossRegionService.updateConfig(warningLevel); |
| | | return success(true); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - é²çªè´§è§åé
ç½® Response VO") |
| | | @Data |
| | | public class MesTraceAntiConfigRespVO { |
| | | |
| | | @Schema(description = "å¤å®ç²åº¦ï¼PROVINCE è·¨ç / CITY è·¨å¸", example = "PROVINCE") |
| | | private String warningLevel; |
| | | |
| | | @Schema(description = "é
置说æ", example = "çªè´§å¤å®ç²åº¦ï¼PROVINCE è·¨ç / CITY è·¨å¸") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - çªè´§é¢è¦å¤ç Request VO") |
| | | @Data |
| | | public class MesTraceCrossRegionHandleReqVO { |
| | | |
| | | @Schema(description = "é¢è¦ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @NotNull(message = "é¢è¦ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å¤çç¶æï¼20 å·²å¤ç / 30 误æ¥", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") |
| | | @NotNull(message = "å¤çç¶æä¸è½ä¸ºç©º") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å¤ç夿³¨", example = "ç¡®è®¤ä¸ºç©æµä¸è½¬ï¼éçªè´§") |
| | | private String handleRemark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Schema(description = "管çåå° - çªè´§é¢è¦åå¸ç»è®¡ Request VO") |
| | | @Data |
| | | public class MesTraceCrossRegionStatisticsReqVO { |
| | | |
| | | @Schema(description = "åç»ç»´åº¦ï¼scanProvince/scanCity/clientId/itemId/batchId", example = "scanProvince") |
| | | @NotBlank(message = "åç»ç»´åº¦ä¸è½ä¸ºç©º") |
| | | private String groupBy; |
| | | |
| | | @Schema(description = "å¼å§æ¶é´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime startTime; |
| | | |
| | | @Schema(description = "ç»ææ¶é´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime endTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - çªè´§é¢è¦åå¸ç»è®¡ Response VO") |
| | | @Data |
| | | public class MesTraceCrossRegionStatisticsRespVO { |
| | | |
| | | @Schema(description = "åç»åç§°", example = "广ä¸ç") |
| | | private String name; |
| | | |
| | | @Schema(description = "é¢è¦æ°é", example = "30") |
| | | private Long count; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - çªè´§é¢è¦æ±æ» Response VO") |
| | | @Data |
| | | public class MesTraceCrossRegionSummaryRespVO { |
| | | |
| | | @Schema(description = "é¢è¦æ»æ°", example = "100") |
| | | private Long total; |
| | | |
| | | @Schema(description = "å¾
å¤çæ°é", example = "30") |
| | | private Long pending; |
| | | |
| | | @Schema(description = "å·²å¤çæ°é", example = "60") |
| | | private Long handled; |
| | | |
| | | @Schema(description = "è¯¯æ¥æ°é", example = "10") |
| | | private Long falseAlarm; |
| | | |
| | | @Schema(description = "è·¨ççªè´§æ°é", example = "80") |
| | | private Long crossProvince; |
| | | |
| | | @Schema(description = "è·¨å¸çªè´§æ°é", example = "20") |
| | | private Long crossCity; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Schema(description = "管çåå° - çªè´§é¢è¦å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class MesTraceCrossRegionWarningPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "ç¶æï¼10 å¾
å¤ç / 20 å·²å¤ç / 30 误æ¥", example = "10") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "é¢è¦ç±»åï¼CROSS_PROVINCE / CROSS_CITY", example = "CROSS_PROVINCE") |
| | | private String warningType; |
| | | |
| | | @Schema(description = "æ¹æ¬¡ç¼å·") |
| | | private Long batchId; |
| | | |
| | | @Schema(description = "æ¹æ¬¡å·", example = "PC20260901L001M001_001") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "è¢ç ", example = "BAG-00000001") |
| | | private String bagCode; |
| | | |
| | | @Schema(description = "ç»éåç¼å·") |
| | | private Long clientId; |
| | | |
| | | @Schema(description = "ç»éååç§°", example = "åä¸åºåç»éå") |
| | | private String clientName; |
| | | |
| | | @Schema(description = "é¢è¦äº§çæ¶é´èå´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime[] eventTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - çªè´§é¢è¦ Response VO") |
| | | @Data |
| | | public class MesTraceCrossRegionWarningRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "é¢è¦ç¼å·", example = "WARN202609010001") |
| | | private String warningNo; |
| | | |
| | | @Schema(description = "è¢ç ç¼å·", example = "1") |
| | | private Long bagCodeId; |
| | | |
| | | @Schema(description = "è¢ç ", example = "BAG-00000001") |
| | | private String bagCode; |
| | | |
| | | @Schema(description = "æçç ç¼å·", example = "1") |
| | | private Long palletCodeId; |
| | | |
| | | @Schema(description = "æçç ", example = "BAG-00000001-P1") |
| | | private String palletCode; |
| | | |
| | | @Schema(description = "çäº§æ¹æ¬¡ç¼å·", example = "1") |
| | | private Long batchId; |
| | | |
| | | @Schema(description = "æ¹æ¬¡å·", example = "PC20260901L001M001_001") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "ç©æç¼å·", example = "1") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "ç©æç¼ç ", example = "M001") |
| | | private String itemCode; |
| | | |
| | | @Schema(description = "ç©æåç§°", example = "æäº¤æ°´ç¨»ç§å") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "éå®åºåºåç¼å·", example = "1") |
| | | private Long salesId; |
| | | |
| | | @Schema(description = "åºåºåå·", example = "PS202609010001") |
| | | private String salesCode; |
| | | |
| | | @Schema(description = "ç»éåç¼å·", example = "1") |
| | | private Long clientId; |
| | | |
| | | @Schema(description = "ç»éååç§°", example = "åä¸åºåç»éå") |
| | | private String clientName; |
| | | |
| | | @Schema(description = "ç»éåç份", example = "æ±èç") |
| | | private String dealerProvince; |
| | | |
| | | @Schema(description = "ç»éååå¸", example = "å京å¸") |
| | | private String dealerCity; |
| | | |
| | | @Schema(description = "æ«ç ç份", example = "广ä¸ç") |
| | | private String scanProvince; |
| | | |
| | | @Schema(description = "æ«ç åå¸", example = "广å·å¸") |
| | | private String scanCity; |
| | | |
| | | @Schema(description = "é¢è¦ç±»åï¼CROSS_PROVINCE / CROSS_CITY", example = "CROSS_PROVINCE") |
| | | private String warningType; |
| | | |
| | | @Schema(description = "é¢è¦ç±»ååç§°", example = "è·¨ççªè´§") |
| | | private String warningTypeName; |
| | | |
| | | @Schema(description = "å
³èæ¶è´¹è
æ«ç äºä»¶ç¼å·", example = "1") |
| | | private Long scanEventId; |
| | | |
| | | @Schema(description = "ç¶æï¼10 å¾
å¤ç / 20 å·²å¤ç / 30 误æ¥", example = "10") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "ç¶æåç§°", example = "å¾
å¤ç") |
| | | private String statusName; |
| | | |
| | | @Schema(description = "å¤ç人ç¼å·", example = "1") |
| | | private Long handleUserId; |
| | | |
| | | @Schema(description = "å¤ç人å§å", example = "å¼ ä¸") |
| | | private String handleUserName; |
| | | |
| | | @Schema(description = "å¤çæ¶é´") |
| | | private LocalDateTime handleTime; |
| | | |
| | | @Schema(description = "å¤ç夿³¨", example = "ç¡®è®¤ä¸ºç©æµä¸è½¬ï¼éçªè´§") |
| | | private String handleRemark; |
| | | |
| | | @Schema(description = "é¢è¦äº§çæ¶é´") |
| | | private LocalDateTime eventTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.integrity; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityRecordPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityVerifyReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityVerifyRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.integrity.MesTraceIntegrityRecordDO; |
| | | import cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @Tag(name = "管çåå° - MES æ°æ®å®æ´æ§æ ¡éª") |
| | | @RestController |
| | | @RequestMapping("/mes/trace-integrity") |
| | | @Validated |
| | | public class MesTraceIntegrityController { |
| | | |
| | | @Resource |
| | | private MesTraceIntegrityService integrityService; |
| | | |
| | | @GetMapping("/verify") |
| | | @Operation(summary = "æ ¡éªæ°æ®å®æ´æ§ï¼éç®æè¦æ¯å¯¹ï¼") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-integrity:verify')") |
| | | public CommonResult<MesTraceIntegrityVerifyRespVO> verify(@Valid MesTraceIntegrityVerifyReqVO reqVO) { |
| | | return success(integrityService.verify(reqVO.getBizType(), reqVO.getBizId())); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾æ°æ®å®æ´æ§è®°å½å页") |
| | | @PreAuthorize("@ss.hasPermission('mes:trace-integrity:query')") |
| | | public CommonResult<PageResult<MesTraceIntegrityRecordDO>> getIntegrityPage(@Valid MesTraceIntegrityRecordPageReqVO reqVO) { |
| | | return success(integrityService.getIntegrityPage(reqVO)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | |
| | | @Schema(description = "管çåå° - æ°æ®å®æ´æ§è®°å½å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class MesTraceIntegrityRecordPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "ä¸å¡ç±»åï¼BATCH / SALES / SCAN", example = "BATCH") |
| | | private String bizType; |
| | | |
| | | @Schema(description = "ä¸å¡è®°å½ç¼å·", example = "1") |
| | | private Long bizId; |
| | | |
| | | @Schema(description = "ä¸å¡ç¼ç ", example = "PC20260901L001M001_001") |
| | | private String bizCode; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - æ°æ®å®æ´æ§æ ¡éª Request VO") |
| | | @Data |
| | | public class MesTraceIntegrityVerifyReqVO { |
| | | |
| | | @Schema(description = "ä¸å¡ç±»åï¼BATCH æ¹æ¬¡ / SALES éå®åºåº / SCAN æ¶è´¹è
æ«ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "BATCH") |
| | | @NotBlank(message = "ä¸å¡ç±»åä¸è½ä¸ºç©º") |
| | | private String bizType; |
| | | |
| | | @Schema(description = "ä¸å¡è®°å½ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @NotNull(message = "ä¸å¡è®°å½ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long bizId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - æ°æ®å®æ´æ§æ ¡éª Response VO") |
| | | @Data |
| | | public class MesTraceIntegrityVerifyRespVO { |
| | | |
| | | @Schema(description = "ä¸å¡ç±»åï¼BATCH / SALES / SCAN", example = "BATCH") |
| | | private String bizType; |
| | | |
| | | @Schema(description = "ä¸å¡è®°å½ç¼å·", example = "1024") |
| | | private Long bizId; |
| | | |
| | | @Schema(description = "ä¸å¡ç¼ç ", example = "PC20260901L001M001_001") |
| | | private String bizCode; |
| | | |
| | | @Schema(description = "æ¯å¦æ ¡éªéè¿", example = "true") |
| | | private Boolean matched; |
| | | |
| | | @Schema(description = "åå¨çæè¦ SHA-256", example = "a1b2c3...") |
| | | private String storedHash; |
| | | |
| | | @Schema(description = "å½åæ°æ®éç®æè¦ SHA-256", example = "a1b2c3...") |
| | | private String currentHash; |
| | | |
| | | @Schema(description = "æ¹æ¬¡å½åè¢ç éåæè¦ï¼ä»
BATCH æ ¡éªè¿åï¼ä¾äººå·¥æ¯å¯¹è¢ç é²ç¯¡æ¹ï¼", example = "d4e5f6...") |
| | | private String bagSetHash; |
| | | |
| | | @Schema(description = "æ ¡éªè¯´æ", example = "æ°æ®å®æ´ï¼æªè¢«ç¯¡æ¹") |
| | | private String message; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * MES æº¯æº H5 é
ç½® Resp VO |
| | | */ |
| | | @Data |
| | | public class MesTraceQrConfigRespVO { |
| | | |
| | | /** |
| | | * æº¯æº H5 页é¢åºç¡å°åï¼äºç»´ç å
容åç¼ï¼å¦ http://host:port/trace.htmlï¼ |
| | | */ |
| | | private String traceBaseUrl; |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO; |
| | |
| | | private MesWmProductSalesService productSalesService; |
| | | @Resource |
| | | private CrmCustomerApi crmCustomerApi; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private MesWmSalesNoticeService salesNoticeService; |
| | | |
| | |
| | | convertSet(list, MesWmProductSalesDO::getClientId)); |
| | | Map<Long, MesWmSalesNoticeDO> noticeMap = salesNoticeService.getSalesNoticeMap( |
| | | convertSet(list, MesWmProductSalesDO::getNoticeId)); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesWmProductSalesDO::getOutboundUserId)); |
| | | // 2. æå»ºç»æ |
| | | return BeanUtils.toBean(list, MesWmProductSalesRespVO.class, vo -> { |
| | | MapUtils.findAndThen(customerMap, vo.getClientId(), customer -> |
| | | vo.setClientName(customer.getName())); |
| | | if (vo.getClientName() == null) { |
| | | MapUtils.findAndThen(customerMap, vo.getClientId(), customer -> vo.setClientName(customer.getName())); |
| | | } |
| | | MapUtils.findAndThen(noticeMap, vo.getNoticeId(), |
| | | notice -> vo.setNoticeCode(notice.getCode())); |
| | | MapUtils.findAndThen(userMap, vo.getOutboundUserId(), |
| | | user -> vo.setOutboundUserName(user.getNickname())); |
| | | }); |
| | | } |
| | | |
| | |
| | | @Schema(description = "客æ·ç¼å·", example = "1") |
| | | private Long clientId; |
| | | |
| | | @Schema(description = "å®é
åºåºæ¶é´èå´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime[] outboundTime; |
| | | |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | |
| | | @ExcelProperty("客æ·") |
| | | private String clientName; |
| | | |
| | | @Schema(description = "å®é
åºåºæ¶é´") |
| | | @ExcelProperty("å®é
åºåºæ¶é´") |
| | | private LocalDateTime outboundTime; |
| | | |
| | | @Schema(description = "æ§è¡åºåºæä½äººID") |
| | | private Long outboundUserId; |
| | | |
| | | @Schema(description = "æ§è¡åºåºæä½äººåç§°") |
| | | @ExcelProperty("åºåºæä½äºº") |
| | | private String outboundUserName; |
| | | |
| | | @Schema(description = "éå®è®¢åå·", example = "SO2026030001") |
| | | @ExcelProperty("éå®è®¢åå·") |
| | | private String salesOrderCode; |
| | |
| | | @Schema(description = "ç§åè´¨æ£ä¿¡æ¯") |
| | | private QualityVO quality; |
| | | |
| | | // ====== é²ä¼ªéªè¯åºå ====== |
| | | @Schema(description = "é²ä¼ªéªè¯ä¿¡æ¯ï¼è¯¥è¿½æº¯ç æ«ç 次æ°ä¸æ¶é´ï¼") |
| | | private AntiFakeVO antiFake; |
| | | |
| | | // ====== å¯éæ©å±åºåï¼ææ°æ®æè¿åï¼ç©ºè¡¨è¿å空åè¡¨ï¼ ====== |
| | | @Schema(description = "éè´æ¥æºï¼å¯éï¼") |
| | | private List<MesWmBatchPurchaseSourceRespVO> purchaseSources; |
| | |
| | | private LocalDateTime eventTime; |
| | | } |
| | | |
| | | @Schema(description = "é²ä¼ªéªè¯ä¿¡æ¯") |
| | | @Data |
| | | public static class AntiFakeVO { |
| | | @Schema(description = "该追溯ç 累计æ«ç 次æ°", example = "3") |
| | | private Long scanCount; |
| | | @Schema(description = "馿¬¡æ«ç æ¶é´") |
| | | private LocalDateTime firstScanTime; |
| | | @Schema(description = "æè¿æ«ç æ¶é´") |
| | | private LocalDateTime lastScanTime; |
| | | } |
| | | |
| | | @Schema(description = "ç§åè´¨æ£ä¿¡æ¯") |
| | | @Data |
| | | public static class QualityVO { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç 访é®äºä»¶ DO |
| | | * <p> |
| | | * è®°å½å
¬ä¼éè¿äºç»´ç /H5 æ«ææº¯æºé¡µé¢çè®¿é®æ
åµï¼æåä¸å¤±è´¥åè®°å½ï¼ï¼ |
| | | * ç¨äºåææ¶è´¹è
溯æºè®¿é®éãè¶å¿ä¸åå¸ã |
| | | */ |
| | | @TableName("mes_trace_consumer_scan_event") |
| | | @KeySequence("mes_trace_consumer_scan_event_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class MesTraceConsumerScanEventDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** æ«ç åå§å
容ï¼å¿
è¦æ¶æªæï¼ */ |
| | | private String traceCode; |
| | | |
| | | /** è§£æåç追溯ç */ |
| | | private String normalizedCode; |
| | | |
| | | /** å
¥å£ç±»åï¼BAG/PALLET/BATCH */ |
| | | private String sourceType; |
| | | |
| | | /** æ«ç å
¥å£æ¸ éï¼å¾®ä¿¡/æ¯ä»å®/æµè§å¨ */ |
| | | private String channel; |
| | | |
| | | /** ç份ï¼IP å½å±å°ï¼ä»
åå°åä¸åææä¿¡æ¯ï¼ */ |
| | | private String province; |
| | | |
| | | /** åå¸ï¼IP å½å±å°ï¼ä»
åå°åä¸åææä¿¡æ¯ï¼ */ |
| | | private String city; |
| | | |
| | | /** çäº§æ¹æ¬¡ç¼å· */ |
| | | private Long batchId; |
| | | |
| | | /** æ¯å¦æ¥è¯¢æåï¼0 失败ï¼1 æåï¼ */ |
| | | private Boolean success; |
| | | |
| | | /** 失败ç¼ç */ |
| | | private String errorCode; |
| | | |
| | | /** 客æ·ç«¯ IP SHA-256 æè¦ï¼ä¸ä¿åææï¼ */ |
| | | private String ipHash; |
| | | |
| | | /** User-Agent SHA-256 æè¦ï¼ä¸ä¿åææï¼ */ |
| | | private String userAgentHash; |
| | | |
| | | /** è®¿é®æ¶é´ */ |
| | | private LocalDateTime eventTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | /** |
| | | * é²çªè´§è§åé
ç½® DOï¼key-valueï¼ |
| | | */ |
| | | @TableName("mes_trace_anti_config") |
| | | @KeySequence("mes_trace_anti_config_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class MesTraceAntiConfigDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** é
置项 Keyï¼å¦ warning_levelï¼ */ |
| | | private String configKey; |
| | | |
| | | /** é
ç½®å¼ï¼warning_levelï¼PROVINCE è·¨ç / CITY è·¨å¸ï¼ */ |
| | | private String configValue; |
| | | |
| | | /** é
置说æ */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * çªè´§é¢è¦è®°å½ DO |
| | | * <p> |
| | | * æ¶è´¹è
æ«ç å°åºä¸è¢ç åºåºç»éåæå¨å°åºä¸ä¸è´æ¶ï¼ç³»ç»èªå¨çæé¢è¦ï¼ä¾ç®¡ç端稽æ¥ä¸å¤çã |
| | | */ |
| | | @TableName("mes_trace_cross_region_warning") |
| | | @KeySequence("mes_trace_cross_region_warning_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class MesTraceCrossRegionWarningDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** é¢è¦ç¼å· */ |
| | | private String warningNo; |
| | | |
| | | /** è¢ç ç¼å· */ |
| | | private Long bagCodeId; |
| | | |
| | | /** è¢ç */ |
| | | private String bagCode; |
| | | |
| | | /** æçç ç¼å· */ |
| | | private Long palletCodeId; |
| | | |
| | | /** æçç */ |
| | | private String palletCode; |
| | | |
| | | /** çäº§æ¹æ¬¡ç¼å· */ |
| | | private Long batchId; |
| | | |
| | | /** æ¹æ¬¡å· */ |
| | | private String batchCode; |
| | | |
| | | /** ç©æç¼å· */ |
| | | private Long itemId; |
| | | |
| | | /** ç©æç¼ç */ |
| | | private String itemCode; |
| | | |
| | | /** ç©æåç§° */ |
| | | private String itemName; |
| | | |
| | | /** éå®åºåºåç¼å· */ |
| | | private Long salesId; |
| | | |
| | | /** åºåºåå· */ |
| | | private String salesCode; |
| | | |
| | | /** ç»éåç¼å· */ |
| | | private Long clientId; |
| | | |
| | | /** ç»éååç§° */ |
| | | private String clientName; |
| | | |
| | | /** ç»éåç份 */ |
| | | private String dealerProvince; |
| | | |
| | | /** ç»éååå¸ */ |
| | | private String dealerCity; |
| | | |
| | | /** æ«ç ç份 */ |
| | | private String scanProvince; |
| | | |
| | | /** æ«ç åå¸ */ |
| | | private String scanCity; |
| | | |
| | | /** é¢è¦ç±»åï¼CROSS_PROVINCE / CROSS_CITY */ |
| | | private String warningType; |
| | | |
| | | /** å
³èæ¶è´¹è
æ«ç äºä»¶ç¼å· */ |
| | | private Long scanEventId; |
| | | |
| | | /** ç¶æï¼10 å¾
å¤ç / 20 å·²å¤ç / 30 è¯¯æ¥ */ |
| | | private Integer status; |
| | | |
| | | /** å¤ç人ç¼å· */ |
| | | private Long handleUserId; |
| | | |
| | | /** å¤çæ¶é´ */ |
| | | private LocalDateTime handleTime; |
| | | |
| | | /** å¤ç夿³¨ */ |
| | | private String handleRemark; |
| | | |
| | | /** é¢è¦äº§çæ¶é´ */ |
| | | private LocalDateTime eventTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.trace.integrity; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æ°æ®å®æ´æ§æ ¡éªè®°å½ DO |
| | | * <p> |
| | | * 卿¹æ¬¡å建ãéå®åºåºãæ¶è´¹è
æ«ç çå
³é®äºä»¶åçæ¶ï¼å¯¹ä¸å¯åæ ¸å¿åæ®µçæ SHA-256 æè¦ï¼ |
| | | * ä¾ç®¡çç«¯éæ°è®¡ç®æ¯å¯¹ï¼åç°äºå篡æ¹ã |
| | | */ |
| | | @TableName("mes_trace_integrity_record") |
| | | @KeySequence("mes_trace_integrity_record_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class MesTraceIntegrityRecordDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** ä¸å¡ç±»åï¼BATCH æ¹æ¬¡ / SALES éå®åºåº / SCAN æ¶è´¹è
æ«ç */ |
| | | private String bizType; |
| | | |
| | | /** ä¸å¡è®°å½ç¼å· */ |
| | | private Long bizId; |
| | | |
| | | /** ä¸å¡ç¼ç ï¼æ¹æ¬¡å· / åºåºåå· / 追溯ç ï¼ */ |
| | | private String bizCode; |
| | | |
| | | /** å
容æè¦ SHA-256 */ |
| | | private String contentHash; |
| | | |
| | | /** æè¦çææ¶é´ */ |
| | | private LocalDateTime eventTime; |
| | | |
| | | } |
| | |
| | | */ |
| | | private Long clientId; |
| | | /** |
| | | * ç»éååç§°å¿«ç
§ |
| | | */ |
| | | private String clientName; |
| | | /** |
| | | * éå®è®¢åID |
| | | */ |
| | | private Long saleOrderId; |
| | |
| | | * åºåºæ¥æ |
| | | */ |
| | | private LocalDateTime salesDate; |
| | | /** |
| | | * å®é
åºåºæ¶é´ |
| | | */ |
| | | private LocalDateTime outboundTime; |
| | | /** |
| | | * æ§è¡åºåºæä½äººID |
| | | */ |
| | | private Long outboundUserId; |
| | | |
| | | /** |
| | | * è系人 |
| | |
| | | return selectOne(MesProBatchDO::getTempCode, tempCode); |
| | | } |
| | | |
| | | /** çæä¸´æ¶æ¹æ¬¡ç ç¨çå
¨å±åºå·ï¼è§£æ temp_code çæ°ååç¼ï¼ä¸åå¨åè¿å 0 */ |
| | | @Select("SELECT IFNULL(MAX(CAST(SUBSTRING(temp_code, 4) AS UNSIGNED)), 0) FROM mes_pro_batch WHERE deleted = 0") |
| | | /** çæä¸´æ¶æ¹æ¬¡ç ç¨çå
¨å±åºå·ï¼è§£æ temp_code çæ°ååç¼ï¼ä¸åå¨åè¿å 0ã |
| | | * ä¸è½è¿æ»¤ deletedï¼å¯ä¸ç´¢å¼ uk_temp_code 对é»è¾å é¤è¡ä»çæï¼å¿½ç¥è½¯å ä¼å¯¼è´åºå·åéãç å¼éå¤ */ |
| | | @Select("SELECT IFNULL(MAX(CAST(SUBSTRING(temp_code, 4) AS UNSIGNED)), 0) FROM mes_pro_batch") |
| | | Integer selectMaxTempCodeSeq(); |
| | | |
| | | default Long selectCountByLineId(Long lineId) { |
| | |
| | | return selectOne(MesProPalletDO::getTempCode, tempCode); |
| | | } |
| | | |
| | | /** çæä¸´æ¶æçç ç¨çå
¨å±åºå·ï¼è§£æ temp_code çæ°ååç¼ï¼ä¸åå¨åè¿å 0 */ |
| | | @Select("SELECT IFNULL(MAX(CAST(SUBSTRING(temp_code, 4) AS UNSIGNED)), 0) FROM mes_pro_pallet WHERE deleted = 0") |
| | | /** çæä¸´æ¶æçç ç¨çå
¨å±åºå·ï¼è§£æ temp_code çæ°ååç¼ï¼ä¸åå¨åè¿å 0ã |
| | | * ä¸è½è¿æ»¤ deletedï¼å¯ä¸ç´¢å¼ uk_temp_code 对é»è¾å é¤è¡ä»çæï¼å¿½ç¥è½¯å ä¼å¯¼è´åºå·åéãç å¼éå¤ */ |
| | | @Select("SELECT IFNULL(MAX(CAST(SUBSTRING(temp_code, 4) AS UNSIGNED)), 0) FROM mes_pro_pallet") |
| | | Integer selectMaxTempCodeSeq(); |
| | | |
| | | @Select("SELECT * FROM mes_pro_pallet WHERE id = #{id} AND deleted = 0 FOR UPDATE") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç 访é®äºä»¶ Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface MesTraceConsumerScanEventMapper extends BaseMapperX<MesTraceConsumerScanEventDO> { |
| | | |
| | | /** |
| | | * æè¿½æº¯ç æ¥è¯¢æ«ç äºä»¶ï¼æ£åºï¼ä¾é²ä¼ªéªè¯ç»è®¡æ«ç 次æ°ä¸é¦æ¬¡/æè¿æ«ç æ¶é´ï¼ |
| | | * |
| | | * @param normalizedCode è§£æåç追溯ç |
| | | * @return æ«ç äºä»¶å表 |
| | | */ |
| | | default List<MesTraceConsumerScanEventDO> selectListByNormalizedCode(String normalizedCode) { |
| | | return selectList(new LambdaQueryWrapperX<MesTraceConsumerScanEventDO>() |
| | | .eq(MesTraceConsumerScanEventDO::getNormalizedCode, normalizedCode) |
| | | .orderByAsc(MesTraceConsumerScanEventDO::getEventTime)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç 访é®ç»è®¡ Mapper |
| | | * <p> |
| | | * ç»è®¡éç¨æ¥æç» + Java ä¾§èåï¼é¿å
ç»è®¡ SQL ä¸ MySQL/H2 æ¹è¨å·®å¼ï¼å¹¶æ¶é¤å¨æåæ¼æ¥æ³¨å
¥é¢ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface MesTraceConsumerScanStatisticsMapper extends BaseMapperX<MesTraceConsumerScanEventDO> { |
| | | |
| | | /** |
| | | * æ¥è¯¢ç»è®¡æ¶é´èå´å
ï¼å¼å§æ¶é´å«ãç»ææ¶é´ä¸å«ï¼çæ¶è´¹è
æ«ç 访é®äºä»¶ |
| | | */ |
| | | default List<MesTraceConsumerScanEventDO> selectListByTime(LocalDateTime startTime, LocalDateTime endTime) { |
| | | return selectList(new LambdaQueryWrapperX<MesTraceConsumerScanEventDO>() |
| | | .geIfPresent(MesTraceConsumerScanEventDO::getEventTime, startTime) |
| | | .ltIfPresent(MesTraceConsumerScanEventDO::getEventTime, endTime) |
| | | .orderByAsc(MesTraceConsumerScanEventDO::getEventTime)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion.MesTraceAntiConfigDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * é²çªè´§è§åé
ç½® Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface MesTraceAntiConfigMapper extends BaseMapperX<MesTraceAntiConfigDO> { |
| | | |
| | | default MesTraceAntiConfigDO selectByConfigKey(String configKey) { |
| | | return selectOne(MesTraceAntiConfigDO::getConfigKey, configKey); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion.MesTraceCrossRegionWarningDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * çªè´§é¢è¦è®°å½ Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface MesTraceCrossRegionWarningMapper extends BaseMapperX<MesTraceCrossRegionWarningDO> { |
| | | |
| | | default PageResult<MesTraceCrossRegionWarningDO> selectPage(MesTraceCrossRegionWarningPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<MesTraceCrossRegionWarningDO>() |
| | | .eqIfPresent(MesTraceCrossRegionWarningDO::getStatus, reqVO.getStatus()) |
| | | .eqIfPresent(MesTraceCrossRegionWarningDO::getWarningType, reqVO.getWarningType()) |
| | | .eqIfPresent(MesTraceCrossRegionWarningDO::getBatchId, reqVO.getBatchId()) |
| | | .likeIfPresent(MesTraceCrossRegionWarningDO::getBatchCode, reqVO.getBatchCode()) |
| | | .likeIfPresent(MesTraceCrossRegionWarningDO::getBagCode, reqVO.getBagCode()) |
| | | .eqIfPresent(MesTraceCrossRegionWarningDO::getClientId, reqVO.getClientId()) |
| | | .likeIfPresent(MesTraceCrossRegionWarningDO::getClientName, reqVO.getClientName()) |
| | | .betweenIfPresent(MesTraceCrossRegionWarningDO::getEventTime, reqVO.getEventTime()) |
| | | .orderByDesc(MesTraceCrossRegionWarningDO::getId)); |
| | | } |
| | | |
| | | /** |
| | | * ææ¶é´èå´æ¥è¯¢é¢è¦å表ï¼ä¾ç»è®¡åç»ä½¿ç¨ï¼ |
| | | */ |
| | | default List<MesTraceCrossRegionWarningDO> selectListByEventTimeBetween(java.time.LocalDateTime startTime, |
| | | java.time.LocalDateTime endTime) { |
| | | return selectList(new LambdaQueryWrapperX<MesTraceCrossRegionWarningDO>() |
| | | .betweenIfPresent(MesTraceCrossRegionWarningDO::getEventTime, startTime, endTime)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.trace.integrity; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityRecordPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.integrity.MesTraceIntegrityRecordDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * æ°æ®å®æ´æ§æ ¡éªè®°å½ Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface MesTraceIntegrityRecordMapper extends BaseMapperX<MesTraceIntegrityRecordDO> { |
| | | |
| | | default MesTraceIntegrityRecordDO selectByBiz(String bizType, Long bizId) { |
| | | return selectOne(new LambdaQueryWrapperX<MesTraceIntegrityRecordDO>() |
| | | .eq(MesTraceIntegrityRecordDO::getBizType, bizType) |
| | | .eq(MesTraceIntegrityRecordDO::getBizId, bizId)); |
| | | } |
| | | |
| | | default PageResult<MesTraceIntegrityRecordDO> selectPage(MesTraceIntegrityRecordPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<MesTraceIntegrityRecordDO>() |
| | | .eqIfPresent(MesTraceIntegrityRecordDO::getBizType, reqVO.getBizType()) |
| | | .eqIfPresent(MesTraceIntegrityRecordDO::getBizId, reqVO.getBizId()) |
| | | .likeIfPresent(MesTraceIntegrityRecordDO::getBizCode, reqVO.getBizCode()) |
| | | .orderByDesc(MesTraceIntegrityRecordDO::getId)); |
| | | } |
| | | |
| | | } |
| | |
| | | delete(MesWmProductSalesDetailDO::getSalesId, salesId); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®è¢ç ç¼å·æ¥è¯¢æè¿çåºåºæç»ï¼ä¾çªè´§ç¨½æ¥åæ¥åºåºå»åï¼ |
| | | */ |
| | | default MesWmProductSalesDetailDO selectLatestByBagCodeId(Long bagCodeId) { |
| | | return selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmProductSalesDetailDO>() |
| | | .eq(MesWmProductSalesDetailDO::getBagCodeId, bagCodeId) |
| | | .orderByDesc(MesWmProductSalesDetailDO::getId) |
| | | .last("LIMIT 1")); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®æçç ç¼å·æ¥è¯¢æè¿çåºåºæç»ï¼ä¾çªè´§ç¨½æ¥åæ¥åºåºå»åï¼ |
| | | */ |
| | | default MesWmProductSalesDetailDO selectLatestByPalletCodeId(Long palletCodeId) { |
| | | return selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmProductSalesDetailDO>() |
| | | .eq(MesWmProductSalesDetailDO::getPalletCodeId, palletCodeId) |
| | | .orderByDesc(MesWmProductSalesDetailDO::getId) |
| | | .last("LIMIT 1")); |
| | | } |
| | | |
| | | } |
| | |
| | | .eqIfPresent(MesWmProductSalesDO::getClientId, reqVO.getClientId()) |
| | | .eqIfPresent(MesWmProductSalesDO::getStatus, reqVO.getStatus()) |
| | | .betweenIfPresent(MesWmProductSalesDO::getSalesDate, reqVO.getShipmentDate()) |
| | | .betweenIfPresent(MesWmProductSalesDO::getOutboundTime, reqVO.getOutboundTime()) |
| | | .orderByDesc(MesWmProductSalesDO::getId)); |
| | | } |
| | | |
| | |
| | | .eqIfPresent(MesWmProductSalesDO::getClientId, reqVO.getClientId()) |
| | | .eqIfPresent(MesWmProductSalesDO::getStatus, reqVO.getStatus()) |
| | | .betweenIfPresent(MesWmProductSalesDO::getSalesDate, reqVO.getShipmentDate()) |
| | | .betweenIfPresent(MesWmProductSalesDO::getOutboundTime, reqVO.getOutboundTime()) |
| | | .orderByDesc(MesWmProductSalesDO::getId); |
| | | if (permittedClientIds != null) { |
| | | if (CollUtil.isEmpty(permittedClientIds)) { |
| | |
| | | // ========== MES å
¬å¼æ«ç 溯æºï¼1-040-760-000ï¼ ========== |
| | | ErrorCode MES_TRACE_CODE_NOT_FOUND = new ErrorCode(1_040_760_000, "æªæ¥è¯¢å°è¯¥è¿½æº¯ç ï¼è¯·æ ¸å¯¹äºç»´ç æèç³»å®æ¹"); |
| | | |
| | | // ========== MES é²ä¼ªé²çªè´§-çªè´§é¢è¦ï¼1-040-761-000ï¼ ========== |
| | | ErrorCode MES_TRACE_CROSS_REGION_WARNING_NOT_EXISTS = new ErrorCode(1_040_761_000, "çªè´§é¢è¦è®°å½ä¸åå¨"); |
| | | ErrorCode MES_TRACE_CROSS_REGION_STATUS_INVALID = new ErrorCode(1_040_761_001, "çªè´§é¢è¦ç¶ææ æï¼ä»
å¾
å¤çé¢è¦å¯å¤ç"); |
| | | ErrorCode MES_TRACE_ANTI_CONFIG_NOT_EXISTS = new ErrorCode(1_040_761_002, "é²çªè´§è§åé
ç½®ä¸åå¨"); |
| | | ErrorCode MES_TRACE_ANTI_CONFIG_VALUE_INVALID = new ErrorCode(1_040_761_003, "é²çªè´§è§åé
ç½®å¼æ æï¼ä»
æ¯æ PROVINCE/CITY"); |
| | | |
| | | // ========== MES é²ä¼ªé²çªè´§-æ°æ®å®æ´æ§ï¼1-040-762-000ï¼ ========== |
| | | ErrorCode MES_TRACE_INTEGRITY_RECORD_NOT_EXISTS = new ErrorCode(1_040_762_000, "æ°æ®å®æ´æ§è®°å½ä¸åå¨ï¼è¯·å
对ä¸å¡æ°æ®çææè¦"); |
| | | ErrorCode MES_TRACE_INTEGRITY_BIZ_TYPE_INVALID = new ErrorCode(1_040_762_001, "æ°æ®å®æ´æ§ä¸å¡ç±»åæ æï¼ä»
æ¯æ BATCH/SALES/SCAN"); |
| | | ErrorCode MES_TRACE_INTEGRITY_BIZ_NOT_EXISTS = new ErrorCode(1_040_762_002, "å¾
æ ¡éªçä¸å¡æ°æ®ä¸åå¨"); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.enums.trace; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * æ°æ®å®æ´æ§ä¸å¡ç±»åæä¸¾ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum MesTraceIntegrityBizTypeEnum { |
| | | |
| | | BATCH("BATCH", "çäº§æ¹æ¬¡"), |
| | | SALES("SALES", "éå®åºåº"), |
| | | SCAN("SCAN", "æ¶è´¹è
æ«ç "); |
| | | |
| | | /** |
| | | * ä¸å¡ç±»å |
| | | */ |
| | | private final String type; |
| | | /** |
| | | * ç±»ååç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | /** |
| | | * 夿ä¸å¡ç±»åæ¯å¦åæ³ |
| | | * |
| | | * @param type ä¸å¡ç±»å |
| | | * @return æ¯å¦åæ³ |
| | | */ |
| | | public static boolean isValid(String type) { |
| | | for (MesTraceIntegrityBizTypeEnum e : values()) { |
| | | if (e.type.equals(type)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.enums.trace; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * çªè´§é¢è¦ç¶ææä¸¾ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum MesTraceWarningStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | PENDING(10, "å¾
å¤ç"), |
| | | HANDLED(20, "å·²å¤ç"), |
| | | FALSE_ALARM(30, "误æ¥"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesTraceWarningStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * ç¶æ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * ç¶æåç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.enums.trace; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * çªè´§é¢è¦ç±»åæä¸¾ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum MesTraceWarningTypeEnum { |
| | | |
| | | CROSS_PROVINCE("CROSS_PROVINCE", "è·¨ççªè´§"), |
| | | CROSS_CITY("CROSS_CITY", "è·¨å¸çªè´§"); |
| | | |
| | | /** |
| | | * ç±»å |
| | | */ |
| | | private final String type; |
| | | /** |
| | | * ç±»ååç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.line.MesProLineService; |
| | | import cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private MesProLineService lineService; |
| | | @Resource |
| | | private MesTraceIntegrityService integrityService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | |
| | | // 4. æå
¥ç产人åé
ç½® |
| | | insertBatchWorkers(batch.getId(), createReqVO.getWorkers()); |
| | | // 5. çææ¹æ¬¡å®æ´æ§æè¦ |
| | | integrityService.recordBatchIntegrity(batch.getId()); |
| | | return batch.getId(); |
| | | } |
| | | |
| | |
| | | // 4. é建ç产人åé
ç½® |
| | | batchWorkerMapper.deleteByBatchId(fillReqVO.getId()); |
| | | insertBatchWorkers(fillReqVO.getId(), fillReqVO.getWorkers()); |
| | | // 5. çææ¹æ¬¡å®æ´æ§æè¦ï¼è¡¥å½æ£å¼ä¿¡æ¯åè¦çï¼ |
| | | integrityService.recordBatchIntegrity(fillReqVO.getId()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | // 5. é建ç产人åé
ç½® |
| | | batchWorkerMapper.deleteByBatchId(updateReqVO.getId()); |
| | | insertBatchWorkers(updateReqVO.getId(), updateReqVO.getWorkers()); |
| | | // 6. æè¦è¦çèå´å«åç§/产线/çäº§æ¥æ/è®¡åæ°éï¼ç¼è¾åéæ°çæé¿å
误æ¥ç¯¡æ¹ |
| | | integrityService.recordBatchIntegrity(updateReqVO.getId()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.scan.MesProScanEventService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; |
| | | import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService; |
| | | import cn.iocoder.yudao.module.mes.service.trace.consumer.MesTraceConsumerScanEventService; |
| | | import cn.iocoder.yudao.module.mes.service.trace.crossregion.MesTraceCrossRegionService; |
| | | import cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private MesWmBatchService mesWmBatchService; |
| | | @Resource |
| | | private MesTraceConsumerScanEventService consumerScanEventService; |
| | | @Resource |
| | | private MesTraceCrossRegionService crossRegionService; |
| | | @Resource |
| | | private MesTraceIntegrityService integrityService; |
| | | @Resource |
| | | private MesTraceConsumerScanEventMapper consumerScanEventMapper; |
| | | |
| | | @Override |
| | | public MesProTracePublicRespVO queryTrace(String code) { |
| | | String rawCode = StrUtil.trim(StrUtil.nullToEmpty(code)); |
| | | String traceCode = normalize(rawCode); |
| | | if (StrUtil.isEmpty(traceCode)) { |
| | | recordConsumerScan(rawCode, traceCode, null, null, false); |
| | | throw exception(MES_TRACE_CODE_NOT_FOUND); |
| | | } |
| | | // 1. è§£æè¿½æº¯å
¥å£ï¼å®ä½æ¹æ¬¡ |
| | | TraceEntry entry = resolveEntry(traceCode); |
| | | if (entry.batchId == null) { |
| | | throw exception(MES_TRACE_CODE_NOT_FOUND); |
| | | TraceEntry entry = null; |
| | | try { |
| | | // 1. è§£æè¿½æº¯å
¥å£ï¼å®ä½æ¹æ¬¡ |
| | | entry = resolveEntry(traceCode); |
| | | if (entry.batchId == null) { |
| | | throw exception(MES_TRACE_CODE_NOT_FOUND); |
| | | } |
| | | // 2. èåå
¨ç¨ä¿¡æ¯ |
| | | MesProTracePublicRespVO result = buildRespVO(rawCode, traceCode, entry.sourceType, entry.batchId); |
| | | recordConsumerScan(rawCode, traceCode, entry.sourceType, entry.batchId, true); |
| | | return result; |
| | | } catch (RuntimeException ex) { |
| | | recordConsumerScan(rawCode, traceCode, entry == null ? null : entry.sourceType, |
| | | entry == null ? null : entry.batchId, false); |
| | | throw ex; |
| | | } |
| | | // 2. èåå
¨ç¨ä¿¡æ¯ |
| | | return buildRespVO(rawCode, traceCode, entry.sourceType, entry.batchId); |
| | | } |
| | | |
| | | /** |
| | | * è®°å½æ¶è´¹è
æ«ç äºä»¶ï¼å¹¶è§¦åé²ä¼ªé²çªè´§æ©å±ï¼çªè´§å¤å® + æ«ç æè¦ï¼ï¼å容éä¸å½±å溯æºä¸»æµç¨ |
| | | */ |
| | | private MesTraceConsumerScanEventDO recordConsumerScan(String rawCode, String traceCode, String sourceType, |
| | | Long batchId, boolean success) { |
| | | MesTraceConsumerScanEventDO event = consumerScanEventService.recordScan(rawCode, traceCode, sourceType, |
| | | batchId, success, success ? null : MES_TRACE_CODE_NOT_FOUND.getCode().toString()); |
| | | if (event == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | } catch (Exception ex) { |
| | | log.warn("[recordConsumerScan][çªè´§å¤å®å¤±è´¥] eventId={}", event.getId(), ex); |
| | | } |
| | | try { |
| | | integrityService.recordScanIntegrity(event); |
| | | } catch (Exception ex) { |
| | | log.warn("[recordConsumerScan][æ«ç æè¦çæå¤±è´¥] eventId={}", event.getId(), ex); |
| | | } |
| | | return event; |
| | | } |
| | | |
| | | // ==================== 追溯ç è§£æ ==================== |
| | |
| | | resp.setInbound(buildInbound(batchId)); |
| | | // è´¨æ£åºå |
| | | resp.setQuality(buildQuality(batch)); |
| | | // é²ä¼ªéªè¯åºå |
| | | fillAntiFake(resp, traceCode); |
| | | // å¯éæ©å±åºåï¼ææ°æ®æè¿åï¼ |
| | | fillOptionalModules(resp, batch); |
| | | return resp; |
| | |
| | | return vo; |
| | | } |
| | | |
| | | /** |
| | | * å¡«å
é²ä¼ªéªè¯åºåï¼è¯¥è¿½æº¯ç 累计æ«ç 次æ°ã馿¬¡/æè¿æ«ç æ¶é´ï¼æ£åéªè¯å¢å¼ºï¼ |
| | | */ |
| | | private void fillAntiFake(MesProTracePublicRespVO resp, String traceCode) { |
| | | try { |
| | | List<MesTraceConsumerScanEventDO> events = consumerScanEventMapper.selectListByNormalizedCode(traceCode); |
| | | if (CollUtil.isEmpty(events)) { |
| | | return; |
| | | } |
| | | MesProTracePublicRespVO.AntiFakeVO vo = new MesProTracePublicRespVO.AntiFakeVO(); |
| | | vo.setScanCount((long) events.size()); |
| | | vo.setFirstScanTime(events.get(0).getEventTime()); |
| | | vo.setLastScanTime(events.get(events.size() - 1).getEventTime()); |
| | | resp.setAntiFake(vo); |
| | | } catch (Exception ex) { |
| | | log.warn("[fillAntiFake][追溯ç {} é²ä¼ªéªè¯ç»è®¡å¤±è´¥: {}]", traceCode, ex.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void fillOptionalModules(MesProTracePublicRespVO resp, MesProBatchDO batch) { |
| | | try { |
| | | List<MesWmBatchPurchaseSourceRespVO> purchaseSources = mesWmBatchService.getPurchaseSourceList(batch.getCode()); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç 访é®äºä»¶ Service æ¥å£ |
| | | * <p> |
| | | * è®°å½å
¬ä¼æº¯æºæ«ç è®¿é®æ
åµãè®°å½å¤±è´¥ä¸å¾å½±åæº¯æºæ¥è¯¢ä¸»æµç¨ï¼æ
å®ç°å±éä¿è¯ä¸æå¼å¸¸ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface MesTraceConsumerScanEventService { |
| | | |
| | | /** |
| | | * è®°å½ä¸æ¬¡æ¶è´¹è
æ«ç 访é®äºä»¶ |
| | | * |
| | | * @param traceCode æ«ç åå§å
容 |
| | | * @param normalizedCode è§£æåç追溯ç |
| | | * @param sourceType å
¥å£ç±»åï¼BAG/PALLET/BATCH |
| | | * @param batchId çäº§æ¹æ¬¡ç¼å·ï¼å¯ç©º |
| | | * @param success æ¯å¦æ¥è¯¢æå |
| | | * @param errorCode 失败ç¼ç ï¼æåä¼ null |
| | | * @return äºä»¶è®°å½ï¼å«ä¸»é®ä¸æ«ç å°åºï¼ï¼è®°å½å¤±è´¥è¿å null |
| | | */ |
| | | MesTraceConsumerScanEventDO recordScan(String traceCode, String normalizedCode, String sourceType, Long batchId, |
| | | boolean success, String errorCode); |
| | | |
| | | /** |
| | | * æ ¹æ® ID æ¥è¯¢äºä»¶ï¼ä¾æ ¡éª/è°è¯ä½¿ç¨ï¼ |
| | | * |
| | | * @param id ä¸»é® |
| | | * @return äºä»¶ |
| | | */ |
| | | MesTraceConsumerScanEventDO getEvent(Long id); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.consumer; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.crypto.digest.DigestUtil; |
| | | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| | | import cn.iocoder.yudao.framework.ip.core.Area; |
| | | import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; |
| | | import cn.iocoder.yudao.framework.ip.core.utils.IPUtils; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç 访é®äºä»¶ Service å®ç° |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | public class MesTraceConsumerScanEventServiceImpl implements MesTraceConsumerScanEventService { |
| | | |
| | | private static final int TRACE_CODE_MAX_LENGTH = 255; |
| | | private static final int NORMALIZED_CODE_MAX_LENGTH = 128; |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanEventMapper eventMapper; |
| | | |
| | | @Override |
| | | public MesTraceConsumerScanEventDO recordScan(String traceCode, String normalizedCode, String sourceType, |
| | | Long batchId, boolean success, String errorCode) { |
| | | try { |
| | | MesTraceConsumerScanEventDO event = new MesTraceConsumerScanEventDO(); |
| | | event.setTraceCode(limit(traceCode, TRACE_CODE_MAX_LENGTH)); |
| | | event.setNormalizedCode(limit(normalizedCode, NORMALIZED_CODE_MAX_LENGTH)); |
| | | event.setSourceType(sourceType); |
| | | event.setChannel(limit(channel(), 32)); |
| | | String[] region = region(ServletUtils.getClientIP()); |
| | | event.setProvince(limit(region[0], 64)); |
| | | event.setCity(limit(region[1], 64)); |
| | | event.setBatchId(batchId); |
| | | event.setSuccess(success); |
| | | event.setErrorCode(limit(errorCode, 64)); |
| | | event.setIpHash(hash(ServletUtils.getClientIP())); |
| | | event.setUserAgentHash(hash(ServletUtils.getUserAgent())); |
| | | event.setEventTime(LocalDateTime.now()); |
| | | eventMapper.insert(event); |
| | | return event; |
| | | } catch (Exception ex) { |
| | | log.warn("[recordScan][è®°å½æ¶è´¹è
æ«ç 访é®äºä»¶å¤±è´¥] traceCode={}", limit(traceCode, 64), ex); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public MesTraceConsumerScanEventDO getEvent(Long id) { |
| | | return eventMapper.selectById(id); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ® User-Agent è¯å«æ«ç å
¥å£æ¸ é |
| | | */ |
| | | private String channel() { |
| | | String ua = ServletUtils.getUserAgent(); |
| | | if (StrUtil.isBlank(ua)) { |
| | | return null; |
| | | } |
| | | String lower = ua.toLowerCase(); |
| | | if (lower.contains("micromessenger")) { |
| | | return "微信"; |
| | | } |
| | | if (lower.contains("alipayclient")) { |
| | | return "æ¯ä»å®"; |
| | | } |
| | | return "æµè§å¨"; |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®å®¢æ·ç«¯ IP è§£æç份ãåå¸ï¼è§£æå¤±è´¥è¿å空ï¼ä¸å½±åäºä»¶è®°å½ |
| | | */ |
| | | private String[] region(String ip) { |
| | | if (StrUtil.isBlank(ip)) { |
| | | return new String[]{null, null}; |
| | | } |
| | | Area area = IPUtils.getArea(ip); |
| | | if (area == null) { |
| | | return new String[]{null, null}; |
| | | } |
| | | String province = null; |
| | | String city = null; |
| | | Area node = area; |
| | | while (node != null) { |
| | | Integer type = node.getType(); |
| | | if (AreaTypeEnum.PROVINCE.getType().equals(type)) { |
| | | province = node.getName(); |
| | | } else if (AreaTypeEnum.CITY.getType().equals(type)) { |
| | | city = node.getName(); |
| | | } |
| | | node = node.getParent(); |
| | | } |
| | | return new String[]{province, city}; |
| | | } |
| | | |
| | | private String hash(String value) { |
| | | return value == null || value.isBlank() ? null : DigestUtil.sha256Hex(value); |
| | | } |
| | | |
| | | private String limit(String value, int maxLength) { |
| | | if (value == null || value.length() <= maxLength) { |
| | | return value; |
| | | } |
| | | return value.substring(0, maxLength); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | public interface MesTraceConsumerScanStatisticsService { |
| | | MesTraceConsumerScanSummaryRespVO getSummary(LocalDateTime startTime, LocalDateTime endTime); |
| | | List<MesTraceConsumerScanTrendRespVO> getTrend(LocalDateTime startTime, LocalDateTime endTime); |
| | | List<MesTraceConsumerScanDistributionRespVO> getDistribution(MesTraceConsumerScanDistributionReqVO reqVO); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanStatisticsMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.LocalTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Comparator; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.TreeMap; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç 访é®ç»è®¡ Service å®ç° |
| | | * <p> |
| | | * éç¨æ¥æç» + Java ä¾§èåï¼é¿å
ç»è®¡ SQL ä¸ MySQL/H2 æ¹è¨å·®å¼ï¼åæ¶ä¸æ´é²å¨æå SQLã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | public class MesTraceConsumerScanStatisticsServiceImpl implements MesTraceConsumerScanStatisticsService { |
| | | |
| | | /** |
| | | * åç»ç»´åº¦ç½ååï¼ä»
å
许æåºå®å段èåï¼éæ³ç»´åº¦ç´æ¥æç» |
| | | */ |
| | | private static final Map<String, Function<MesTraceConsumerScanEventDO, String>> GROUP_COLUMNS = Map.of( |
| | | "sourceType", e -> e.getSourceType(), |
| | | "success", e -> Boolean.TRUE.equals(e.getSuccess()) ? "1" : "0", |
| | | "channel", MesTraceConsumerScanEventDO::getChannel, |
| | | "province", MesTraceConsumerScanEventDO::getProvince, |
| | | "city", MesTraceConsumerScanEventDO::getCity, |
| | | "batchId", e -> e.getBatchId() == null ? null : String.valueOf(e.getBatchId())); |
| | | |
| | | /** |
| | | * 空åç»é®å ä½ç¬¦ï¼Collectors.groupingBy 䏿¥å null keyï¼å
ç»ä¸æ¿æ¢åå¨å±ç¤ºæ¶æ å°ä¸º"æªç¥" |
| | | */ |
| | | private static final String UNKNOWN_KEY = "__unknown__"; |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanStatisticsMapper mapper; |
| | | |
| | | @Override |
| | | public MesTraceConsumerScanSummaryRespVO getSummary(LocalDateTime startTime, LocalDateTime endTime) { |
| | | List<MesTraceConsumerScanEventDO> events = mapper.selectListByTime(startTime, endTime); |
| | | MesTraceConsumerScanSummaryRespVO vo = new MesTraceConsumerScanSummaryRespVO(); |
| | | vo.setTotal((long) events.size()); |
| | | vo.setSuccess(events.stream().filter(e -> Boolean.TRUE.equals(e.getSuccess())).count()); |
| | | vo.setFailed(events.stream().filter(e -> !Boolean.TRUE.equals(e.getSuccess())).count()); |
| | | vo.setUniqueBatchCount(events.stream().map(MesTraceConsumerScanEventDO::getBatchId) |
| | | .filter(java.util.Objects::nonNull).distinct().count()); |
| | | return vo; |
| | | } |
| | | |
| | | @Override |
| | | public List<MesTraceConsumerScanTrendRespVO> getTrend(LocalDateTime startTime, LocalDateTime endTime) { |
| | | List<MesTraceConsumerScanEventDO> events = mapper.selectListByTime(startTime, endTime); |
| | | // ææ¥æèå |
| | | Map<LocalDate, MesTraceConsumerScanTrendRespVO> data = new TreeMap<>(); |
| | | LocalDate min = null; |
| | | LocalDate max = null; |
| | | for (MesTraceConsumerScanEventDO event : events) { |
| | | LocalDate date = event.getEventTime().toLocalDate(); |
| | | MesTraceConsumerScanTrendRespVO vo = data.computeIfAbsent(date, d -> { |
| | | MesTraceConsumerScanTrendRespVO v = new MesTraceConsumerScanTrendRespVO(); |
| | | v.setDate(d.toString()); |
| | | v.setTotal(0L); |
| | | v.setSuccess(0L); |
| | | v.setFailed(0L); |
| | | return v; |
| | | }); |
| | | vo.setTotal(vo.getTotal() + 1); |
| | | if (Boolean.TRUE.equals(event.getSuccess())) { |
| | | vo.setSuccess(vo.getSuccess() + 1); |
| | | } else { |
| | | vo.setFailed(vo.getFailed() + 1); |
| | | } |
| | | if (min == null || date.isBefore(min)) { |
| | | min = date; |
| | | } |
| | | if (max == null || date.isAfter(max)) { |
| | | max = date; |
| | | } |
| | | } |
| | | // è¡¥é½åºé´å
æ æ°æ®çæ¥æï¼æ¶é´åæ°ä¸ºç©ºæ¶éåå·²ææ°æ®çæå°/æå¤§æ¥æåºé´ï¼ |
| | | LocalDate start = startTime != null ? startTime.toLocalDate() : min; |
| | | LocalDate end = endTime != null |
| | | ? (endTime.toLocalTime().equals(LocalTime.MIDNIGHT) ? endTime.toLocalDate().minusDays(1) : endTime.toLocalDate()) |
| | | : max; |
| | | if (start != null && end != null && !start.isAfter(end)) { |
| | | for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { |
| | | data.computeIfAbsent(day, d -> { |
| | | MesTraceConsumerScanTrendRespVO v = new MesTraceConsumerScanTrendRespVO(); |
| | | v.setDate(d.toString()); |
| | | v.setTotal(0L); |
| | | v.setSuccess(0L); |
| | | v.setFailed(0L); |
| | | return v; |
| | | }); |
| | | } |
| | | } |
| | | return new ArrayList<>(data.values()); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesTraceConsumerScanDistributionRespVO> getDistribution(MesTraceConsumerScanDistributionReqVO reqVO) { |
| | | Function<MesTraceConsumerScanEventDO, String> extractor = GROUP_COLUMNS.get(reqVO.getGroupBy()); |
| | | if (extractor == null) { |
| | | throw new IllegalArgumentException("䏿¯æçåç»ç»´åº¦"); |
| | | } |
| | | List<MesTraceConsumerScanEventDO> events = mapper.selectListByTime(reqVO.getStartTime(), reqVO.getEndTime()); |
| | | // ææ°éååºåç»ï¼groupingBy 䏿¥å null keyï¼å
ç»ä¸æ¿æ¢ä¸ºå ä½ç¬¦åèå |
| | | Map<String, Long> counts = events.stream().collect(Collectors.groupingBy(e -> { |
| | | String key = extractor.apply(e); |
| | | return key == null ? UNKNOWN_KEY : key; |
| | | }, Collectors.counting())); |
| | | return counts.entrySet().stream() |
| | | .sorted(Comparator.comparing(Map.Entry<String, Long>::getValue).reversed() |
| | | .thenComparing(Map.Entry.comparingByKey())) |
| | | .map(entry -> { |
| | | MesTraceConsumerScanDistributionRespVO vo = new MesTraceConsumerScanDistributionRespVO(); |
| | | String name = entry.getKey(); |
| | | vo.setName(UNKNOWN_KEY.equals(name) ? "æªç¥" : displayName(reqVO.getGroupBy(), name)); |
| | | vo.setCount(entry.getValue()); |
| | | return vo; |
| | | }) |
| | | .collect(Collectors.toCollection(ArrayList::new)); |
| | | } |
| | | |
| | | /** |
| | | * å¸å°ååºåå¯è¯»æ å°ï¼ä¿è¯ç»è®¡å£å¾ä¸å端å±ç¤ºä¸è´ |
| | | */ |
| | | private String displayName(String groupBy, String name) { |
| | | if ("success".equals(groupBy)) { |
| | | return "1".equals(name) ? "æå" : "失败"; |
| | | } |
| | | return name; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceAntiConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionHandleReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * çªè´§é¢è¦ Service æ¥å£ |
| | | * <p> |
| | | * æ¶è´¹è
æ«ç å°åºä¸è¢ç åºåºç»éåæå¨å°åºä¸ä¸è´æ¶èªå¨çæé¢è¦ï¼å¹¶æä¾ç®¡ç端稽æ¥ãå¤çä¸ç»è®¡è½åã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface MesTraceCrossRegionService { |
| | | |
| | | /** |
| | | * æ¶è´¹è
æ«ç æååè¿è¡çªè´§å¤å®ï¼å¼å¸¸æµåèªå¨çæé¢è¦ |
| | | * <p> |
| | | * å¤å®å¤±è´¥ä¸å½±å溯æºä¸»æµç¨ï¼å®ç°å±éä¿è¯ä¸æå¼å¸¸ã |
| | | * |
| | | * @param event æ¶è´¹è
æ«ç äºä»¶ï¼å«è§£æåç追溯ç ãå
¥å£ç±»åãæ«ç å°åºï¼ |
| | | */ |
| | | void checkAndCreateWarning(MesTraceConsumerScanEventDO event); |
| | | |
| | | /** |
| | | * è·å¾çªè´§é¢è¦å页 |
| | | * |
| | | * @param reqVO æ¥è¯¢æ¡ä»¶ |
| | | * @return çªè´§é¢è¦å页 |
| | | */ |
| | | PageResult<MesTraceCrossRegionWarningRespVO> getWarningPage(MesTraceCrossRegionWarningPageReqVO reqVO); |
| | | |
| | | /** |
| | | * å¤ççªè´§é¢è¦ï¼å·²å¤ç / 误æ¥ï¼ï¼ä»
å¾
å¤çç¶æå¯å¤ç |
| | | * |
| | | * @param reqVO å¤çä¿¡æ¯ |
| | | */ |
| | | void handleWarning(MesTraceCrossRegionHandleReqVO reqVO); |
| | | |
| | | /** |
| | | * è·å¾çªè´§é¢è¦æ±æ» |
| | | * |
| | | * @param startTime å¼å§æ¶é´ï¼å¯ç©º |
| | | * @param endTime ç»ææ¶é´ï¼å¯ç©º |
| | | * @return æ±æ»ä¿¡æ¯ |
| | | */ |
| | | MesTraceCrossRegionSummaryRespVO getWarningSummary(LocalDateTime startTime, LocalDateTime endTime); |
| | | |
| | | /** |
| | | * è·å¾çªè´§é¢è¦åå¸ç»è®¡ï¼æç份/åå¸/ç»éå/ç©æ/æ¹æ¬¡åç»ï¼ |
| | | * |
| | | * @param reqVO åç»æ¡ä»¶ |
| | | * @return åå¸ç»è®¡å表 |
| | | */ |
| | | List<MesTraceCrossRegionStatisticsRespVO> getWarningStatistics(MesTraceCrossRegionStatisticsReqVO reqVO); |
| | | |
| | | /** |
| | | * è·å¾é²çªè´§è§åé
ç½® |
| | | * |
| | | * @return è§åé
ç½® |
| | | */ |
| | | MesTraceAntiConfigRespVO getConfig(); |
| | | |
| | | /** |
| | | * æ´æ°é²çªè´§è§åé
ç½®ï¼å¤å®ç²åº¦åæ¢ï¼PROVINCE è·¨ç / CITY è·¨å¸ï¼ |
| | | * |
| | | * @param warningLevel å¤å®ç²åº¦ |
| | | */ |
| | | void updateConfig(String warningLevel); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.crossregion; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.IdUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.ip.core.Area; |
| | | import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; |
| | | import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceAntiConfigRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionHandleReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningRespVO; |
| | | 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.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion.MesTraceAntiConfigDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion.MesTraceCrossRegionWarningDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.crossregion.MesTraceAntiConfigMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.crossregion.MesTraceCrossRegionWarningMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceWarningStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceWarningTypeEnum; |
| | | 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; |
| | | import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_ANTI_CONFIG_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_ANTI_CONFIG_VALUE_INVALID; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_CROSS_REGION_STATUS_INVALID; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_CROSS_REGION_WARNING_NOT_EXISTS; |
| | | |
| | | /** |
| | | * çªè´§é¢è¦ Service å®ç°ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class MesTraceCrossRegionServiceImpl implements MesTraceCrossRegionService { |
| | | |
| | | private static final String CONFIG_KEY_WARNING_LEVEL = "warning_level"; |
| | | |
| | | /** |
| | | * åå¸ç»è®¡åç»ç½ååï¼ä»
å
许åºå®ä¸å¡å段åç»ï¼é²æ¢ä»»æ key ä¼ å
¥ã |
| | | */ |
| | | private static final Map<String, java.util.function.Function<MesTraceCrossRegionWarningDO, String>> STAT_GROUP_EXTRACTORS = |
| | | Map.ofEntries( |
| | | Map.entry("scanProvince", w -> w.getScanProvince()), |
| | | Map.entry("scanCity", w -> w.getScanCity()), |
| | | Map.entry("clientId", w -> w.getClientName()), |
| | | Map.entry("itemId", w -> w.getItemName()), |
| | | Map.entry("batchId", w -> w.getBatchCode())); |
| | | |
| | | @Resource |
| | | private MesTraceCrossRegionWarningMapper warningMapper; |
| | | @Resource |
| | | private MesTraceAntiConfigMapper antiConfigMapper; |
| | | @Resource |
| | | private MesWmProductSalesMapper productSalesMapper; |
| | | @Resource |
| | | private MesWmProductSalesDetailMapper productSalesDetailMapper; |
| | | @Resource |
| | | private MesProBagCodeService bagCodeService; |
| | | @Resource |
| | | private MesProPalletService palletService; |
| | | @Resource |
| | | private MesProBatchService batchService; |
| | | @Resource |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @Override |
| | | public void checkAndCreateWarning(MesTraceConsumerScanEventDO event) { |
| | | try { |
| | | doCheckAndCreateWarning(event); |
| | | } catch (Exception ex) { |
| | | // çªè´§å¤å®å¤±è´¥ä¸å½±åæ¶è´¹è
溯æºä¸»æµç¨ |
| | | log.warn("[checkAndCreateWarning][çªè´§å¤å®å¤±è´¥] traceCode={}", event == null ? null : event.getNormalizedCode(), ex); |
| | | } |
| | | } |
| | | |
| | | private void doCheckAndCreateWarning(MesTraceConsumerScanEventDO event) { |
| | | if (event == null) { |
| | | return; |
| | | } |
| | | // 1. ä»
è¢ç /æçå
¥å£å¯ç²¾ç¡®å®ä½åºåºå»åï¼æ¹æ¬¡ç è·³è¿ |
| | | String sourceType = event.getSourceType(); |
| | | boolean bag = "BAG".equals(sourceType); |
| | | boolean pallet = "PALLET".equals(sourceType); |
| | | if (!bag && !pallet) { |
| | | return; |
| | | } |
| | | // 2. æ«ç å°åºï¼IP å½å±å°ï¼è®°å½äºä»¶æ¶å·²è§£æï¼ï¼æ å°åºæ æ³å¤å® |
| | | String scanProvince = event.getProvince(); |
| | | String scanCity = event.getCity(); |
| | | if (StrUtil.isBlank(scanProvince) && StrUtil.isBlank(scanCity)) { |
| | | return; |
| | | } |
| | | // 3. 忥è¢ç /æçï¼å®ä½åºåºå |
| | | Long codeId; |
| | | Long batchId; |
| | | String code; |
| | | Long salesId; |
| | | if (bag) { |
| | | MesProBagCodeDO bagCode = bagCodeService.getBagCodeByCode(event.getNormalizedCode()); |
| | | if (bagCode == null) { |
| | | return; |
| | | } |
| | | codeId = bagCode.getId(); |
| | | code = bagCode.getCode(); |
| | | batchId = bagCode.getBatchId(); |
| | | salesId = bagCode.getSalesId(); |
| | | } else { |
| | | MesProPalletDO palletCode = palletService.getPalletByCode(event.getNormalizedCode()); |
| | | if (palletCode == null) { |
| | | return; |
| | | } |
| | | codeId = palletCode.getId(); |
| | | code = palletCode.getCode(); |
| | | batchId = palletCode.getBatchId(); |
| | | salesId = palletCode.getSalesId(); |
| | | } |
| | | // 4. è¢ç æªåååºåºåï¼å岿°æ®ï¼æ¶ï¼å
åºæ¥æç»è¡¨ |
| | | if (salesId == null) { |
| | | MesWmProductSalesDetailDO detail = bag |
| | | ? productSalesDetailMapper.selectLatestByBagCodeId(codeId) |
| | | : productSalesDetailMapper.selectLatestByPalletCodeId(codeId); |
| | | if (detail == null || detail.getSalesId() == null) { |
| | | return; // æªåºåºï¼æ çªè´§æ¯å¯¹åºç¡ |
| | | } |
| | | salesId = detail.getSalesId(); |
| | | } |
| | | // 5. æ¥åºåºåä¸ç»éåå°åº |
| | | MesWmProductSalesDO sales = productSalesMapper.selectById(salesId); |
| | | if (sales == null || sales.getClientId() == null) { |
| | | return; |
| | | } |
| | | CrmCustomerRespDTO customer = customerApi.getCustomer(sales.getClientId()); |
| | | if (customer == null || customer.getAreaId() == null) { |
| | | return; |
| | | } |
| | | String[] dealerRegion = resolveRegion(customer.getAreaId()); |
| | | String dealerProvince = dealerRegion[0]; |
| | | String dealerCity = dealerRegion[1]; |
| | | if (StrUtil.isBlank(dealerProvince)) { |
| | | return; |
| | | } |
| | | // 6. æå¤å®ç²åº¦æ¯å¯¹ |
| | | String warningType = matchWarningType(getWarningLevel(), scanProvince, scanCity, dealerProvince, dealerCity); |
| | | if (warningType == null) { |
| | | return; |
| | | } |
| | | // 7. åä½å¡«å
ç©æ/æ¹æ¬¡ä¿¡æ¯ |
| | | MesProBatchDO batch = batchId != null ? batchService.getBatch(batchId) : null; |
| | | MesMdmItemDO item = batch != null && batch.getItemId() != null ? mdmItemService.getItem(batch.getItemId()) : null; |
| | | // 8. åå
¥é¢è¦ |
| | | MesTraceCrossRegionWarningDO warning = MesTraceCrossRegionWarningDO.builder() |
| | | .warningNo(generateWarningNo()) |
| | | .bagCodeId(bag ? codeId : null) |
| | | .bagCode(bag ? code : null) |
| | | .palletCodeId(pallet ? codeId : null) |
| | | .palletCode(pallet ? code : null) |
| | | .batchId(batch != null ? batch.getId() : null) |
| | | .batchCode(batch != null ? batch.getCode() : null) |
| | | .itemId(item != null ? item.getId() : null) |
| | | .itemCode(item != null ? item.getCode() : null) |
| | | .itemName(item != null ? item.getName() : null) |
| | | .salesId(sales.getId()) |
| | | .salesCode(sales.getCode()) |
| | | .clientId(sales.getClientId()) |
| | | .clientName(sales.getClientName()) |
| | | .dealerProvince(dealerProvince) |
| | | .dealerCity(dealerCity) |
| | | .scanProvince(scanProvince) |
| | | .scanCity(scanCity) |
| | | .warningType(warningType) |
| | | .scanEventId(event.getId()) |
| | | .status(MesTraceWarningStatusEnum.PENDING.getStatus()) |
| | | .eventTime(event.getEventTime() != null ? event.getEventTime() : LocalDateTime.now()) |
| | | .build(); |
| | | warningMapper.insert(warning); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesTraceCrossRegionWarningRespVO> getWarningPage(MesTraceCrossRegionWarningPageReqVO reqVO) { |
| | | PageResult<MesTraceCrossRegionWarningDO> pageResult = warningMapper.selectPage(reqVO); |
| | | if (CollUtil.isEmpty(pageResult.getList())) { |
| | | return PageResult.empty(); |
| | | } |
| | | // æ¹éæ¥å¤ç人å§å |
| | | Set<Long> handleUserIds = convertSet(pageResult.getList(), MesTraceCrossRegionWarningDO::getHandleUserId); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(handleUserIds); |
| | | List<MesTraceCrossRegionWarningRespVO> list = BeanUtils.toBean(pageResult.getList(), |
| | | MesTraceCrossRegionWarningRespVO.class, vo -> { |
| | | MesTraceCrossRegionWarningDO warning = pageResult.getList().stream() |
| | | .filter(w -> ObjUtil.equal(w.getId(), vo.getId())).findFirst().orElse(null); |
| | | if (warning == null) { |
| | | return; |
| | | } |
| | | vo.setStatusName(statusName(warning.getStatus())); |
| | | vo.setWarningTypeName(warningTypeName(warning.getWarningType())); |
| | | MapUtils.findAndThen(userMap, warning.getHandleUserId(), |
| | | user -> vo.setHandleUserName(user.getNickname())); |
| | | }); |
| | | return new PageResult<>(list, pageResult.getTotal()); |
| | | } |
| | | |
| | | @Override |
| | | public void handleWarning(MesTraceCrossRegionHandleReqVO reqVO) { |
| | | MesTraceCrossRegionWarningDO warning = warningMapper.selectById(reqVO.getId()); |
| | | if (warning == null) { |
| | | throw exception(MES_TRACE_CROSS_REGION_WARNING_NOT_EXISTS); |
| | | } |
| | | if (!ObjUtil.equal(MesTraceWarningStatusEnum.PENDING.getStatus(), warning.getStatus())) { |
| | | throw exception(MES_TRACE_CROSS_REGION_STATUS_INVALID); |
| | | } |
| | | if (!ObjUtil.equal(MesTraceWarningStatusEnum.HANDLED.getStatus(), reqVO.getStatus()) |
| | | && !ObjUtil.equal(MesTraceWarningStatusEnum.FALSE_ALARM.getStatus(), reqVO.getStatus())) { |
| | | throw exception(MES_TRACE_CROSS_REGION_STATUS_INVALID); |
| | | } |
| | | warningMapper.updateById(new MesTraceCrossRegionWarningDO() |
| | | .setId(warning.getId()) |
| | | .setStatus(reqVO.getStatus()) |
| | | .setHandleRemark(reqVO.getHandleRemark()) |
| | | .setHandleUserId(SecurityFrameworkUtils.getLoginUserId()) |
| | | .setHandleTime(LocalDateTime.now())); |
| | | } |
| | | |
| | | @Override |
| | | public MesTraceCrossRegionSummaryRespVO getWarningSummary(LocalDateTime startTime, LocalDateTime endTime) { |
| | | List<MesTraceCrossRegionWarningDO> list = warningMapper.selectListByEventTimeBetween(startTime, endTime); |
| | | MesTraceCrossRegionSummaryRespVO vo = new MesTraceCrossRegionSummaryRespVO(); |
| | | long total = list.size(); |
| | | long pending = 0L; |
| | | long handled = 0L; |
| | | long falseAlarm = 0L; |
| | | long crossProvince = 0L; |
| | | long crossCity = 0L; |
| | | for (MesTraceCrossRegionWarningDO w : list) { |
| | | if (ObjUtil.equal(MesTraceWarningStatusEnum.PENDING.getStatus(), w.getStatus())) { |
| | | pending++; |
| | | } else if (ObjUtil.equal(MesTraceWarningStatusEnum.HANDLED.getStatus(), w.getStatus())) { |
| | | handled++; |
| | | } else if (ObjUtil.equal(MesTraceWarningStatusEnum.FALSE_ALARM.getStatus(), w.getStatus())) { |
| | | falseAlarm++; |
| | | } |
| | | if (ObjUtil.equal(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType(), w.getWarningType())) { |
| | | crossProvince++; |
| | | } else if (ObjUtil.equal(MesTraceWarningTypeEnum.CROSS_CITY.getType(), w.getWarningType())) { |
| | | crossCity++; |
| | | } |
| | | } |
| | | vo.setTotal(total).setPending(pending).setHandled(handled).setFalseAlarm(falseAlarm) |
| | | .setCrossProvince(crossProvince).setCrossCity(crossCity); |
| | | return vo; |
| | | } |
| | | |
| | | @Override |
| | | public List<MesTraceCrossRegionStatisticsRespVO> getWarningStatistics(MesTraceCrossRegionStatisticsReqVO reqVO) { |
| | | java.util.function.Function<MesTraceCrossRegionWarningDO, String> extractor = STAT_GROUP_EXTRACTORS.get(reqVO.getGroupBy()); |
| | | if (extractor == null) { |
| | | throw new IllegalArgumentException("䏿¯æçåç»ç»´åº¦"); |
| | | } |
| | | List<MesTraceCrossRegionWarningDO> list = warningMapper.selectListByEventTimeBetween(reqVO.getStartTime(), reqVO.getEndTime()); |
| | | Map<String, Long> counts = new LinkedHashMap<>(); |
| | | for (MesTraceCrossRegionWarningDO w : list) { |
| | | String key = StrUtil.nullToDefault(extractor.apply(w), "æªç¥"); |
| | | counts.merge(key, 1L, Long::sum); |
| | | } |
| | | return counts.entrySet().stream() |
| | | .sorted(Map.Entry.<String, Long>comparingByValue().reversed()) |
| | | .map(entry -> new MesTraceCrossRegionStatisticsRespVO().setName(entry.getKey()).setCount(entry.getValue())) |
| | | .toList(); |
| | | } |
| | | |
| | | @Override |
| | | public MesTraceAntiConfigRespVO getConfig() { |
| | | MesTraceAntiConfigDO config = antiConfigMapper.selectByConfigKey(CONFIG_KEY_WARNING_LEVEL); |
| | | if (config == null) { |
| | | throw exception(MES_TRACE_ANTI_CONFIG_NOT_EXISTS); |
| | | } |
| | | return new MesTraceAntiConfigRespVO().setWarningLevel(config.getConfigValue()).setRemark(config.getRemark()); |
| | | } |
| | | |
| | | @Override |
| | | public void updateConfig(String warningLevel) { |
| | | if (!ObjUtil.equal("PROVINCE", warningLevel) && !ObjUtil.equal("CITY", warningLevel)) { |
| | | throw exception(MES_TRACE_ANTI_CONFIG_VALUE_INVALID); |
| | | } |
| | | MesTraceAntiConfigDO config = antiConfigMapper.selectByConfigKey(CONFIG_KEY_WARNING_LEVEL); |
| | | if (config == null) { |
| | | throw exception(MES_TRACE_ANTI_CONFIG_NOT_EXISTS); |
| | | } |
| | | antiConfigMapper.updateById(new MesTraceAntiConfigDO().setId(config.getId()).setConfigValue(warningLevel)); |
| | | } |
| | | |
| | | // ==================== ç§ææ¹æ³ ==================== |
| | | |
| | | /** |
| | | * 读åå¤å®ç²åº¦ï¼PROVINCE è·¨ç / CITY è·¨å¸ï¼é
置缺失æ¶é»è®¤æçå¤å® |
| | | */ |
| | | private String getWarningLevel() { |
| | | MesTraceAntiConfigDO config = antiConfigMapper.selectByConfigKey(CONFIG_KEY_WARNING_LEVEL); |
| | | return config != null ? StrUtil.nullToDefault(config.getConfigValue(), "PROVINCE") : "PROVINCE"; |
| | | } |
| | | |
| | | /** |
| | | * æå¤å®ç²åº¦æ¯å¯¹æ«ç å°åºä¸ç»éåå°åºï¼è¿åå½ä¸é¢è¦ç±»åï¼æªå½ä¸è¿å null |
| | | */ |
| | | private String matchWarningType(String level, String scanProvince, String scanCity, |
| | | String dealerProvince, String dealerCity) { |
| | | if ("CITY".equals(level)) { |
| | | // åå¸ç²åº¦ï¼æ«ç å¸ä¸ç»éåå¸ä¸ä¸è´ï¼é忹齿åå¸ï¼å¦åéåççº§å¤æï¼ |
| | | if (StrUtil.isNotBlank(scanCity) && StrUtil.isNotBlank(dealerCity) && !scanCity.equals(dealerCity)) { |
| | | return MesTraceWarningTypeEnum.CROSS_CITY.getType(); |
| | | } |
| | | if (StrUtil.isNotBlank(scanProvince) && StrUtil.isNotBlank(dealerProvince) && !scanProvince.equals(dealerProvince)) { |
| | | return MesTraceWarningTypeEnum.CROSS_PROVINCE.getType(); |
| | | } |
| | | return null; |
| | | } |
| | | // ç级ç²åº¦ |
| | | if (StrUtil.isNotBlank(scanProvince) && StrUtil.isNotBlank(dealerProvince) && !scanProvince.equals(dealerProvince)) { |
| | | return MesTraceWarningTypeEnum.CROSS_PROVINCE.getType(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ® Area id åä¸è§£æçå¸åºï¼ä¸æ¶è´¹è
æ«ç äºä»¶ IP å½å±å°è§£æå£å¾ä¸è´ï¼ä¿è¯å¯æ¯è¾ï¼ |
| | | */ |
| | | private String[] resolveRegion(Integer areaId) { |
| | | if (areaId == null) { |
| | | return new String[]{null, null}; |
| | | } |
| | | Area area = AreaUtils.getArea(areaId); |
| | | if (area == null) { |
| | | return new String[]{null, null}; |
| | | } |
| | | String province = null; |
| | | String city = null; |
| | | Area node = area; |
| | | while (node != null) { |
| | | Integer type = node.getType(); |
| | | if (AreaTypeEnum.PROVINCE.getType().equals(type)) { |
| | | province = node.getName(); |
| | | } else if (AreaTypeEnum.CITY.getType().equals(type)) { |
| | | city = node.getName(); |
| | | } |
| | | node = node.getParent(); |
| | | } |
| | | return new String[]{province, city}; |
| | | } |
| | | |
| | | /** |
| | | * çæé¢è¦ç¼å·ï¼WARN + æ¶é´æ³ + éæºåºå· |
| | | */ |
| | | private String generateWarningNo() { |
| | | return "WARN" + java.time.LocalDateTime.now() |
| | | .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) |
| | | + StrUtil.sub(IdUtil.fastSimpleUUID(), 0, 6).toUpperCase(); |
| | | } |
| | | |
| | | private String statusName(Integer status) { |
| | | if (status == null) { |
| | | return ""; |
| | | } |
| | | for (MesTraceWarningStatusEnum e : MesTraceWarningStatusEnum.values()) { |
| | | if (ObjUtil.equal(e.getStatus(), status)) { |
| | | return e.getName(); |
| | | } |
| | | } |
| | | return String.valueOf(status); |
| | | } |
| | | |
| | | private String warningTypeName(String type) { |
| | | if (type == null) { |
| | | return ""; |
| | | } |
| | | for (MesTraceWarningTypeEnum e : MesTraceWarningTypeEnum.values()) { |
| | | if (ObjUtil.equal(e.getType(), type)) { |
| | | return e.getName(); |
| | | } |
| | | } |
| | | return type; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.integrity; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityRecordPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityVerifyRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.integrity.MesTraceIntegrityRecordDO; |
| | | |
| | | /** |
| | | * æ°æ®å®æ´æ§ Service æ¥å£ |
| | | * <p> |
| | | * 卿¹æ¬¡å建ãéå®åºåºãæ¶è´¹è
æ«ç çå
³é®äºä»¶åçæ¶ï¼å¯¹ä¸å¯åæ ¸å¿åæ®µçæ SHA-256 æè¦å¹¶åå¨ï¼ |
| | | * æä¾éç®æ¯å¯¹è½åï¼ä¾ç®¡ç端åç°äºå篡æ¹ãæè¦çæå¤±è´¥ä¸å¾é»æä¸å¡ä¸»æµç¨ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface MesTraceIntegrityService { |
| | | |
| | | /** |
| | | * çæå¹¶ä¿åæ¹æ¬¡å®æ´æ§æè¦ï¼code|itemId|lineCode|produceDate|planQuantityï¼ |
| | | * |
| | | * @param batchId æ¹æ¬¡ç¼å· |
| | | */ |
| | | void recordBatchIntegrity(Long batchId); |
| | | |
| | | /** |
| | | * çæå¹¶ä¿åéå®åºåºå®æ´æ§æè¦ï¼code|clientId|salesDate|æç»è¢ç /æçç idåè¡¨ï¼ |
| | | * |
| | | * @param salesId éå®åºåºåç¼å· |
| | | */ |
| | | void recordSalesIntegrity(Long salesId); |
| | | |
| | | /** |
| | | * çæå¹¶ä¿åæ¶è´¹è
æ«ç äºä»¶å®æ´æ§æè¦ï¼normalizedCode|sourceType|province|city|success|eventTimeï¼ |
| | | * |
| | | * @param event æ¶è´¹è
æ«ç äºä»¶ |
| | | */ |
| | | void recordScanIntegrity(MesTraceConsumerScanEventDO event); |
| | | |
| | | /** |
| | | * æ ¡éªæ°æ®å®æ´æ§ï¼éç®å½åæ°æ®æè¦ï¼ä¸åå¨æè¦æ¯å¯¹ |
| | | * |
| | | * @param bizType ä¸å¡ç±»åï¼BATCH / SALES / SCAN |
| | | * @param bizId ä¸å¡è®°å½ç¼å· |
| | | * @return æ ¡éªç»æ |
| | | */ |
| | | MesTraceIntegrityVerifyRespVO verify(String bizType, Long bizId); |
| | | |
| | | /** |
| | | * è·å¾æ°æ®å®æ´æ§è®°å½å页 |
| | | * |
| | | * @param reqVO æ¥è¯¢æ¡ä»¶ |
| | | * @return è®°å½å页 |
| | | */ |
| | | PageResult<MesTraceIntegrityRecordDO> getIntegrityPage(MesTraceIntegrityRecordPageReqVO reqVO); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.integrity; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.crypto.digest.DigestUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityRecordPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityVerifyRespVO; |
| | | 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.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.integrity.MesTraceIntegrityRecordDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.integrity.MesTraceIntegrityRecordMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceIntegrityBizTypeEnum; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Comparator; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_INTEGRITY_BIZ_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_INTEGRITY_BIZ_TYPE_INVALID; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_INTEGRITY_RECORD_NOT_EXISTS; |
| | | |
| | | /** |
| | | * æ°æ®å®æ´æ§ Service å®ç°ç±» |
| | | * <p> |
| | | * æè¦ä»
è¦çä¸å¯åæ ¸å¿å段ï¼é¿å
æ£å¸¸ä¸å¡æµè½¬ï¼è稿âç产ä¸â宿ãåºåºåç¶ææ´æ°çï¼å¯¼è´è¯¯æ¥ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class MesTraceIntegrityServiceImpl implements MesTraceIntegrityService { |
| | | |
| | | @Resource |
| | | private MesTraceIntegrityRecordMapper integrityRecordMapper; |
| | | @Resource |
| | | private MesProBatchMapper batchMapper; |
| | | @Resource |
| | | private MesProBagCodeMapper bagCodeMapper; |
| | | @Resource |
| | | private MesWmProductSalesMapper productSalesMapper; |
| | | @Resource |
| | | private MesWmProductSalesDetailMapper productSalesDetailMapper; |
| | | @Resource |
| | | private MesTraceConsumerScanEventMapper scanEventMapper; |
| | | |
| | | @Override |
| | | public void recordBatchIntegrity(Long batchId) { |
| | | try { |
| | | MesProBatchDO batch = batchMapper.selectById(batchId); |
| | | if (batch == null) { |
| | | return; |
| | | } |
| | | upsertRecord(MesTraceIntegrityBizTypeEnum.BATCH.getType(), batch.getId(), batch.getCode(), batchHash(batch)); |
| | | } catch (Exception ex) { |
| | | log.warn("[recordBatchIntegrity][æ¹æ¬¡æè¦çæå¤±è´¥] batchId={}", batchId, ex); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void recordSalesIntegrity(Long salesId) { |
| | | try { |
| | | MesWmProductSalesDO sales = productSalesMapper.selectById(salesId); |
| | | if (sales == null) { |
| | | return; |
| | | } |
| | | String hash = salesHash(sales, productSalesDetailMapper.selectListBySalesId(salesId)); |
| | | upsertRecord(MesTraceIntegrityBizTypeEnum.SALES.getType(), sales.getId(), sales.getCode(), hash); |
| | | } catch (Exception ex) { |
| | | log.warn("[recordSalesIntegrity][åºåºæè¦çæå¤±è´¥] salesId={}", salesId, ex); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void recordScanIntegrity(MesTraceConsumerScanEventDO event) { |
| | | if (event == null || event.getId() == null) { |
| | | return; |
| | | } |
| | | try { |
| | | upsertRecord(MesTraceIntegrityBizTypeEnum.SCAN.getType(), event.getId(), event.getNormalizedCode(), scanHash(event)); |
| | | } catch (Exception ex) { |
| | | log.warn("[recordScanIntegrity][æ«ç æè¦çæå¤±è´¥] scanEventId={}", event.getId(), ex); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public MesTraceIntegrityVerifyRespVO verify(String bizType, Long bizId) { |
| | | if (!MesTraceIntegrityBizTypeEnum.isValid(bizType)) { |
| | | throw exception(MES_TRACE_INTEGRITY_BIZ_TYPE_INVALID); |
| | | } |
| | | MesTraceIntegrityRecordDO record = integrityRecordMapper.selectByBiz(bizType, bizId); |
| | | if (record == null) { |
| | | throw exception(MES_TRACE_INTEGRITY_RECORD_NOT_EXISTS); |
| | | } |
| | | String currentHash; |
| | | String bizCode = record.getBizCode(); |
| | | String bagSetHash = null; |
| | | if (MesTraceIntegrityBizTypeEnum.BATCH.getType().equals(bizType)) { |
| | | MesProBatchDO batch = batchMapper.selectById(bizId); |
| | | if (batch == null) { |
| | | throw exception(MES_TRACE_INTEGRITY_BIZ_NOT_EXISTS); |
| | | } |
| | | bizCode = batch.getCode(); |
| | | currentHash = batchHash(batch); |
| | | bagSetHash = bagSetHash(bizId); |
| | | } else if (MesTraceIntegrityBizTypeEnum.SALES.getType().equals(bizType)) { |
| | | MesWmProductSalesDO sales = productSalesMapper.selectById(bizId); |
| | | if (sales == null) { |
| | | throw exception(MES_TRACE_INTEGRITY_BIZ_NOT_EXISTS); |
| | | } |
| | | bizCode = sales.getCode(); |
| | | currentHash = salesHash(sales, productSalesDetailMapper.selectListBySalesId(bizId)); |
| | | } else { |
| | | MesTraceConsumerScanEventDO event = scanEventMapper.selectById(bizId); |
| | | if (event == null) { |
| | | throw exception(MES_TRACE_INTEGRITY_BIZ_NOT_EXISTS); |
| | | } |
| | | bizCode = event.getNormalizedCode(); |
| | | currentHash = scanHash(event); |
| | | } |
| | | boolean matched = StrUtil.equals(record.getContentHash(), currentHash); |
| | | return new MesTraceIntegrityVerifyRespVO() |
| | | .setBizType(bizType) |
| | | .setBizId(bizId) |
| | | .setBizCode(bizCode) |
| | | .setMatched(matched) |
| | | .setStoredHash(record.getContentHash()) |
| | | .setCurrentHash(currentHash) |
| | | .setBagSetHash(bagSetHash) |
| | | .setMessage(matched ? "æ°æ®å®æ´ï¼æªè¢«ç¯¡æ¹" : "æ°æ®ä¸ä¸è´ï¼å¯è½åå¨ç¯¡æ¹"); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesTraceIntegrityRecordDO> getIntegrityPage(MesTraceIntegrityRecordPageReqVO reqVO) { |
| | | return integrityRecordMapper.selectPage(reqVO); |
| | | } |
| | | |
| | | // ==================== ç§ææ¹æ³ ==================== |
| | | |
| | | /** |
| | | * æ¹æ¬¡æè¦ï¼ä»
è¦çå建åä¸ååæ´çæ ¸å¿å段ï¼ä¸å«ç¶æãè¢ç æ°éçå¯ååæ®µ |
| | | */ |
| | | private String batchHash(MesProBatchDO batch) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(StrUtil.nullToEmpty(batch.getCode())).append('|') |
| | | .append(nvl(batch.getItemId())).append('|') |
| | | .append(StrUtil.nullToEmpty(batch.getLineCode())).append('|') |
| | | .append(nvl(batch.getProduceDate())).append('|') |
| | | .append(nvl(batch.getPlanQuantity())); |
| | | return DigestUtil.sha256Hex(sb.toString()); |
| | | } |
| | | |
| | | /** |
| | | * åºåºæè¦ï¼æç»è¢ç /æçç id æåºåæ¼å
¥ï¼ä¿è¯æç»é¡ºåºä¸å½±åæè¦ |
| | | */ |
| | | private String salesHash(MesWmProductSalesDO sales, List<MesWmProductSalesDetailDO> details) { |
| | | List<Long> codeIds = details.stream() |
| | | .map(d -> d.getBagCodeId() != null ? d.getBagCodeId() : d.getPalletCodeId()) |
| | | .filter(Objects::nonNull) |
| | | .sorted(Comparator.naturalOrder()) |
| | | .toList(); |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(StrUtil.nullToEmpty(sales.getCode())).append('|') |
| | | .append(nvl(sales.getClientId())).append('|') |
| | | .append(nvl(sales.getSalesDate())).append('|') |
| | | .append(codeIds); |
| | | return DigestUtil.sha256Hex(sb.toString()); |
| | | } |
| | | |
| | | /** |
| | | * æ«ç äºä»¶æè¦ï¼äºä»¶ä»
insert 䏿¹ï¼å
¨å段å®å
¨ |
| | | */ |
| | | private String scanHash(MesTraceConsumerScanEventDO event) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(StrUtil.nullToEmpty(event.getNormalizedCode())).append('|') |
| | | .append(StrUtil.nullToEmpty(event.getSourceType())).append('|') |
| | | .append(StrUtil.nullToEmpty(event.getProvince())).append('|') |
| | | .append(StrUtil.nullToEmpty(event.getCity())).append('|') |
| | | .append(nvl(event.getSuccess())).append('|') |
| | | .append(nvl(event.getEventTime())); |
| | | return DigestUtil.sha256Hex(sb.toString()); |
| | | } |
| | | |
| | | /** |
| | | * æ¹æ¬¡å½åè¢ç éåæè¦ï¼å®æ¶è®¡ç®ï¼ä¾äººå·¥æ¯å¯¹è¢ç é²ç¯¡æ¹ï¼ä¸å
¥åºï¼ |
| | | */ |
| | | private String bagSetHash(Long batchId) { |
| | | List<String> codes = bagCodeMapper.selectListByBatchId(batchId).stream() |
| | | .map(MesProBagCodeDO::getCode) |
| | | .filter(StrUtil::isNotBlank) |
| | | .sorted() |
| | | .toList(); |
| | | return DigestUtil.sha256Hex(String.join("|", codes)); |
| | | } |
| | | |
| | | /** |
| | | * æè¦ä¸å卿¶æ´æ°ï¼å¦åè¦çï¼åä¸ä¸å¡è®°å½åªä¿çææ°æè¦ï¼ |
| | | */ |
| | | private void upsertRecord(String bizType, Long bizId, String bizCode, String hash) { |
| | | MesTraceIntegrityRecordDO record = integrityRecordMapper.selectByBiz(bizType, bizId); |
| | | if (record == null) { |
| | | integrityRecordMapper.insert(MesTraceIntegrityRecordDO.builder() |
| | | .bizType(bizType) |
| | | .bizId(bizId) |
| | | .bizCode(bizCode) |
| | | .contentHash(hash) |
| | | .eventTime(LocalDateTime.now()) |
| | | .build()); |
| | | } else { |
| | | integrityRecordMapper.updateById(MesTraceIntegrityRecordDO.builder() |
| | | .id(record.getId()) |
| | | .bizCode(bizCode) |
| | | .contentHash(hash) |
| | | .eventTime(LocalDateTime.now()) |
| | | .build()); |
| | | } |
| | | } |
| | | |
| | | private String nvl(Object value) { |
| | | return value == null ? "" : String.valueOf(value); |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | |
| | | @Resource |
| | | @Lazy |
| | | private MesQcOqcService oqcService; |
| | | |
| | | @Resource |
| | | private MesTraceIntegrityService integrityService; |
| | | |
| | | @Resource |
| | | private ErpSaleOrderApi saleOrderApi; |
| | |
| | | // 3. éåæææç»ï¼å建åºåäºå¡ï¼æ£ååºå + è®°å½æµæ°´ï¼ |
| | | createTransactionList(sales); |
| | | |
| | | // 4. æ´æ°åºåºåç¶æ |
| | | // 4. åºåç»éåä¸å®é
åºåºæä½ä¿¡æ¯ |
| | | CrmCustomerRespDTO customer = customerApi.getCustomer(sales.getClientId()); |
| | | productSalesMapper.updateById(new MesWmProductSalesDO() |
| | | .setId(id).setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus())); |
| | | .setId(id) |
| | | .setClientName(customer != null ? customer.getName() : null) |
| | | .setOutboundTime(java.time.LocalDateTime.now()) |
| | | .setOutboundUserId(SecurityFrameworkUtils.getLoginUserId()) |
| | | .setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus())); |
| | | |
| | | // 5. æ´æ°å
³èçåè´§éç¥ååºåºæ°éåç¶æ |
| | | if (sales.getNoticeId() != null) { |
| | |
| | | |
| | | // 6. åå ERP éå®è®¢åçåºåºæ°éåç¶æ |
| | | updateErpSaleOrderOutCount(sales); |
| | | |
| | | // 7. ååè¢ç /æçåºåºå»åï¼ä¾çªè´§ç¨½æ¥å®ä½ç»éåå°åºï¼ï¼å¹¶çæåºåºå®æ´æ§æè¦ |
| | | List<MesWmProductSalesDetailDO> outDetails = productSalesDetailService.getProductSalesDetailListBySalesId(id); |
| | | for (MesWmProductSalesDetailDO detail : outDetails) { |
| | | if (detail.getBagCodeId() != null) { |
| | | bagCodeService.markOutbound(detail.getBagCodeId(), id); |
| | | } |
| | | if (detail.getPalletCodeId() != null) { |
| | | palletService.markOutbound(detail.getPalletCodeId(), id); |
| | | } |
| | | } |
| | | integrityService.recordSalesIntegrity(id); |
| | | } |
| | | |
| | | /** |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.context.annotation.Import; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertNull; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | | /** |
| | | * {@link MesTraceConsumerScanEventServiceImpl} çåå
æµè¯ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Import(MesTraceConsumerScanEventServiceImpl.class) |
| | | public class MesTraceConsumerScanEventServiceImplTest extends BaseDbUnitTest { |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanEventService eventService; |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanEventMapper eventMapper; |
| | | |
| | | /** |
| | | * æ¥è¯¢åæå
¥çç¬¬ä¸æ¡äºä»¶ï¼id ç±åºå/éªè±ç®æ³çæï¼ä¸åºå®ä¸º 1ï¼ |
| | | */ |
| | | private MesTraceConsumerScanEventDO getFirstEvent() { |
| | | List<MesTraceConsumerScanEventDO> list = eventMapper.selectList(null); |
| | | return list.isEmpty() ? null : list.get(0); |
| | | } |
| | | |
| | | @Test |
| | | public void testRecordScan_æåäºä»¶è½åº() { |
| | | // æ 请æ±ä¸ä¸ææ¶ IP/UA/æ¸ é为空ï¼ä½äºä»¶ä»åºè½åºä¸ä¸æå¼å¸¸ |
| | | eventService.recordScan("PC-RAW-CODE", "PC-CODE-001", "BAG", 100L, true, null); |
| | | |
| | | MesTraceConsumerScanEventDO event = getFirstEvent(); |
| | | assertNotNull(event); |
| | | assertEquals("PC-RAW-CODE", event.getTraceCode()); |
| | | assertEquals("PC-CODE-001", event.getNormalizedCode()); |
| | | assertEquals("BAG", event.getSourceType()); |
| | | assertEquals(100L, event.getBatchId()); |
| | | assertTrue(Boolean.TRUE.equals(event.getSuccess())); |
| | | assertNull(event.getErrorCode()); |
| | | assertNull(event.getIpHash()); // æ 请æ±ä¸ä¸ææ ææ IPï¼ä¹ä¸åºä¿å |
| | | assertNull(event.getChannel()); |
| | | assertNotNull(event.getEventTime()); |
| | | } |
| | | |
| | | @Test |
| | | public void testRecordScan_失败äºä»¶è½åº() { |
| | | eventService.recordScan("NOT-EXIST", null, null, null, false, "MES_TRACE_CODE_NOT_FOUND"); |
| | | |
| | | MesTraceConsumerScanEventDO event = getFirstEvent(); |
| | | assertNotNull(event); |
| | | assertFalse(Boolean.TRUE.equals(event.getSuccess())); |
| | | assertEquals("MES_TRACE_CODE_NOT_FOUND", event.getErrorCode()); |
| | | assertNull(event.getSourceType()); |
| | | assertNull(event.getBatchId()); |
| | | } |
| | | |
| | | @Test |
| | | public void testRecordScan_è¶
é¿ç æªæ() { |
| | | String longCode = "X".repeat(500); |
| | | eventService.recordScan(longCode, longCode, "BATCH", null, true, null); |
| | | |
| | | MesTraceConsumerScanEventDO event = getFirstEvent(); |
| | | assertNotNull(event); |
| | | // traceCode æªæå° 255ãnormalizedCode æªæå° 128 |
| | | assertEquals(255, event.getTraceCode().length()); |
| | | assertEquals(128, event.getNormalizedCode().length()); |
| | | } |
| | | |
| | | @Test |
| | | public void testRecordScan_è®°å½å¼å¸¸ä¸å½±åæ¥å£() { |
| | | // recordScan å
é¨åæå¼å¸¸ï¼äºä»¶ä»åºæ£å¸¸è½åº |
| | | eventService.recordScan("CODE", "CODE", "BATCH", null, true, null); |
| | | assertNotNull(getFirstEvent()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.consumer; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.context.annotation.Import; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | |
| | | /** |
| | | * {@link MesTraceConsumerScanStatisticsServiceImpl} çåå
æµè¯ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Import(MesTraceConsumerScanStatisticsServiceImpl.class) |
| | | public class MesTraceConsumerScanStatisticsServiceImplTest extends BaseDbUnitTest { |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanStatisticsService statisticsService; |
| | | |
| | | @Resource |
| | | private MesTraceConsumerScanEventMapper eventMapper; |
| | | |
| | | private MesTraceConsumerScanEventDO buildEvent(String traceCode, String sourceType, String channel, |
| | | String province, Long batchId, boolean success, |
| | | LocalDateTime eventTime) { |
| | | return MesTraceConsumerScanEventDO.builder() |
| | | .traceCode(traceCode).normalizedCode(traceCode).sourceType(sourceType) |
| | | .channel(channel).province(province).batchId(batchId).success(success) |
| | | .eventTime(eventTime).build(); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetSummary_ç»è®¡å£å¾() { |
| | | LocalDateTime t1 = LocalDateTime.of(2026, 9, 1, 10, 0); |
| | | LocalDateTime t2 = LocalDateTime.of(2026, 9, 2, 11, 0); |
| | | eventMapper.insert(buildEvent("B1", "BAG", "微信", "æ±èç", 100L, true, t1)); |
| | | eventMapper.insert(buildEvent("B2", "BAG", "æµè§å¨", "æ±èç", 100L, true, t2)); |
| | | eventMapper.insert(buildEvent("B3", "BATCH", null, null, null, false, t2)); |
| | | |
| | | MesTraceConsumerScanSummaryRespVO vo = statisticsService.getSummary( |
| | | LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 3, 0, 0)); |
| | | |
| | | assertNotNull(vo); |
| | | assertEquals(3L, vo.getTotal()); |
| | | assertEquals(2L, vo.getSuccess()); |
| | | assertEquals(1L, vo.getFailed()); |
| | | assertEquals(1L, vo.getUniqueBatchCount()); // ä»
batchId é空ä¸å»é |
| | | } |
| | | |
| | | @Test |
| | | public void testGetTrend_è¡¥é½æ æ°æ®æ¥æ() { |
| | | // 9 æ 1 æ¥ä¸¤æ¡ã9 æ 3 æ¥ä¸æ¡ï¼9 æ 2 æ¥æ æ°æ®åºè¡¥ 0 |
| | | eventMapper.insert(buildEvent("B1", "BAG", "微信", null, 100L, true, |
| | | LocalDateTime.of(2026, 9, 1, 9, 0))); |
| | | eventMapper.insert(buildEvent("B2", "BATCH", null, null, null, false, |
| | | LocalDateTime.of(2026, 9, 1, 10, 0))); |
| | | eventMapper.insert(buildEvent("B3", "BAG", "æµè§å¨", null, 100L, true, |
| | | LocalDateTime.of(2026, 9, 3, 9, 0))); |
| | | |
| | | List<MesTraceConsumerScanTrendRespVO> list = statisticsService.getTrend( |
| | | LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 4, 0, 0)); |
| | | |
| | | assertNotNull(list); |
| | | assertEquals(3, list.size()); |
| | | assertEquals("2026-09-01", list.get(0).getDate()); |
| | | assertEquals(2L, list.get(0).getTotal()); |
| | | assertEquals("2026-09-02", list.get(1).getDate()); |
| | | assertEquals(0L, list.get(1).getTotal()); // è¡¥ 0 |
| | | assertEquals("2026-09-03", list.get(2).getDate()); |
| | | assertEquals(1L, list.get(2).getTotal()); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetDistribution_bySourceType() { |
| | | eventMapper.insert(buildEvent("B1", "BAG", null, null, 100L, true, |
| | | LocalDateTime.of(2026, 9, 1, 9, 0))); |
| | | eventMapper.insert(buildEvent("B2", "PALLET", null, null, 100L, true, |
| | | LocalDateTime.of(2026, 9, 1, 10, 0))); |
| | | eventMapper.insert(buildEvent("B3", "PALLET", null, null, null, false, |
| | | LocalDateTime.of(2026, 9, 1, 11, 0))); |
| | | |
| | | MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); |
| | | reqVO.setGroupBy("sourceType"); |
| | | List<MesTraceConsumerScanDistributionRespVO> list = statisticsService.getDistribution(reqVO); |
| | | |
| | | assertNotNull(list); |
| | | // PALLET 2 æ¡ > BAG 1 æ¡ï¼æ count ååºï¼ |
| | | assertEquals("PALLET", list.get(0).getName()); |
| | | assertEquals(2L, list.get(0).getCount()); |
| | | assertEquals("BAG", list.get(1).getName()); |
| | | assertEquals(1L, list.get(1).getCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetDistribution_nullKeyæ å°æªç¥() { |
| | | // è§£æå¤±è´¥çäºä»¶ sourceType 为 nullï¼ä¸å¾å groupBy ç©ºé®æ NPEï¼åºèå为"æªç¥" |
| | | eventMapper.insert(buildEvent("B1", "BAG", null, null, 100L, true, |
| | | LocalDateTime.of(2026, 9, 1, 9, 0))); |
| | | eventMapper.insert(buildEvent("B2", null, null, null, null, false, |
| | | LocalDateTime.of(2026, 9, 1, 10, 0))); |
| | | eventMapper.insert(buildEvent("B3", null, null, null, null, false, |
| | | LocalDateTime.of(2026, 9, 1, 11, 0))); |
| | | |
| | | MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); |
| | | reqVO.setGroupBy("sourceType"); |
| | | List<MesTraceConsumerScanDistributionRespVO> list = statisticsService.getDistribution(reqVO); |
| | | |
| | | assertNotNull(list); |
| | | // æ count ååºï¼æªç¥(2) å¨ BAG(1) ä¹å |
| | | assertEquals(2, list.size()); |
| | | assertEquals("æªç¥", list.get(0).getName()); |
| | | assertEquals(2L, list.get(0).getCount()); |
| | | assertEquals("BAG", list.get(1).getName()); |
| | | assertEquals(1L, list.get(1).getCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetDistribution_successæ¾ç¤ºåç§°() { |
| | | eventMapper.insert(buildEvent("B1", "BAG", null, null, 100L, true, |
| | | LocalDateTime.of(2026, 9, 1, 9, 0))); |
| | | eventMapper.insert(buildEvent("B2", "BAG", null, null, null, false, |
| | | LocalDateTime.of(2026, 9, 1, 10, 0))); |
| | | |
| | | MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); |
| | | reqVO.setGroupBy("success"); |
| | | List<MesTraceConsumerScanDistributionRespVO> list = statisticsService.getDistribution(reqVO); |
| | | |
| | | assertNotNull(list); |
| | | assertEquals(2, list.size()); |
| | | // æ°éç¸åæ¶æåç»åååºï¼"0"(失败) å¨ "1"(æå) ä¹å |
| | | assertEquals("失败", list.get(0).getName()); |
| | | assertEquals("æå", list.get(1).getName()); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetDistribution_éæ³ç»´åº¦æç»() { |
| | | MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); |
| | | reqVO.setGroupBy("item_code; DROP TABLE x"); |
| | | assertThrows(IllegalArgumentException.class, () -> statisticsService.getDistribution(reqVO)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.crossregion; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionHandleReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionStatisticsRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionWarningRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceAntiConfigRespVO; |
| | | 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.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion.MesTraceAntiConfigDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.crossregion.MesTraceCrossRegionWarningDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.crossregion.MesTraceAntiConfigMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.crossregion.MesTraceCrossRegionWarningMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceWarningStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceWarningTypeEnum; |
| | | 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; |
| | | import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import jakarta.annotation.Resource; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.context.annotation.Import; |
| | | import org.springframework.test.context.bean.override.mockito.MockitoBean; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_ANTI_CONFIG_VALUE_INVALID; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_CROSS_REGION_STATUS_INVALID; |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | /** |
| | | * {@link MesTraceCrossRegionServiceImpl} çåå
æµè¯ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Import(MesTraceCrossRegionServiceImpl.class) |
| | | public class MesTraceCrossRegionServiceImplTest extends BaseDbUnitTest { |
| | | |
| | | @Resource |
| | | private MesTraceCrossRegionService crossRegionService; |
| | | @Resource |
| | | private MesTraceCrossRegionWarningMapper warningMapper; |
| | | @Resource |
| | | private MesTraceAntiConfigMapper antiConfigMapper; |
| | | |
| | | @MockitoBean |
| | | private MesProBagCodeService bagCodeService; |
| | | @MockitoBean |
| | | private MesProPalletService palletService; |
| | | @MockitoBean |
| | | private MesProBatchService batchService; |
| | | @MockitoBean |
| | | private MesMdmItemService mdmItemService; |
| | | @MockitoBean |
| | | private CrmCustomerApi customerApi; |
| | | @MockitoBean |
| | | private AdminUserApi adminUserApi; |
| | | @MockitoBean |
| | | private MesWmProductSalesMapper productSalesMapper; |
| | | @MockitoBean |
| | | private MesWmProductSalesDetailMapper productSalesDetailMapper; |
| | | |
| | | // ==================== çªè´§å¤å® ==================== |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_è·¨ç触å() { |
| | | // é
ç½®ç级ç²åº¦ |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | mockDealer("æ±èç", "å京å¸"); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BAG", "æµæ±ç", "æå·å¸", true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | List<MesTraceCrossRegionWarningDO> warnings = warningMapper.selectList(); |
| | | assertEquals(1, warnings.size()); |
| | | MesTraceCrossRegionWarningDO warning = warnings.get(0); |
| | | assertEquals(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType(), warning.getWarningType()); |
| | | assertEquals("æ±èç", warning.getDealerProvince()); |
| | | assertEquals("æµæ±ç", warning.getScanProvince()); |
| | | assertEquals("SO-001", warning.getSalesCode()); |
| | | assertEquals("åä¸ç»éå", warning.getClientName()); |
| | | assertEquals(event.getId(), warning.getScanEventId()); |
| | | assertEquals(MesTraceWarningStatusEnum.PENDING.getStatus(), warning.getStatus()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_è·¨å¸è§¦å() { |
| | | // å¸çº§ç²åº¦ï¼æ«ç å¸ â ç»éåå¸ â CROSS_CITY |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("CITY").build()); |
| | | mockDealer("æ±èç", "å京å¸"); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BAG", "æ±èç", "èå·å¸", true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | List<MesTraceCrossRegionWarningDO> warnings = warningMapper.selectList(); |
| | | assertEquals(1, warnings.size()); |
| | | assertEquals(MesTraceWarningTypeEnum.CROSS_CITY.getType(), warnings.get(0).getWarningType()); |
| | | assertEquals("å京å¸", warnings.get(0).getDealerCity()); |
| | | assertEquals("èå·å¸", warnings.get(0).getScanCity()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_åå°åºä¸é¢è¦() { |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | mockDealer("æ±èç", "å京å¸"); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BAG", "æ±èç", "å京å¸", true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | assertEquals(0, warningMapper.selectCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_æ«ç æ å°åºå®¹é() { |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | mockDealer("æ±èç", "å京å¸"); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BAG", null, null, true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | assertEquals(0, warningMapper.selectCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_æ¹æ¬¡å
¥å£è·³è¿() { |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | mockDealer("æ±èç", "å京å¸"); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BATCH", "æµæ±ç", "æå·å¸", true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | assertEquals(0, warningMapper.selectCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_è¢ç æªåºåºè·³è¿() { |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | // è¢ç æªåååºåºåï¼ä¸æç»è¡¨æ è®°å½ â ä¸é¢è¦ |
| | | when(bagCodeService.getBagCodeByCode("BAG-00000001")).thenReturn( |
| | | MesProBagCodeDO.builder().id(1L).code("BAG-00000001").batchId(10L).build()); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BAG", "æµæ±ç", "æå·å¸", true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | verify(productSalesDetailMapper).selectLatestByBagCodeId(1L); |
| | | assertEquals(0, warningMapper.selectCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCheckAndCreateWarning_è¢ç æªç»å®æ¹æ¬¡è·³è¿() { |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | when(bagCodeService.getBagCodeByCode("BAG-00000001")).thenReturn(null); |
| | | MesTraceConsumerScanEventDO event = buildEvent("BAG", "æµæ±ç", "æå·å¸", true); |
| | | |
| | | crossRegionService.checkAndCreateWarning(event); |
| | | |
| | | assertEquals(0, warningMapper.selectCount()); |
| | | } |
| | | |
| | | // ==================== ç¶ææµè½¬ ==================== |
| | | |
| | | @Test |
| | | public void testHandleWarning_ç¶ææµè½¬() { |
| | | MesTraceCrossRegionWarningDO warning = buildWarning(MesTraceWarningStatusEnum.PENDING.getStatus()); |
| | | warningMapper.insert(warning); |
| | | |
| | | MesTraceCrossRegionHandleReqVO reqVO = new MesTraceCrossRegionHandleReqVO(); |
| | | reqVO.setId(warning.getId()); |
| | | reqVO.setStatus(MesTraceWarningStatusEnum.HANDLED.getStatus()); |
| | | reqVO.setHandleRemark("ç¡®è®¤ä¸ºç©æµä¸è½¬"); |
| | | crossRegionService.handleWarning(reqVO); |
| | | |
| | | MesTraceCrossRegionWarningDO updated = warningMapper.selectById(warning.getId()); |
| | | assertEquals(MesTraceWarningStatusEnum.HANDLED.getStatus(), updated.getStatus()); |
| | | assertEquals("ç¡®è®¤ä¸ºç©æµä¸è½¬", updated.getHandleRemark()); |
| | | } |
| | | |
| | | @Test |
| | | public void testHandleWarning_å·²å¤çä¸å¯åå¤ç() { |
| | | MesTraceCrossRegionWarningDO warning = buildWarning(MesTraceWarningStatusEnum.HANDLED.getStatus()); |
| | | warningMapper.insert(warning); |
| | | |
| | | MesTraceCrossRegionHandleReqVO reqVO = new MesTraceCrossRegionHandleReqVO(); |
| | | reqVO.setId(warning.getId()); |
| | | reqVO.setStatus(MesTraceWarningStatusEnum.FALSE_ALARM.getStatus()); |
| | | ServiceException ex = assertThrows(ServiceException.class, |
| | | () -> crossRegionService.handleWarning(reqVO)); |
| | | assertEquals(MES_TRACE_CROSS_REGION_STATUS_INVALID.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | // ==================== é
ç½® ==================== |
| | | |
| | | @Test |
| | | public void testGetConfig_updateConfig() { |
| | | antiConfigMapper.insert(MesTraceAntiConfigDO.builder() |
| | | .configKey("warning_level").configValue("PROVINCE").build()); |
| | | |
| | | MesTraceAntiConfigRespVO config = crossRegionService.getConfig(); |
| | | assertEquals("PROVINCE", config.getWarningLevel()); |
| | | |
| | | crossRegionService.updateConfig("CITY"); |
| | | assertEquals("CITY", antiConfigMapper.selectByConfigKey("warning_level").getConfigValue()); |
| | | |
| | | ServiceException ex = assertThrows(ServiceException.class, |
| | | () -> crossRegionService.updateConfig("INVALID")); |
| | | assertEquals(MES_TRACE_ANTI_CONFIG_VALUE_INVALID.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | // ==================== æ±æ»ä¸ç»è®¡ ==================== |
| | | |
| | | @Test |
| | | public void testGetWarningSummary() { |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.PENDING.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType())); |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.HANDLED.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_CITY.getType())); |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.FALSE_ALARM.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType())); |
| | | |
| | | MesTraceCrossRegionSummaryRespVO vo = crossRegionService.getWarningSummary(null, null); |
| | | |
| | | assertEquals(3L, vo.getTotal()); |
| | | assertEquals(1L, vo.getPending()); |
| | | assertEquals(1L, vo.getHandled()); |
| | | assertEquals(1L, vo.getFalseAlarm()); |
| | | assertEquals(2L, vo.getCrossProvince()); |
| | | assertEquals(1L, vo.getCrossCity()); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWarningStatistics_åç»ç»è®¡() { |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.PENDING.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType()).setScanProvince("æµæ±ç")); |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.PENDING.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType()).setScanProvince("æµæ±ç")); |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.PENDING.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType()).setScanProvince("æ±èç")); |
| | | |
| | | MesTraceCrossRegionStatisticsReqVO reqVO = new MesTraceCrossRegionStatisticsReqVO(); |
| | | reqVO.setGroupBy("scanProvince"); |
| | | List<MesTraceCrossRegionStatisticsRespVO> list = crossRegionService.getWarningStatistics(reqVO); |
| | | |
| | | assertEquals(2, list.size()); |
| | | assertEquals("æµæ±ç", list.get(0).getName()); |
| | | assertEquals(2L, list.get(0).getCount()); |
| | | assertEquals("æ±èç", list.get(1).getName()); |
| | | assertEquals(1L, list.get(1).getCount()); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWarningStatistics_éæ³ç»´åº¦æç»() { |
| | | MesTraceCrossRegionStatisticsReqVO reqVO = new MesTraceCrossRegionStatisticsReqVO(); |
| | | reqVO.setGroupBy("item_code; DROP TABLE x"); |
| | | assertThrows(IllegalArgumentException.class, |
| | | () -> crossRegionService.getWarningStatistics(reqVO)); |
| | | } |
| | | |
| | | // ==================== å页 ==================== |
| | | |
| | | @Test |
| | | public void testGetWarningPage() { |
| | | when(adminUserApi.getUserMap(any())).thenReturn(Collections.emptyMap()); |
| | | warningMapper.insert(buildWarning(MesTraceWarningStatusEnum.PENDING.getStatus()) |
| | | .setWarningType(MesTraceWarningTypeEnum.CROSS_PROVINCE.getType()) |
| | | .setScanProvince("æµæ±ç").setDealerProvince("æ±èç")); |
| | | |
| | | MesTraceCrossRegionWarningPageReqVO reqVO = new MesTraceCrossRegionWarningPageReqVO(); |
| | | reqVO.setStatus(MesTraceWarningStatusEnum.PENDING.getStatus()); |
| | | var page = crossRegionService.getWarningPage(reqVO); |
| | | |
| | | assertEquals(1L, page.getTotal()); |
| | | MesTraceCrossRegionWarningRespVO vo = page.getList().get(0); |
| | | assertEquals(MesTraceWarningStatusEnum.PENDING.getName(), vo.getStatusName()); |
| | | assertEquals(MesTraceWarningTypeEnum.CROSS_PROVINCE.getName(), vo.getWarningTypeName()); |
| | | assertTrue(vo.getWarningNo() != null); |
| | | } |
| | | |
| | | // ==================== è¾
婿¹æ³ ==================== |
| | | |
| | | /** |
| | | * æå»ºæ¶è´¹è
æ«ç äºä»¶ï¼id åºå®ï¼ä¾¿äºæè¨å
³èï¼ |
| | | */ |
| | | private MesTraceConsumerScanEventDO buildEvent(String sourceType, String province, String city, boolean success) { |
| | | return MesTraceConsumerScanEventDO.builder() |
| | | .id(1L) |
| | | .normalizedCode("BAG-00000001") |
| | | .sourceType(sourceType) |
| | | .province(province) |
| | | .city(city) |
| | | .batchId(10L) |
| | | .success(success) |
| | | .eventTime(LocalDateTime.of(2026, 9, 1, 10, 0)) |
| | | .build(); |
| | | } |
| | | |
| | | /** |
| | | * Mock è¢ç â åºåºå â ç»éåï¼æå®å°åºï¼é¾è·¯ |
| | | */ |
| | | private void mockDealer(String dealerProvince, String dealerCity) { |
| | | MesProBagCodeDO bag = MesProBagCodeDO.builder().id(1L).code("BAG-00000001") |
| | | .batchId(10L).salesId(100L).build(); |
| | | when(bagCodeService.getBagCodeByCode("BAG-00000001")).thenReturn(bag); |
| | | |
| | | MesWmProductSalesDO sales = MesWmProductSalesDO.builder().id(100L).code("SO-001") |
| | | .clientId(200L).clientName("åä¸ç»éå").build(); |
| | | when(productSalesMapper.selectById(100L)).thenReturn(sales); |
| | | |
| | | Integer areaId = AreaUtils.parseArea(dealerProvince + "/" + dealerCity).getId(); |
| | | CrmCustomerRespDTO customer = new CrmCustomerRespDTO(); |
| | | customer.setId(200L); |
| | | customer.setName("åä¸ç»éå"); |
| | | customer.setAreaId(areaId); |
| | | when(customerApi.getCustomer(200L)).thenReturn(customer); |
| | | |
| | | MesProBatchDO batch = MesProBatchDO.builder().id(10L).itemId(5L).build(); |
| | | when(batchService.getBatch(10L)).thenReturn(batch); |
| | | |
| | | MesMdmItemDO item = MesMdmItemDO.builder().id(5L).code("ITEM001").name("水稻ç§å").build(); |
| | | when(mdmItemService.getItem(5L)).thenReturn(item); |
| | | } |
| | | |
| | | private MesTraceCrossRegionWarningDO buildWarning(Integer status) { |
| | | return MesTraceCrossRegionWarningDO.builder() |
| | | .warningNo("WARN20260901100000ABCDEF") |
| | | .batchId(10L) |
| | | .batchCode("PC20260901L001M001_001") |
| | | .salesId(100L) |
| | | .salesCode("SO-001") |
| | | .clientId(200L) |
| | | .clientName("åä¸ç»éå") |
| | | .dealerProvince("æ±èç") |
| | | .dealerCity("å京å¸") |
| | | .scanProvince("æµæ±ç") |
| | | .scanCity("æå·å¸") |
| | | .status(status) |
| | | .eventTime(LocalDateTime.of(2026, 9, 1, 10, 0)) |
| | | .build(); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.trace.integrity; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.trace.integrity.vo.MesTraceIntegrityVerifyRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.trace.integrity.MesTraceIntegrityRecordDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.trace.integrity.MesTraceIntegrityRecordMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.trace.MesTraceIntegrityBizTypeEnum; |
| | | import jakarta.annotation.Resource; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.context.annotation.Import; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_INTEGRITY_BIZ_TYPE_INVALID; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_INTEGRITY_RECORD_NOT_EXISTS; |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | | /** |
| | | * {@link MesTraceIntegrityServiceImpl} çåå
æµè¯ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Import(MesTraceIntegrityServiceImpl.class) |
| | | public class MesTraceIntegrityServiceImplTest extends BaseDbUnitTest { |
| | | |
| | | @Resource |
| | | private MesTraceIntegrityService integrityService; |
| | | @Resource |
| | | private MesTraceIntegrityRecordMapper integrityRecordMapper; |
| | | @Resource |
| | | private MesTraceConsumerScanEventMapper scanEventMapper; |
| | | |
| | | @Test |
| | | public void testRecordScanIntegrity_çææè¦å¹¶å
¥åº() { |
| | | MesTraceConsumerScanEventDO event = insertEvent("æ±èç", "å京å¸"); |
| | | integrityService.recordScanIntegrity(event); |
| | | |
| | | MesTraceIntegrityRecordDO record = integrityRecordMapper.selectByBiz( |
| | | MesTraceIntegrityBizTypeEnum.SCAN.getType(), event.getId()); |
| | | assertNotNull(record); |
| | | assertEquals(64, record.getContentHash().length()); |
| | | assertEquals(event.getNormalizedCode(), record.getBizCode()); |
| | | } |
| | | |
| | | @Test |
| | | public void testVerify_æ°æ®å®æ´éè¿() { |
| | | MesTraceConsumerScanEventDO event = insertEvent("æ±èç", "å京å¸"); |
| | | integrityService.recordScanIntegrity(event); |
| | | |
| | | MesTraceIntegrityVerifyRespVO vo = integrityService.verify( |
| | | MesTraceIntegrityBizTypeEnum.SCAN.getType(), event.getId()); |
| | | |
| | | assertTrue(vo.getMatched()); |
| | | assertEquals(vo.getStoredHash(), vo.getCurrentHash()); |
| | | assertEquals(event.getNormalizedCode(), vo.getBizCode()); |
| | | } |
| | | |
| | | @Test |
| | | public void testVerify_ç¯¡æ¹æ£æµå¤±è´¥() { |
| | | MesTraceConsumerScanEventDO event = insertEvent("æ±èç", "å京å¸"); |
| | | integrityService.recordScanIntegrity(event); |
| | | // 模æäºåç¯¡æ¹æ«ç å°åº |
| | | scanEventMapper.updateById(new MesTraceConsumerScanEventDO().setId(event.getId()).setProvince("æµæ±ç")); |
| | | |
| | | MesTraceIntegrityVerifyRespVO vo = integrityService.verify( |
| | | MesTraceIntegrityBizTypeEnum.SCAN.getType(), event.getId()); |
| | | |
| | | assertFalse(vo.getMatched()); |
| | | assertEquals("æ°æ®ä¸ä¸è´ï¼å¯è½åå¨ç¯¡æ¹", vo.getMessage()); |
| | | } |
| | | |
| | | @Test |
| | | public void testVerify_è¦çæ´æ°ä¸äº§çéå¤è®°å½() { |
| | | MesTraceConsumerScanEventDO event = insertEvent("æ±èç", "å京å¸"); |
| | | integrityService.recordScanIntegrity(event); |
| | | integrityService.recordScanIntegrity(event); |
| | | |
| | | assertEquals(1L, integrityRecordMapper.selectCount()); |
| | | MesTraceIntegrityRecordDO record = integrityRecordMapper.selectByBiz( |
| | | MesTraceIntegrityBizTypeEnum.SCAN.getType(), event.getId()); |
| | | assertEquals(64, record.getContentHash().length()); |
| | | } |
| | | |
| | | @Test |
| | | public void testVerify_æ æè¦è®°å½å¼å¸¸() { |
| | | ServiceException ex = assertThrows(ServiceException.class, |
| | | () -> integrityService.verify(MesTraceIntegrityBizTypeEnum.SCAN.getType(), 99999L)); |
| | | assertEquals(MES_TRACE_INTEGRITY_RECORD_NOT_EXISTS.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | @Test |
| | | public void testVerify_éæ³ä¸å¡ç±»å() { |
| | | ServiceException ex = assertThrows(ServiceException.class, |
| | | () -> integrityService.verify("XXX", 1L)); |
| | | assertEquals(MES_TRACE_INTEGRITY_BIZ_TYPE_INVALID.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | @Test |
| | | public void testRecordScanIntegrity_nulläºä»¶å®¹é() { |
| | | integrityService.recordScanIntegrity(null); |
| | | integrityService.recordScanIntegrity(MesTraceConsumerScanEventDO.builder().id(null).build()); |
| | | assertEquals(0L, integrityRecordMapper.selectCount()); |
| | | } |
| | | |
| | | // ==================== è¾
婿¹æ³ ==================== |
| | | |
| | | private MesTraceConsumerScanEventDO insertEvent(String province, String city) { |
| | | MesTraceConsumerScanEventDO event = MesTraceConsumerScanEventDO.builder() |
| | | .traceCode("BAG-00000001") |
| | | .normalizedCode("BAG-00000001") |
| | | .sourceType("BAG") |
| | | .province(province) |
| | | .city(city) |
| | | .batchId(10L) |
| | | .success(true) |
| | | .eventTime(LocalDateTime.of(2026, 9, 1, 10, 0)) |
| | | .build(); |
| | | scanEventMapper.insert(event); |
| | | return event; |
| | | } |
| | | |
| | | } |
| | |
| | | DELETE FROM "mes_pro_task"; |
| | | DELETE FROM "mes_pro_route_process"; |
| | | DELETE FROM "mes_qc_indicator_result"; |
| | | DELETE FROM "mes_trace_consumer_scan_event"; |
| | | DELETE FROM "mes_trace_anti_config"; |
| | | DELETE FROM "mes_trace_cross_region_warning"; |
| | | DELETE FROM "mes_trace_integrity_record"; |
| | |
| | | "tenant_id" bigint NOT NULL DEFAULT 0, |
| | | PRIMARY KEY ("id") |
| | | ); |
| | | |
| | | -- ---------------------------- |
| | | -- MES æ¶è´¹è
æ«ç 访é®äºä»¶ |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS "mes_trace_consumer_scan_event" ( |
| | | "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| | | "trace_code" varchar(255) NOT NULL, |
| | | "normalized_code" varchar(128) DEFAULT NULL, |
| | | "source_type" varchar(32) DEFAULT NULL, |
| | | "channel" varchar(32) DEFAULT NULL, |
| | | "province" varchar(64) DEFAULT NULL, |
| | | "city" varchar(64) DEFAULT NULL, |
| | | "batch_id" bigint DEFAULT NULL, |
| | | "success" bit NOT NULL DEFAULT FALSE, |
| | | "error_code" varchar(64) DEFAULT NULL, |
| | | "ip_hash" varchar(64) DEFAULT NULL, |
| | | "user_agent_hash" varchar(64) DEFAULT NULL, |
| | | "event_time" timestamp NOT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "updater" varchar(64) DEFAULT '', |
| | | "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "deleted" bit NOT NULL DEFAULT FALSE, |
| | | "tenant_id" bigint NOT NULL DEFAULT 0, |
| | | PRIMARY KEY ("id") |
| | | ); |
| | | |
| | | -- ---------------------------- |
| | | -- MES é²çªè´§è§åé
ç½® |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS "mes_trace_anti_config" ( |
| | | "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| | | "config_key" varchar(64) DEFAULT NULL, |
| | | "config_value" varchar(255) DEFAULT NULL, |
| | | "remark" varchar(255) DEFAULT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "updater" varchar(64) DEFAULT '', |
| | | "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "deleted" bit NOT NULL DEFAULT FALSE, |
| | | PRIMARY KEY ("id") |
| | | ); |
| | | |
| | | -- ---------------------------- |
| | | -- MES çªè´§é¢è¦è®°å½ |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS "mes_trace_cross_region_warning" ( |
| | | "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| | | "warning_no" varchar(64) DEFAULT NULL, |
| | | "bag_code_id" bigint DEFAULT NULL, |
| | | "bag_code" varchar(64) DEFAULT NULL, |
| | | "pallet_code_id" bigint DEFAULT NULL, |
| | | "pallet_code" varchar(64) DEFAULT NULL, |
| | | "batch_id" bigint DEFAULT NULL, |
| | | "batch_code" varchar(64) DEFAULT NULL, |
| | | "item_id" bigint DEFAULT NULL, |
| | | "item_code" varchar(64) DEFAULT NULL, |
| | | "item_name" varchar(128) DEFAULT NULL, |
| | | "sales_id" bigint DEFAULT NULL, |
| | | "sales_code" varchar(64) DEFAULT NULL, |
| | | "client_id" bigint DEFAULT NULL, |
| | | "client_name" varchar(128) DEFAULT NULL, |
| | | "dealer_province" varchar(64) DEFAULT NULL, |
| | | "dealer_city" varchar(64) DEFAULT NULL, |
| | | "scan_province" varchar(64) DEFAULT NULL, |
| | | "scan_city" varchar(64) DEFAULT NULL, |
| | | "warning_type" varchar(32) DEFAULT NULL, |
| | | "scan_event_id" bigint DEFAULT NULL, |
| | | "status" tinyint NOT NULL DEFAULT 10, |
| | | "handle_user_id" bigint DEFAULT NULL, |
| | | "handle_time" timestamp DEFAULT NULL, |
| | | "handle_remark" varchar(255) DEFAULT NULL, |
| | | "event_time" timestamp NOT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "updater" varchar(64) DEFAULT '', |
| | | "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "deleted" bit NOT NULL DEFAULT FALSE, |
| | | PRIMARY KEY ("id") |
| | | ); |
| | | |
| | | -- ---------------------------- |
| | | -- MES æ°æ®å®æ´æ§æ ¡éªè®°å½ |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS "mes_trace_integrity_record" ( |
| | | "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| | | "biz_type" varchar(32) NOT NULL, |
| | | "biz_id" bigint NOT NULL, |
| | | "biz_code" varchar(64) DEFAULT NULL, |
| | | "content_hash" varchar(64) NOT NULL, |
| | | "event_time" timestamp NOT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "updater" varchar(64) DEFAULT '', |
| | | "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | "deleted" bit NOT NULL DEFAULT FALSE, |
| | | PRIMARY KEY ("id") |
| | | ); |
| | |
| | | .scroll-list::-webkit-scrollbar { width: 4px; } |
| | | .scroll-list::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; } |
| | | |
| | | /* æ£åéªè¯å¡ï¼é¡¶é¨ï¼æ«ç ä¼å
å±ç¤ºï¼ */ |
| | | .verify-card { |
| | | border: 1px solid var(--primary-light); |
| | | background: linear-gradient(180deg, #eef8ee, #fff); |
| | | } |
| | | .verify-card .card-bd { padding: 14px 16px; } |
| | | .verify-title { |
| | | display: flex; align-items: center; gap: 8px; |
| | | font-size: 16px; font-weight: 700; color: var(--primary); |
| | | margin-bottom: 4px; |
| | | } |
| | | .verify-title::before { content: ""; width: 5px; height: 16px; border-radius: 2px; background: var(--primary); } |
| | | .verify-sub { font-size: 12px; color: var(--text-sub); margin-bottom: 10px; } |
| | | .verify-badge { margin-bottom: 10px; } |
| | | .verify-stats { margin-top: 10px; border-top: 1px dashed var(--border); padding-top: 4px; } |
| | | |
| | | /* 坿å å¡ç */ |
| | | .card.collapsible .card-hd { cursor: pointer; position: relative; padding-right: 30px; } |
| | | .card.collapsible .card-hd::after { |
| | | content: "â¾"; position: absolute; right: 12px; top: 50%; |
| | | transform: translateY(-50%); color: var(--text-sub); font-size: 12px; |
| | | transition: transform .2s; |
| | | } |
| | | .card.collapsible:not(.open) .card-hd::after { transform: translateY(-50%) rotate(-90deg); } |
| | | .card.collapsible .card-hd .count { margin-right: 16px; } |
| | | .card.collapsible .card-bd { display: none; } |
| | | .card.collapsible.open .card-bd { display: block; } |
| | | |
| | | footer { |
| | | max-width: 480px; margin: 0 auto; |
| | | padding: 20px 16px 28px; text-align: center; |
| | |
| | | return '<span class="badge ' + item[1] + '">' + esc(item[0]) + '</span>'; |
| | | } |
| | | |
| | | function card(title, countHtml, bodyHtml) { |
| | | return '<div class="card">' + |
| | | '<div class="card-hd"><span class="bar"></span>' + esc(title) + |
| | | function card(title, countHtml, bodyHtml, opts) { |
| | | opts = opts || {}; |
| | | var collapsible = !!opts.collapsible; |
| | | var open = collapsible && !!opts.open; |
| | | var hd = '<div class="card-hd"' + |
| | | (collapsible ? ' onclick="this.parentNode.classList.toggle(\'open\')"' : '') + |
| | | '><span class="bar"></span>' + esc(title) + |
| | | (countHtml ? '<span class="count">' + countHtml + '</span>' : '') + |
| | | '</div>' + '<div class="card-bd">' + bodyHtml + '</div>' + '</div>'; |
| | | '</div>'; |
| | | return '<div class="card' + (collapsible ? ' collapsible' : '') + (open ? ' open' : '') + '">' + |
| | | hd + '<div class="card-bd">' + bodyHtml + '</div>' + '</div>'; |
| | | } |
| | | |
| | | function kv(k, v, dim) { |
| | |
| | | return; |
| | | } |
| | | var html = ''; |
| | | html += buildVerifyCard(d); |
| | | html += buildItemCard(d); |
| | | html += buildBatchCard(d); |
| | | html += buildQualityCard(d); |
| | |
| | | window.scrollTo({ top: 0, behavior: 'smooth' }); |
| | | } |
| | | |
| | | // åºå0ï¼æ£åéªè¯ï¼é¡¶é¨ï¼æ«ç ä¼å
å±ç¤ºéªè¯ç»è®ºä¸æ¥è¯¢ç»è®¡ï¼ |
| | | function stripUrl(v) { |
| | | if (!v) return v; |
| | | if (/^https?:\/\//i.test(v)) { |
| | | var m = v.match(/[?&]code=([^&]*)/); |
| | | return m ? decodeURIComponent(m[1]) : ''; |
| | | } |
| | | return v; |
| | | } |
| | | |
| | | function buildVerifyCard(d) { |
| | | var typeLabel = { BAG: 'è¢ç ', PALLET: 'æçç ', BATCH: 'æ¹æ¬¡ç ' }[d.sourceType] || '追溯ç '; |
| | | var code = stripUrl(d.requestCode) || d.batchCode || d.tempCode || '-'; |
| | | var bagNo = '-'; |
| | | if (d.sourceType === 'BAG' && d.bags) { |
| | | for (var i = 0; i < d.bags.length; i++) { |
| | | if (d.bags[i].code === code) { |
| | | bagNo = d.bags[i].bagNo; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | var statsHtml = '<div class="verify-stats">'; |
| | | if (d.antiFake && d.antiFake.scanCount > 0) { |
| | | statsHtml += kv('累计æ¥è¯¢', esc(d.antiFake.scanCount) + ' 次') + |
| | | kv('馿¬¡æ¥è¯¢', fmtDate(d.antiFake.firstScanTime)) + |
| | | kv('æè¿æ¥è¯¢', fmtDate(d.antiFake.lastScanTime)); |
| | | } else { |
| | | statsHtml += kv('累计æ¥è¯¢', 'ææ æ¥è¯¢è®°å½'); |
| | | } |
| | | statsHtml += '</div>'; |
| | | return '<div class="card verify-card">' + |
| | | '<div class="card-bd">' + |
| | | '<div class="verify-title">æ£åéªè¯</div>' + |
| | | '<div class="verify-badge"><span class="badge-big green">â
æ£åéªè¯éè¿</span></div>' + |
| | | '<div class="verify-sub">该追溯ç 为æ£è§æ¸ é产åï¼æº¯æºä¿¡æ¯ç宿æ</div>' + |
| | | kv('追溯类å', typeLabel) + |
| | | kv(typeLabel, text(code)) + |
| | | (d.sourceType === 'BAG' ? kv('è¢å·', bagNo) : '') + |
| | | kv('æ¹æ¬¡å·', text(d.batchCode)) + |
| | | kv('åç§', text(d.itemName)) + |
| | | statsHtml + |
| | | '</div></div>'; |
| | | } |
| | | |
| | | // åºå1ï¼åç§ä¿¡æ¯ |
| | | function buildItemCard(d) { |
| | | if (!d.itemCode && !d.itemName && !d.itemSpecification) return ''; |
| | | return card('åç§ä¿¡æ¯', null, |
| | | kv('åç§åç§°', text(d.itemName)) + |
| | | kv('è§æ ¼', text(d.itemSpecification)) + |
| | | kv('åç§ç¼ç ', text(d.itemCode)) |
| | | kv('åç§ç¼ç ', text(d.itemCode)), |
| | | { collapsible: true, open: true } |
| | | ); |
| | | } |
| | | |
| | |
| | | kv('å·²çæè¢ç ', fmtNum(d.bagQuantity)) + |
| | | kv('å·²çææç', fmtNum(d.palletQuantity)) + |
| | | kv('å
¥åºæ¶é´', fmtDate(d.inboundTime)) + |
| | | (d.remark ? kv('夿³¨', text(d.remark)) : '') |
| | | (d.remark ? kv('夿³¨', text(d.remark)) : ''), |
| | | { collapsible: true, open: true } |
| | | ); |
| | | } |
| | | |
| | |
| | | kv('æ£éªå', text(q.inspectorName)) + |
| | | kv('æ£éªæ¶é´', fmtDate(q.inspectedAt)) + |
| | | (q.reason ? kv('ä¸åæ ¼åå ', text(q.reason)) : '') + |
| | | (rows ? '<div style="margin-top:8px">' + rows + '</div>' : '') |
| | | (rows ? '<div style="margin-top:8px">' + rows + '</div>' : ''), |
| | | { collapsible: true, open: false } |
| | | ); |
| | | } |
| | | |
| | |
| | | esc(type) + 'ï¼' + esc(name) + '</span>'; |
| | | }); |
| | | html += '</div>'; |
| | | return card('ç产人å', null, html); |
| | | return card('ç产人å', null, html, { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // åºå5ï¼æ«ç å
¥åºäºä»¶ |
| | |
| | | kv('å
¥åºæ°é', fmtNum(e.quantity)) + |
| | | kv('设å¤ç¼ç ', text(e.deviceCode)) + |
| | | kv('产线', text(e.lineCode)) + |
| | | kv('å
¥åºæ¶é´', fmtDate(e.eventTime)) |
| | | kv('å
¥åºæ¶é´', fmtDate(e.eventTime)), |
| | | { collapsible: true, open: false } |
| | | ); |
| | | } |
| | | |
| | |
| | | fmtNum(p.bagCount) + ' / ' + fmtNum(p.capacity) + '</td></tr>'; |
| | | }); |
| | | rows += '</tbody></table>'; |
| | | return card('æçä¿¡æ¯', countHtml, rows); |
| | | return card('æçä¿¡æ¯', countHtml, rows, { collapsible: true, open: false }); |
| | | } |
| | | |
| | | // åºå7ï¼è¢ç ï¼ä¸è¢ä¸ç ï¼ |
| | |
| | | }); |
| | | rows += '</tbody></table>'; |
| | | return card('è¢ç ä¿¡æ¯', countHtml, |
| | | '<div class="scroll-list">' + rows + '</div>' |
| | | '<div class="scroll-list">' + rows + '</div>', |
| | | { collapsible: true, open: false } |
| | | ); |
| | | } |
| | | |
| | |
| | | rows += '<tr><td>' + text(s.receiptCode) + '</td><td>' + text(s.vendorName) + '</td><td>' + fmtNum(s.receivedQuantity) + '</td><td>' + fmtDate(s.receiptDate) + '</td></tr>'; |
| | | }); |
| | | rows += '</tbody></table>'; |
| | | html += card('éè´æ¥æº', null, rows); |
| | | html += card('éè´æ¥æº', null, rows, { collapsible: true, open: false }); |
| | | } |
| | | if (!isEmptyList(d.salesTargets)) { |
| | | var rows2 = '<table class="mini-table"><thead><tr><th>éå®åºåºå</th><th>客æ·</th><th>æ°é</th><th>æ¥æ</th></tr></thead><tbody>'; |
| | |
| | | rows2 += '<tr><td>' + text(s.outCode || s.salesOrderCode || '') + '</td><td>' + text(s.customerName) + '</td><td>' + fmtNum(s.quantity) + '</td><td>' + fmtDate(s.outTime) + '</td></tr>'; |
| | | }); |
| | | rows2 += '</tbody></table>'; |
| | | html += card('éå®å»å', null, rows2); |
| | | html += card('éå®å»å', null, rows2, { collapsible: true, open: false }); |
| | | } |
| | | if (!isEmptyList(d.processTraces)) { |
| | | var rows3 = '<table class="mini-table"><thead><tr><th>èç¹</th><th>说æ</th><th>æ¶é´</th></tr></thead><tbody>'; |
| | |
| | | rows3 += '<tr><td>' + text(t.nodeName) + '</td><td>' + text(t.description) + '</td><td>' + fmtDate(t.processTime) + '</td></tr>'; |
| | | }); |
| | | rows3 += '</tbody></table>'; |
| | | html += card('å å·¥æµè½¬', null, rows3); |
| | | html += card('å å·¥æµè½¬', null, rows3, { collapsible: true, open: false }); |
| | | } |
| | | return html; |
| | | } |