Merge branch 'dev_New_pro' into dev_山西_黎城康森商砼
已添加335个文件
已修改358个文件
已删除130个文件
| | |
| | | .gradle
|
| | | /build/
|
| | | !gradle/wrapper/gradle-wrapper.jar
|
| | |
|
| | | claude.md
|
| | | target/
|
| | | !.mvn/wrapper/maven-wrapper.jar
|
| | |
|
| | | ######################################################################
|
| | | # IDE
|
| | | # Claude Code
|
| | | .claude/
|
| | |
|
| | |
|
| | | ### STS ###
|
| | | .apt_generated
|
| | |
| | |
|
| | | !*/build/*.java
|
| | | !*/build/*.html
|
| | | !*/build/*.xml |
| | | !*/build/*.xml
|
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # éè´å¤æä»¶åæéä»¶åå¨ä¸åå²åæ¾èè°è¯´æ |
| | | |
| | | > æ´æ°æ¶é´ï¼2026-05-08 |
| | | > éç¨èå´ï¼éè´æºè½ä½å¤æä»¶åæ + åå²ä¼è¯éä»¶åæ¾ |
| | | |
| | | ## 1. åæ´èæ¯ |
| | | |
| | | å端已补é½ä»¥ä¸è½åï¼ |
| | | |
| | | 1. `POST /purchase-ai/analyze-files` ä¸ä¼ æ¶å
åéä»¶å°æå¡å¨ï¼å
Œ
±è®¿é®ï¼ã |
| | | 2. ææä»¶ç±»åè¿åå¯è®¿é®è·¯å¾ï¼ |
| | | - å¾ç / PDFï¼ä¼å
é¢è§è·¯å¾ |
| | | - å
¶å®æä»¶ï¼ä¼å
ä¸è½½è·¯å¾ |
| | | 3. ç¨æ·æé®ä¸éä»¶è·¯å¾åå¼åå
¥ Mongoã |
| | | 4. å岿¶æ¯æ¥å£å¯æâæ¶æ¯ç»´åº¦âåä¼ éä»¶è·¯å¾ï¼å端å¯ç´æ¥åæ¾éä»¶å¡çã |
| | | |
| | | --- |
| | | |
| | | ## 2. æ¥å£è¡ä¸º |
| | | |
| | | ### 2.1 夿件忿¥å£ |
| | | |
| | | ```http |
| | | POST /purchase-ai/analyze-files |
| | | Content-Type: multipart/form-data |
| | | ``` |
| | | |
| | | 请æ±åæ°ï¼ |
| | | |
| | | - `files`: `MultipartFile[]`ï¼å¿
å¡«ï¼ |
| | | - `message`: `string`ï¼å¯éï¼ |
| | | - `memoryId`: `string`ï¼å¯éï¼ |
| | | |
| | | å端å¤çæµç¨ï¼ |
| | | |
| | | 1. è°ç¨ `StorageBlobService.upload(files, true)` ä¸ä¼ å¹¶ä¿åéä»¶ã |
| | | 2. çæé件访é®è·¯å¾ï¼é¢è§ / ä¸è½½ï¼ã |
| | | 3. å°âæ¬æ¬¡æé® + æ¬æ¬¡éä»¶è·¯å¾å表âåå
¥ Mongoã |
| | | 4. ç»§ç»æ§è¡åææä»¶è§£æå AI åææµç¨ã |
| | | |
| | | å¯è½éè¯¯ï¼æµå¼ææ¬ï¼ï¼ |
| | | |
| | | - `æä»¶ä¸ä¼ 失败` |
| | | - `ä¼è¯æä»¶ä¿¡æ¯ä¿å失败` |
| | | |
| | | ### 2.2 å岿¶æ¯æ¥å£ï¼å端éç¹ï¼ |
| | | |
| | | ```http |
| | | GET /purchase-ai/history/messages/{memoryId} |
| | | ``` |
| | | |
| | | æ¶æ¯å¯¹è±¡æ°å¢å¯éåæ®µ `filePaths`ï¼ä»
ç¨æ·æ¶æ¯å¯è½æå¼ï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "role": "user | assistant | system | tool | unknown", |
| | | "content": "æ¶æ¯ææ¬", |
| | | "filePaths": [ |
| | | "/common/preview/xxx?publicKey=...", |
| | | "/common/download/yyy?publicKey=..." |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | 说æï¼ |
| | | |
| | | 1. `filePaths` å¯è½ç¼ºå¤±æä¸ºç©ºï¼èä¼è¯ / æ®é对è¯ï¼ã |
| | | 2. åæ¡ç¨æ·æ¶æ¯å¯è½å
å«å¤ä¸ªéä»¶è·¯å¾ã |
| | | 3. è¯¥åæ®µå·²ææ¶æ¯ç»´åº¦å¯¹é½ï¼å¯ç´æ¥ç¨äºåå²åæ¾ã |
| | | |
| | | --- |
| | | |
| | | ## 3. å端æ¹é å»ºè®®ï¼æå½åå®ç°è½å°ï¼ |
| | | |
| | | ### 3.1 åå²ååºæ¨¡å |
| | | |
| | | 建议å¨å岿¶æ¯åå§ç»æä¸ä¿ç `filePaths`ï¼ |
| | | |
| | | ```ts |
| | | type AiHistoryMessage = { |
| | | role: string; |
| | | content: string; |
| | | filePaths?: string[]; |
| | | }; |
| | | ``` |
| | | |
| | | ### 3.2 UI æ¶æ¯æ¨¡åæ å° |
| | | |
| | | è¥å端页é¢ç¨çæ¯ `localUploadFiles` 渲æéä»¶å¡çï¼å¦ `AIChatSidebar` å½åå®ç°ï¼ï¼å岿¶æ¯éå䏿¬¡æ å°ï¼ |
| | | |
| | | ```ts |
| | | type LocalUploadFileItem = { |
| | | previewId: string; |
| | | name: string; |
| | | size: number; |
| | | type: string; |
| | | isImage: boolean; |
| | | previewUrl: string; |
| | | rawFile: null; |
| | | }; |
| | | ``` |
| | | |
| | | æ å°è§åå»ºè®®ï¼ |
| | | |
| | | 1. ä»
`role === 'user'` ä¸ `filePaths?.length > 0` æ¶çæ `localUploadFiles`ã |
| | | 2. `previewId` ç¨ `${memoryId}-${messageIndex}-${fileIndex}` çæç¨³å®å¼ã |
| | | 3. `name` å¯ä» URL è·¯å¾è§£æï¼è§£æå¤±è´¥ç¨ `file-{n}`ã |
| | | 4. `isImage` 坿æ©å±å夿ï¼`png/jpg/jpeg/gif/webp/bmp/svg`ï¼ã |
| | | 5. å¾çç `previewUrl` ç´æ¥ä½¿ç¨è·¯å¾ï¼éå¾çå¯ç½®ç©ºå¹¶èµ°å¾æ å±ç¤ºã |
| | | |
| | | ### 3.3 æ¶æ¯æ¸²æ |
| | | |
| | | 对äºç¨æ·æ¶æ¯ï¼ |
| | | |
| | | 1. æ£å¸¸æ¸²æ `content`ã |
| | | 2. è¥åå¨ `localUploadFiles`ï¼æç´æ¥ä½¿ç¨ `filePaths`ï¼ï¼å¨æ¶æ¯ä¸æ¹æ¸²æéä»¶å表/å¡çã |
| | | 3. 龿¥ç´æ¥ä½¿ç¨å端è¿åè·¯å¾ï¼ä¸åæ¼æ¥æäºæ¬¡æ¹åã |
| | | |
| | | > åç«¯å·²å®æâé¢è§/ä¸è½½è·¯å¾âéæ©ï¼å端åªè´è´£å±ç¤ºä¸æå¼ã |
| | | |
| | | ### 3.4 å
¼å®¹è¦æ± |
| | | |
| | | - `filePaths` 缺失ï¼ä¸æ¥éï¼ä¸æ¸²æéä»¶åºåã |
| | | - èä¼è¯ï¼ç»§ç»æ `role/content` å±ç¤ºï¼ä¸å½±ååå²è®°å½æ¥çã |
| | | - å¤éä»¶ï¼ä¿æé¡ºåºå±ç¤ºï¼é¿å
æä¹±ç¨æ·ä¸ä¼ 顺åºã |
| | | |
| | | --- |
| | | |
| | | ## 4. Mongo åæ®µè¯´æï¼å端已å®ç°ï¼ |
| | | |
| | | `chat_messages` ææ¡£æ°å¢/使ç¨åæ®µï¼ |
| | | |
| | | - `analyzeUserQuestions: string[]` |
| | | - `analyzeFilePaths: string[]`ï¼å
¼å®¹æ§åæ®µï¼ |
| | | - `analyzeFilePathGroups: string[][]`ï¼æ¨èï¼ææé®åç»ï¼ |
| | | |
| | | å岿¶æ¯åä¼ æ¶ï¼å端ä¼å
读å `analyzeFilePathGroups`ï¼å¹¶å
¼å®¹ `analyzeFilePaths`ã |
| | | |
| | | --- |
| | | |
| | | ## 5. èè°éªæ¶æ¸
å |
| | | |
| | | 1. æ°å»ºä¼è¯ï¼ä¸ä¼ 1 å¼ å¾ç + 1 个 Excelï¼åéåæè¯·æ±ã |
| | | 2. è°ç¨ `GET /purchase-ai/history/messages/{memoryId}`ï¼ç¡®è®¤ç¨æ·æ¶æ¯å« `filePaths` 䏿°éæ£ç¡®ã |
| | | 3. å·æ°é¡µé¢åéæ°è¿å
¥åä¸ä¼è¯ï¼ç¡®è®¤éä»¶å¡çå¯åæ¾ã |
| | | 4. åå«éªè¯ï¼ |
| | | - å¾ç/PDF å¯é¢è§è®¿é® |
| | | - å
¶å®æä»¶å¯ä¸è½½è®¿é® |
| | | 5. éªè¯èä¼è¯ï¼æ `filePaths`ï¼å¯æ£å¸¸å±ç¤ºï¼é¡µé¢ä¸æ¥éã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # 设å¤ä¿å
»å®æ¶ä»»å¡è®¾å¤é¡¹ç®å段å端èè°è¯´æ |
| | | |
| | | > æ´æ°æ¶é´ï¼2026-05-11 |
| | | > éç¨èå´ï¼è®¾å¤ä¿å
»å®æ¶ä»»å¡ï¼`maintenance_task`ï¼ä¸è®¾å¤ä¿å
»è®°å½ï¼`device_maintenance`ï¼ |
| | | |
| | | ## 1. åæ´æ¦è¿° |
| | | |
| | | æ¬æ¬¡åç«¯åæ´ï¼ |
| | | |
| | | 1. `MaintenanceTask` æ°å¢å段 `machineryCategory`ï¼è®¾å¤é¡¹ç®ï¼ã |
| | | 2. 宿¶ä»»å¡æ§è¡çæè®¾å¤ä¿å
»è®°å½æ¶ï¼ä¼æ `MaintenanceTask.machineryCategory` 忥åå
¥ `DeviceMaintenance.machineryCategory`ã |
| | | |
| | | 对åºä»£ç ä½ç½®ï¼ |
| | | |
| | | - `src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java` |
| | | - `src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java` |
| | | |
| | | ## 2. æ°æ®åºåæ´ |
| | | |
| | | éè¦å
æ§è¡æ°æ®åºåæ´ï¼å¦åæ°å¢/æ¥è¯¢è¯¥å段ä¼å¼å¸¸ï¼ï¼ |
| | | |
| | | ```sql |
| | | ALTER TABLE `maintenance_task` |
| | | ADD COLUMN `machinery_category` VARCHAR(100) NULL COMMENT '设å¤é¡¹ç®' AFTER `device_model`; |
| | | ``` |
| | | |
| | | ## 3. åæ®µçº¦å® |
| | | |
| | | | åæ®µ | å«ä¹ | ç±»å | 建议 | |
| | | | --- | --- | --- | --- | |
| | | | machineryCategory | 设å¤é¡¹ç® | string | å端æ°å¢/ç¼è¾å®æ¶ä»»å¡æ¶ä¼ å¼ | |
| | | |
| | | 说æï¼ |
| | | |
| | | - å端å½åæªå¯¹è¯¥å段å强å¶éç©ºæ ¡éªï¼ä½ä¸å¡ä¸å»ºè®®å端ä½ä¸ºå¿
å¡«å¤çã |
| | | - åå² `maintenance_task` æ§æ°æ®è¥è¯¥å段为空ï¼ååºäºæ§ä»»å¡çæçä¿å
»è®°å½ä¹ä¼ä¸ºç©ºã |
| | | |
| | | ## 4. æ¥å£èè° |
| | | |
| | | ### 4.1 æ°å¢å®æ¶ä»»å¡ |
| | | |
| | | ```http |
| | | POST /deviceMaintenanceTask/add |
| | | Content-Type: application/json |
| | | ``` |
| | | |
| | | 请æ±ç¤ºä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "taskName": "ç©ºåæºA-å¨ä¿å
»", |
| | | "taskId": 1001, |
| | | "deviceModel": "GA75", |
| | | "machineryCategory": "å¨å设å¤", |
| | | "frequencyType": "WEEKLY", |
| | | "frequencyDetail": "MON,09:00", |
| | | "registrantId": 1, |
| | | "registrationDate": "2026-05-11", |
| | | "remarks": "èè°æ ·ä¾" |
| | | } |
| | | ``` |
| | | |
| | | ### 4.2 ä¿®æ¹å®æ¶ä»»å¡ |
| | | |
| | | ```http |
| | | POST /deviceMaintenanceTask/update |
| | | Content-Type: application/json |
| | | ``` |
| | | |
| | | 请æ±ç¤ºä¾ï¼å« `id`ï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "id": 12, |
| | | "machineryCategory": "å¨å设å¤" |
| | | } |
| | | ``` |
| | | |
| | | ### 4.3 宿¶ä»»å¡å表 |
| | | |
| | | ```http |
| | | GET /deviceMaintenanceTask/listPage?pageNum=1&pageSize=10 |
| | | ``` |
| | | |
| | | è¿åè®°å½ä¸ä¼å
å« `machineryCategory` åæ®µã |
| | | |
| | | ### 4.4 设å¤ä¿å
»è®°å½å表/详æ
|
| | | |
| | | ```http |
| | | GET /device/maintenance/page?pageNum=1&pageSize=10 |
| | | GET /device/maintenance/{id} |
| | | ``` |
| | | |
| | | è¿åä¸ `machineryCategory` æ¥æºäº `device_maintenance.machinery_category`ã |
| | | 对äºâç±å®æ¶ä»»å¡èªå¨çæâçè®°å½ï¼è¯¥å¼ä¼ç»§æ¿çææ¶å¯¹åºä»»å¡ç `machineryCategory`ã |
| | | |
| | | ## 5. å端æ¹é ç¹ |
| | | |
| | | 1. 宿¶ä»»å¡æ°å¢/ç¼è¾è¡¨åå¢å â设å¤é¡¹ç®ï¼machineryCategoryï¼âè¾å
¥é¡¹ã |
| | | 2. æäº¤ `/deviceMaintenanceTask/add`ã`/deviceMaintenanceTask/update` æ¶æºå¸¦ `machineryCategory`ã |
| | | 3. 宿¶ä»»å¡å表å¢å â设å¤é¡¹ç®âåï¼å¦é¡µé¢æè¯¥å表ï¼ã |
| | | 4. 设å¤ä¿å
»è®°å½å表/详æ
å¢å â设å¤é¡¹ç®âå±ç¤ºï¼ç©ºå¼æ¾ç¤º `--`ã |
| | | |
| | | ## 6. èè°éªæ¶æ¸
å |
| | | |
| | | 1. æ°å¢ä¸ä¸ªå¸¦ `machineryCategory` ç宿¶ä»»å¡ï¼ä¿åæåã |
| | | 2. æ¥è¯¢ `/deviceMaintenanceTask/listPage`ï¼ç¡®è®¤è¯¥ä»»å¡è¿å `machineryCategory`ã |
| | | 3. çå¾
䏿¬¡å®æ¶è§¦åï¼æå°é¢çè°å°ä¸´è¿æ¶é´ï¼ï¼çæä¿å
»è®°å½ã |
| | | 4. æ¥è¯¢ `/device/maintenance/page` æè¯¦æ
æ¥å£ï¼ç¡®è®¤æ°è®°å½ç `machineryCategory` ä¸ä»»å¡ä¸è´ã |
| | | 5. ä¿®æ¹ä»»å¡ `machineryCategory` å忬¡è§¦åï¼ç¡®è®¤âæ°çæè®°å½âä½¿ç¨æ°å¼ï¼åå²è®°å½ä¸ååï¼ã |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # AccountSubject æ å½¢æ¹é åç«¯ä¿®æ¹ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-12 |
| | | |
| | | ## 1. åæ´èæ¯ |
| | | |
| | | `AccountSubjectController` å·²æ¹ä¸ºç¶åå±çº§é彿¨¡å¼ï¼`/accountSubject/list` ç°å¨è¿åæ å½¢ç»æï¼`children` éå½ï¼ï¼ä¸åæ¯å纯çå¹³éºå表ã |
| | | |
| | | --- |
| | | |
| | | ## 2. å端æ¥å£åå |
| | | |
| | | ### 2.1 æ¥è¯¢æ¥å£ï¼å·²åæ´ä¸ºæ ï¼ |
| | | |
| | | - URLï¼`GET /accountSubject/list` |
| | | - å
¥åï¼ä¿æä¸åï¼`current,size,subjectCode,subjectName,subjectType,status`ï¼ |
| | | - åºåï¼ä»æ¯å页壳ï¼`records,total`ï¼ï¼ä½ `records` å为æ èç¹æ°ç»ï¼æ ¹èç¹å页ï¼åèç¹éå½å
åµï¼ |
| | | |
| | | 示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": { |
| | | "records": [ |
| | | { |
| | | "id": 1, |
| | | "parentId": null, |
| | | "subjectCode": "1002", |
| | | "subjectName": "é¶è¡å款", |
| | | "subjectType": "èµäº§ç±»", |
| | | "balanceDirection": "åæ¹", |
| | | "status": 0, |
| | | "leaf": false, |
| | | "children": [ |
| | | { |
| | | "id": 2, |
| | | "parentId": 1, |
| | | "subjectCode": "100201", |
| | | "subjectName": "å·¥è¡å款", |
| | | "subjectType": "èµäº§ç±»", |
| | | "balanceDirection": "åæ¹", |
| | | "status": 0, |
| | | "leaf": true, |
| | | "children": [] |
| | | } |
| | | ] |
| | | } |
| | | ], |
| | | "total": 1 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 2.2 æ°å¢/ç¼è¾æ¥å£å段åå |
| | | |
| | | - `POST /accountSubject/add` |
| | | - `PUT /accountSubject/edit` |
| | | |
| | | æ°å¢æ¯æåæ®µï¼ |
| | | |
| | | - `parentId`ï¼ç¶èç¹IDï¼ä¸ºç©ºè¡¨ç¤ºæ ¹èç¹ï¼ |
| | | |
| | | 示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "id": 2, |
| | | "parentId": 1, |
| | | "subjectCode": "100201", |
| | | "subjectName": "å·¥è¡å款", |
| | | "subjectType": "èµäº§ç±»", |
| | | "balanceDirection": "åæ¹", |
| | | "status": 0, |
| | | "remark": "" |
| | | } |
| | | ``` |
| | | |
| | | ### 2.3 å 餿¥å£è¡ä¸ºåå |
| | | |
| | | - `DELETE /accountSubject/remove/{ids}` |
| | | |
| | | è¡ä¸ºï¼ |
| | | |
| | | 1. å é¤ç¶èç¹æ¶ä¼éå½å 餿æååèç¹ã |
| | | 2. è¥ä»»æå¾
å é¤èç¹ï¼å«ååï¼å·²è¢« `fin_voucher_entry.subject_code` å¼ç¨ï¼åæ´ä½å é¤å¤±è´¥ã |
| | | |
| | | --- |
| | | |
| | | ## 3. å端æ¹é æ¸
å |
| | | |
| | | ## 3.1 æ»è´¦ç§ç®ç®¡ç页 |
| | | |
| | | æä»¶ï¼`src/views/financialManagement/generalLedger/index.vue` |
| | | |
| | | ### å¿
æ¹é¡¹ |
| | | |
| | | 1. æ°å¢âç¶ç§ç®âéæ©æ§ä»¶ï¼`el-cascader` æ `el-tree-select`ï¼ï¼ä¿åæ¶å¸¦ `parentId`ã |
| | | 2. å表æ¹ä¸ºæ 表å±ç¤ºï¼æ¨è `el-table` + `row-key` + `tree-props`ï¼ã |
| | | 3. æç´¢é»è¾ä¿æä¸åï¼ä½è¦å
¼å®¹ `records` 为æ ç»æã |
| | | |
| | | --- |
| | | |
| | | ## 3.2 åè¯é¡µç§ç®ä¸æ |
| | | |
| | | æä»¶ï¼`src/views/financialManagement/voucher/index.vue` |
| | | |
| | | å½ååè¯åå½ä½¿ç¨ `el-select`ï¼å¹³éºï¼ã |
| | | å端已è¿åæ ï¼éè¦å端æå¹³åååç»å®ä¸æã |
| | | |
| | | 示ä¾ï¼å¯å¤ç¨ï¼ï¼ |
| | | |
| | | ```js |
| | | const flattenSubjectTree = (nodes, result = []) => { |
| | | (nodes || []).forEach(node => { |
| | | result.push({ |
| | | code: node.subjectCode, |
| | | name: node.subjectName, |
| | | id: node.id, |
| | | parentId: node.parentId |
| | | }); |
| | | if (node.children?.length) { |
| | | flattenSubjectTree(node.children, result); |
| | | } |
| | | }); |
| | | return result; |
| | | }; |
| | | |
| | | // list æ¥å£è¿ååï¼ |
| | | const treeRecords = response.data?.records || []; |
| | | subjectList.value = flattenSubjectTree(treeRecords); |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 3.3 ç§ç®æ»è´¦/æç»è´¦é¡µçº§è |
| | | |
| | | æä»¶ï¼ |
| | | |
| | | - `src/views/financialManagement/voucher/generalLedger.vue` |
| | | - `src/views/financialManagement/voucher/detailLedger.vue` |
| | | |
| | | å½åé»è¾æ¯æ `records` å¼ºå¶æ å°æ `children: []`ï¼éè¦å é¤è¿æ®µâå¹³éºæ¹é âï¼ç´æ¥ä½¿ç¨å端æ ã |
| | | |
| | | 示ä¾ï¼å¯å¤ç¨ï¼ï¼ |
| | | |
| | | ```js |
| | | const toCascaderTree = (nodes = []) => |
| | | nodes |
| | | .filter(item => item.subjectCode && item.subjectName) |
| | | .map(item => ({ |
| | | code: item.subjectCode, |
| | | name: item.subjectName, |
| | | children: toCascaderTree(item.children || []) |
| | | })); |
| | | |
| | | subjectOptions.value = toCascaderTree(response.data?.records || []); |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 4. 建议çåç«¯åæ®µçº¦å® |
| | | |
| | | 建议å¨å端 `form` å¢å ï¼ |
| | | |
| | | - `parentId: null` |
| | | |
| | | å¹¶å¨ç¼è¾åå¡«æ¶ä¿æ `parentId`ã |
| | | |
| | | --- |
| | | |
| | | ## 5. èè°æ³¨æäºé¡¹ |
| | | |
| | | 1. `/accountSubject/list` ç `total` æ¯æ ¹èç¹æ°éï¼ä¸æ¯å
¨éèç¹æ°ã |
| | | 2. è¥é¡µé¢ä»æå¹³éº `records.map(...)` å¤çï¼ä¼ä¸¢å¤±åèç¹ã |
| | | 3. å é¤ç§ç®å¤±è´¥æ¶ï¼ä¼å
æ£æ¥æ¯å¦è¢«åè¯åå½å¼ç¨ã |
| | | 4. ä¿å失败åºç°âç¶ç§ç®ä¸è½æ¯å½åç§ç®æå
¶åç§ç®âæ¶ï¼éè¦å端éå¶ç¶èç¹å¯éèå´ã |
| | | |
| | | --- |
| | | |
| | | ## 6. æ°æ®åºåæ®µè¦æ± |
| | | |
| | | `account_subject` éè¦å
å« `parent_id` åæ®µï¼`bigint`ï¼å¯ç©ºï¼ã |
| | | è¥çº¿ä¸åºå°æªæ·»å ï¼è¯·å
æ§è¡ DDL åèè°ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- account_subject å¢å ç¶çº§ç§ç®åæ®µï¼æ å½¢ç»æï¼ |
| | | ALTER TABLE `account_subject` |
| | | ADD COLUMN `parent_id` bigint NULL COMMENT 'ç¶ç§ç®IDï¼ä¸ºç©ºè¡¨ç¤ºæ ¹èç¹ï¼' AFTER `id`; |
| | | |
| | | CREATE INDEX `idx_account_subject_parent_id` ON `account_subject` (`parent_id`); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- è´¢å¡ç®¡ç模åå»ºè¡¨èæ¬ï¼åºå®èµäº§/æ å½¢èµäº§/åè¯/ç§ç®è´¦ï¼ |
| | | -- 说æï¼ |
| | | -- 1) æ»è´¦ç§ç®ç»§ç»å¤ç¨å·²æè¡¨ account_subjectï¼ä¸éå¤å建 fin_account_subjectã |
| | | -- 2) éé¢å段ç»ä¸ decimal(18,2)ã |
| | | |
| | | CREATE TABLE IF NOT EXISTS `fin_fixed_asset` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主é®ID', |
| | | `asset_code` varchar(64) NOT NULL COMMENT 'èµäº§ç¼å·', |
| | | `asset_name` varchar(128) NOT NULL COMMENT 'èµäº§åç§°', |
| | | `category` varchar(64) NOT NULL COMMENT 'èµäº§ç±»å«', |
| | | `specification` varchar(255) DEFAULT NULL COMMENT 'è§æ ¼åå·', |
| | | `purchase_date` date NOT NULL COMMENT 'è´ç½®æ¥æ', |
| | | `original_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'èµäº§åå¼', |
| | | `useful_life` int NOT NULL DEFAULT '1' COMMENT '使ç¨å¹´é(å¹´)', |
| | | `residual_rate` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'æ®å¼ç(%)', |
| | | `accumulated_depreciation` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'ç´¯è®¡ææ§', |
| | | `net_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'åå¼', |
| | | `location` varchar(255) DEFAULT NULL COMMENT 'åæ¾å°ç¹', |
| | | `department` varchar(128) DEFAULT NULL COMMENT '使ç¨é¨é¨', |
| | | `keeper` varchar(64) DEFAULT NULL COMMENT 'ä¿ç®¡äºº', |
| | | `status` varchar(32) NOT NULL DEFAULT 'in_use' COMMENT 'ç¶æ: in_use/idle/repair/scrapped', |
| | | `remark` varchar(500) DEFAULT NULL COMMENT '夿³¨', |
| | | `create_user` varchar(64) DEFAULT NULL COMMENT 'å建人', |
| | | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `update_user` varchar(64) DEFAULT NULL COMMENT 'ä¿®æ¹äºº', |
| | | `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ä¿®æ¹æ¶é´', |
| | | `dept_id` bigint DEFAULT NULL COMMENT 'é¨é¨ID', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_fin_fixed_asset_code` (`asset_code`), |
| | | KEY `idx_fin_fixed_asset_status` (`status`), |
| | | KEY `idx_fin_fixed_asset_category` (`category`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='åºå®èµäº§'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS `fin_intangible_asset` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主é®ID', |
| | | `asset_code` varchar(64) NOT NULL COMMENT 'èµäº§ç¼å·', |
| | | `asset_name` varchar(128) NOT NULL COMMENT 'èµäº§åç§°', |
| | | `category` varchar(64) NOT NULL COMMENT 'èµäº§ç±»å«', |
| | | `certificate_no` varchar(128) DEFAULT NULL COMMENT 'è¯ä¹¦ç¼å·', |
| | | `acquisition_date` date NOT NULL COMMENT 'å徿¥æ', |
| | | `original_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'èµäº§åå¼', |
| | | `amortization_period` int NOT NULL DEFAULT '1' COMMENT 'æéå¹´é(å¹´)', |
| | | `residual_rate` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'æ®å¼ç(%)', |
| | | `accumulated_amortization` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '累计æé', |
| | | `net_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'åå¼', |
| | | `validity_date` date DEFAULT NULL COMMENT 'æææè³', |
| | | `status` varchar(32) NOT NULL DEFAULT 'in_use' COMMENT 'ç¶æ: in_use/expired/amortized', |
| | | `description` varchar(1000) DEFAULT NULL COMMENT 'èµäº§æè¿°', |
| | | `remark` varchar(500) DEFAULT NULL COMMENT '夿³¨', |
| | | `create_user` varchar(64) DEFAULT NULL COMMENT 'å建人', |
| | | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `update_user` varchar(64) DEFAULT NULL COMMENT 'ä¿®æ¹äºº', |
| | | `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ä¿®æ¹æ¶é´', |
| | | `dept_id` bigint DEFAULT NULL COMMENT 'é¨é¨ID', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_fin_intangible_asset_code` (`asset_code`), |
| | | KEY `idx_fin_intangible_asset_status` (`status`), |
| | | KEY `idx_fin_intangible_asset_category` (`category`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='æ å½¢èµäº§'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS `fin_voucher` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主é®ID', |
| | | `voucher_no` varchar(64) NOT NULL COMMENT 'åè¯åå·', |
| | | `voucher_date` date NOT NULL COMMENT 'åè¯æ¥æ', |
| | | `summary` varchar(500) DEFAULT NULL COMMENT 'æè¦', |
| | | `debit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'åæ¹å计', |
| | | `credit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'è´·æ¹å计', |
| | | `creator` varchar(64) DEFAULT NULL COMMENT 'å¶å人', |
| | | `status` varchar(32) NOT NULL DEFAULT 'unposted' COMMENT 'ç¶æ: unposted/posted/cancelled', |
| | | `attachment_count` int NOT NULL DEFAULT '0' COMMENT 'éä»¶å¼ æ°', |
| | | `remark` varchar(500) DEFAULT NULL COMMENT '夿³¨', |
| | | `create_user` varchar(64) DEFAULT NULL COMMENT 'å建人', |
| | | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `update_user` varchar(64) DEFAULT NULL COMMENT 'ä¿®æ¹äºº', |
| | | `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ä¿®æ¹æ¶é´', |
| | | `dept_id` bigint DEFAULT NULL COMMENT 'é¨é¨ID', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_fin_voucher_no` (`voucher_no`), |
| | | KEY `idx_fin_voucher_date` (`voucher_date`), |
| | | KEY `idx_fin_voucher_status` (`status`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='åè¯ä¸»è¡¨'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS `fin_voucher_entry` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主é®ID', |
| | | `voucher_id` bigint NOT NULL COMMENT 'åè¯ID', |
| | | `row_no` int NOT NULL DEFAULT '1' COMMENT 'è¡å·', |
| | | `subject_code` varchar(64) NOT NULL COMMENT 'ç§ç®ç¼ç ', |
| | | `subject_name` varchar(128) DEFAULT NULL COMMENT 'ç§ç®åç§°', |
| | | `summary` varchar(500) DEFAULT NULL COMMENT 'æè¦', |
| | | `debit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'åæ¹éé¢', |
| | | `credit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT 'è´·æ¹éé¢', |
| | | `auxiliary_type` varchar(32) DEFAULT NULL COMMENT 'è¾
婿 ¸ç®ç±»å', |
| | | `auxiliary_id` varchar(64) DEFAULT NULL COMMENT 'è¾
婿 ¸ç®å¯¹è±¡ID', |
| | | `auxiliary_name` varchar(128) DEFAULT NULL COMMENT 'è¾
婿 ¸ç®å¯¹è±¡åç§°', |
| | | `create_user` varchar(64) DEFAULT NULL COMMENT 'å建人', |
| | | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `update_user` varchar(64) DEFAULT NULL COMMENT 'ä¿®æ¹äºº', |
| | | `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ä¿®æ¹æ¶é´', |
| | | `dept_id` bigint DEFAULT NULL COMMENT 'é¨é¨ID', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_fin_voucher_entry_voucher` (`voucher_id`), |
| | | KEY `idx_fin_voucher_entry_subject` (`subject_code`), |
| | | KEY `idx_fin_voucher_entry_aux` (`auxiliary_type`, `auxiliary_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='åè¯åå½'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # è´¢å¡ç®¡ç模åå端èè°ææ¡£ï¼account 模åï¼ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-12 |
| | | |
| | | ## 1. éç¨è¯´æ |
| | | |
| | | ### 1.1 ååºç»æ |
| | | æåååºï¼ |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": {} |
| | | } |
| | | ``` |
| | | |
| | | ä¸å¡æ ¡éªå¤±è´¥ï¼ä¾å¦åè´·ä¸å¹³è¡¡ãå¿
填缺失ï¼ç±å
¨å±å¼å¸¸è¿åï¼ |
| | | |
| | | ```json |
| | | { |
| | | "code": 500, |
| | | "msg": "é误信æ¯", |
| | | "data": null |
| | | } |
| | | ``` |
| | | |
| | | ### 1.2 åé¡µç»æ |
| | | å页æ¥å£ç»ä¸ä½¿ç¨ MyBatis-Plus `Page`ï¼ |
| | | - 请æ±åæ°ï¼`current`ã`size` |
| | | - è¿åï¼`data.records`ã`data.total` |
| | | |
| | | --- |
| | | |
| | | ## 2. æ»è´¦ç§ç®ï¼å·²å¨åæ¥å£ä¸å¢å¼ºæ ¡éªï¼ |
| | | |
| | | æ¥å£ä¿æä¸åï¼ |
| | | - `GET /accountSubject/list` |
| | | - `POST /accountSubject/add` |
| | | - `PUT /accountSubject/edit` |
| | | - `DELETE /accountSubject/remove/{ids}` |
| | | - `POST /accountSubject/export` |
| | | |
| | | æ°å¢è§åï¼ |
| | | 1. `subjectCode`ã`subjectName`ã`subjectType` å¿
å¡«ã |
| | | 2. `subjectCode` å¯ä¸æ ¡éªã |
| | | 3. å é¤ååå¼ç¨æ ¡éªï¼è¥è¢«åè¯åå½(`fin_voucher_entry.subject_code`)å¼ç¨ï¼ç¦æ¢å é¤ã |
| | | |
| | | --- |
| | | |
| | | ## 3. åºå®èµäº§ |
| | | |
| | | Base URLï¼`/financial/fixedAsset` |
| | | |
| | | ### 3.1 å页æ¥è¯¢ |
| | | - `GET /page` |
| | | - Queryï¼`current,size,assetCode,assetName,category,status` |
| | | |
| | | ### 3.2 æ°å¢ |
| | | - `POST /add` |
| | | - Bodyï¼JSONï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "assetCode": "GD20260512001", |
| | | "assetName": "åå
¬çµè", |
| | | "category": "electronic", |
| | | "specification": "ThinkPad X1", |
| | | "purchaseDate": "2026-05-01", |
| | | "originalValue": 8000.00, |
| | | "usefulLife": 5, |
| | | "residualRate": 5.00, |
| | | "location": "è´¢å¡é¨", |
| | | "department": "è´¢å¡é¨", |
| | | "keeper": "å¼ ä¸", |
| | | "status": "in_use", |
| | | "remark": "示ä¾" |
| | | } |
| | | ``` |
| | | |
| | | ### 3.3 ä¿®æ¹ |
| | | - `PUT /update` |
| | | - Bodyï¼åæ°å¢ï¼éå
å« `id` |
| | | |
| | | ### 3.4 å é¤ |
| | | - `DELETE /delete?ids=1&ids=2` |
| | | |
| | | ### 3.5 ææ§è®¡æï¼ææï¼ |
| | | - `POST /depreciate` |
| | | - Body å¯éï¼ |
| | | - å
¨é¨å¨ç¨èµäº§ï¼`{}` |
| | | - æå®èµäº§ï¼`{"ids":[1,2,3]}` |
| | | |
| | | æ ¸å¿å
¬å¼ï¼ |
| | | - `monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12)` |
| | | - `accumulatedDepreciation += monthlyDepreciation` |
| | | - `netValue = originalValue - accumulatedDepreciation` |
| | | |
| | | ç¶æå»ºè®®å¼ï¼ |
| | | - `in_use`ã`idle`ã`repair`ã`scrapped` |
| | | |
| | | --- |
| | | |
| | | ## 4. æ å½¢èµäº§ |
| | | |
| | | Base URLï¼`/financial/intangibleAsset` |
| | | |
| | | ### 4.1 å页æ¥è¯¢ |
| | | - `GET /page` |
| | | - Queryï¼`current,size,assetCode,assetName,category,status` |
| | | |
| | | ### 4.2 æ°å¢ |
| | | - `POST /add` |
| | | |
| | | ```json |
| | | { |
| | | "assetCode": "WX20260512001", |
| | | "assetName": "ERP软件许å¯", |
| | | "category": "software", |
| | | "certificateNo": "SW-001", |
| | | "acquisitionDate": "2026-05-01", |
| | | "originalValue": 50000.00, |
| | | "amortizationPeriod": 10, |
| | | "residualRate": 0.00, |
| | | "validityDate": "2036-05-01", |
| | | "status": "in_use", |
| | | "description": "示ä¾", |
| | | "remark": "" |
| | | } |
| | | ``` |
| | | |
| | | ### 4.3 ä¿®æ¹ |
| | | - `PUT /update` |
| | | - Bodyï¼åæ°å¢ï¼éå
å« `id` |
| | | |
| | | ### 4.4 å é¤ |
| | | - `DELETE /delete?ids=1&ids=2` |
| | | |
| | | ### 4.5 æé计æï¼ææï¼ |
| | | - `POST /amortize` |
| | | - Body å¯éï¼ |
| | | - å
¨é¨å¨ç¨èµäº§ï¼`{}` |
| | | - æå®èµäº§ï¼`{"ids":[1,2,3]}` |
| | | |
| | | æ ¸å¿å
¬å¼ï¼ |
| | | - `monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)` |
| | | - `accumulatedAmortization += monthlyAmortization` |
| | | - `netValue = originalValue - accumulatedAmortization` |
| | | - å½ `netValue <= 0` æ¶ï¼`netValue=0` ä¸ `status=amortized` |
| | | |
| | | ç¶æå»ºè®®å¼ï¼ |
| | | - `in_use`ã`expired`ã`amortized` |
| | | |
| | | --- |
| | | |
| | | ## 5. åè¯ |
| | | |
| | | Base URLï¼`/financial/voucher` |
| | | |
| | | ### 5.1 å页æ¥è¯¢ |
| | | - `GET /page` |
| | | - Queryï¼`current,size,voucherNo,creator,status,startDate,endDate` |
| | | |
| | | ### 5.2 æ°å¢ |
| | | - `POST /add` |
| | | |
| | | ```json |
| | | { |
| | | "voucherNo": "è®°-0001", |
| | | "voucherDate": "2026-05-12", |
| | | "summary": "é宿¶å
¥", |
| | | "creator": "å¼ ä¸", |
| | | "attachmentCount": 0, |
| | | "remark": "", |
| | | "entries": [ |
| | | { |
| | | "subjectCode": "1002", |
| | | "subjectName": "é¶è¡å款", |
| | | "summary": "æ¶å°è´§æ¬¾", |
| | | "debit": 1000.00, |
| | | "credit": 0 |
| | | }, |
| | | { |
| | | "subjectCode": "6001", |
| | | "subjectName": "主è¥ä¸å¡æ¶å
¥", |
| | | "summary": "确认æ¶å
¥", |
| | | "debit": 0, |
| | | "credit": 1000.00 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | ### 5.3 ä¿®æ¹ |
| | | - `PUT /update` |
| | | - Bodyï¼åæ°å¢ï¼éå
å« `id` |
| | | - ä»
`unposted` ç¶æå
è®¸ä¿®æ¹ |
| | | |
| | | ### 5.4 è¿è´¦ |
| | | - `POST /post` |
| | | |
| | | ```json |
| | | { |
| | | "id": 1 |
| | | } |
| | | ``` |
| | | |
| | | ### 5.5 ä½åº |
| | | - `POST /cancel` |
| | | |
| | | ```json |
| | | { |
| | | "id": 1 |
| | | } |
| | | ``` |
| | | |
| | | ### 5.6 详æ
|
| | | - `GET /detail/{id}` |
| | | |
| | | å
³é®æ ¡éªï¼ |
| | | 1. åå½è³å°ä¸æ¡ææè¡ï¼ç§ç®ä¸ç©ºï¼ä¸åæ¹æè´·æ¹ > 0ï¼ã |
| | | 2. æ¯æ¡ææåå½ä¸è½åè´·åæ¶å¤§äº 0ã |
| | | 3. å贷平衡ï¼`sum(debit) == sum(credit)` ä¸é½ > 0ã |
| | | 4. `subjectCode` å¿
é¡»åå¨äº `account_subject`ã |
| | | |
| | | ç¶ææµè½¬ï¼ |
| | | - `unposted -> posted` |
| | | - `unposted -> cancelled` |
| | | |
| | | --- |
| | | |
| | | ## 6. ç§ç®æ»è´¦ |
| | | |
| | | ### 6.1 æ¥è¯¢æ¥å£ |
| | | - `GET /financial/ledger/general` |
| | | |
| | | ### 6.2 Query åæ° |
| | | - `subjectCode`ï¼å¿
å¡«ï¼ |
| | | - `startMonth`ï¼YYYY-MMï¼ |
| | | - `endMonth`ï¼YYYY-MMï¼ |
| | | |
| | | ### 6.3 è¿ååæ®µ |
| | | - `rowType`ï¼`opening` / `entry` / `monthly_total` / `yearly_total` |
| | | - `date` |
| | | - `voucherNo` |
| | | - `summary` |
| | | - `debit` |
| | | - `credit` |
| | | - `direction`ï¼å/è´·ï¼ |
| | | - `balance`ï¼åæ£è´·è´ï¼ |
| | | |
| | | 说æï¼ |
| | | - ç§ç®æ¯æâæå®ç§ç®åå
¶ä¸çº§ï¼åç¼å¹é
ï¼âæ¥è¯¢ã |
| | | - èªå¨è¾åºâæåä½é¢ / æ¬æå计 / æ¬å¹´ç´¯è®¡âã |
| | | |
| | | --- |
| | | |
| | | ## 7. ç§ç®æç»è´¦ |
| | | |
| | | ### 7.1 æ¥è¯¢æ¥å£ |
| | | - `GET /financial/ledger/detail` |
| | | |
| | | ### 7.2 Query åæ° |
| | | - `subjectCode`ï¼å¿
å¡«ï¼ |
| | | - `auxiliaryType`ï¼å¯éï¼`customer/supplier/department/employee/project`ï¼ |
| | | - `auxiliaryId`ï¼å¯éï¼ |
| | | - `startMonth`ï¼YYYY-MMï¼ |
| | | - `endMonth`ï¼YYYY-MMï¼ |
| | | |
| | | ### 7.3 è¿ååæ®µ |
| | | åç§ç®æ»è´¦ï¼ |
| | | - `rowType,date,voucherNo,summary,debit,credit,direction,balance` |
| | | |
| | | --- |
| | | |
| | | ## 8. æ°æ®åºèæ¬ |
| | | |
| | | å·²æä¾èæ¬ï¼ |
| | | |
| | | - `doc/20260512_create_financial_management_tables.sql` |
| | | |
| | | å
å«ï¼ |
| | | - `fin_fixed_asset` |
| | | - `fin_intangible_asset` |
| | | - `fin_voucher` |
| | | - `fin_voucher_entry` |
| | | |
| | | æ»è´¦ç§ç®å¤ç¨ç°æ `account_subject`ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | ALTER TABLE `inspection_task` |
| | | ADD COLUMN `inspection_project` VARCHAR(100) NULL COMMENT 'å·¡æ£é¡¹ç®' AFTER `task_name`; |
| | | |
| | | ALTER TABLE `inspection_task` |
| | | ADD COLUMN `inspection_result` VARCHAR(1) NULL COMMENT 'å·¡æ£ç»æ 0å¼å¸¸ 1æ£å¸¸' AFTER `remarks`, |
| | | ADD COLUMN `abnormal_description` VARCHAR(500) NULL COMMENT 'å¼å¸¸æè¿°' AFTER `inspection_result`, |
| | | ADD COLUMN `device_repair_id` BIGINT NULL COMMENT 'å
³èç»´ä¿®åID' AFTER `abnormal_description`, |
| | | ADD COLUMN `acceptance_user_id` BIGINT NULL COMMENT 'éªæ¶äººID' AFTER `device_repair_id`, |
| | | ADD COLUMN `acceptance_name` VARCHAR(100) NULL COMMENT 'éªæ¶äºº' AFTER `acceptance_user_id`; |
| | | |
| | | ALTER TABLE `timing_task` |
| | | ADD COLUMN `inspection_project` VARCHAR(100) NULL COMMENT 'å·¡æ£é¡¹ç®' AFTER `task_name`, |
| | | ADD COLUMN `is_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'æ¯å¦å¯ç¨ 0å¦ 1æ¯' AFTER `is_active`; |
| | | |
| | | CREATE INDEX `idx_inspection_task_device_repair_id` |
| | | ON `inspection_task` (`device_repair_id`); |
| | | |
| | | CREATE INDEX `idx_inspection_task_inspection_result` |
| | | ON `inspection_task` (`inspection_result`); |
| | | |
| | | CREATE INDEX `idx_timing_task_is_enabled` |
| | | ON `timing_task` (`is_enabled`); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # 设å¤å·¡æ£ä¸å®æ¶å·¡æ£å端èè°ææ¡£ï¼inspectiontaskï¼ |
| | | |
| | | > æ´æ°æ¥æï¼2026-05-15 |
| | | > éç¨æ¨¡åï¼è®¾å¤å·¡æ£ä»»å¡ `inspectiontask`ï¼`/inspectionTask`ï¼ä¸å®æ¶å·¡æ£ä»»å¡ï¼`/timingTask`ï¼ |
| | | |
| | | ## 1. æ¬æ¬¡æ¹å¨ |
| | | |
| | | 1. å·¡æ£ä»»å¡æ°å¢åæ®µï¼ |
| | | - `inspectionProject`ï¼å·¡æ£é¡¹ç®ï¼ |
| | | - `inspectionResult`ï¼å·¡æ£ç»æï¼`0`å¼å¸¸ / `1`æ£å¸¸ï¼å¿
å¡«ï¼ |
| | | - `abnormalDescription`ï¼å¼å¸¸æè¿°ï¼ |
| | | - `deviceRepairId`ï¼å
³èç»´ä¿®åIDï¼å¼å¸¸æ¶å端èªå¨åå¡«ï¼ |
| | | - `acceptanceUserId`ï¼éªæ¶äººIDï¼ |
| | | - `acceptanceName`ï¼éªæ¶äººï¼ |
| | | 2. å¼å¸¸æ ¡éªè§åï¼ |
| | | - `inspectionResult=1`ï¼æ£å¸¸ï¼ï¼ç
§çéå¿
å¡«ã |
| | | - `inspectionResult=0`ï¼å¼å¸¸ï¼ï¼å¿
é¡»æç
§çï¼ä¸å¿
须填å `abnormalDescription`ã |
| | | 3. å¼å¸¸èå¨è§åï¼ |
| | | - å¼å¸¸ä¿ååèªå¨çæ `device_repair` å¹¶åå¡« `deviceRepairId`ã |
| | | 4. 宿¶ä»»å¡æ°å¢åæ®µï¼ |
| | | - `inspectionProject`ï¼å·¡æ£é¡¹ç®ï¼ |
| | | - `isEnabled`ï¼æ¯å¦å¯ç¨ï¼`0`å¦ / `1`æ¯ï¼ |
| | | 5. 夿³¨å¸¦å
¥è§åï¼ |
| | | - 宿¶ä»»å¡èªå¨çæå·¡æ£è®°å½æ¶ï¼è¥å®æ¶ä»»å¡ `remarks` æå¼ï¼ä¼æ¼æ¥å°å·¡æ£è®°å½å¤æ³¨ä¸ã |
| | | |
| | | ## 2. æ°æ®åºåæ´ |
| | | |
| | | èè°åæ§è¡ SQLï¼ |
| | | |
| | | - [doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql](/D:/ç马/åé/å端/product-inventory-management-after-jdk25/doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql) |
| | | |
| | | > 说æï¼è¯¥èæ¬å½åä½ç¨äº `inspection_task` ä¸ `timing_task` ä¸¤å¼ è¡¨ï¼æä»¶ååå²ä¿çæªæ¹ã |
| | | |
| | | ## 3. å·¡æ£ä»»å¡æ¥å£ |
| | | |
| | | ### 3.1 ä¿åæ¥å£ |
| | | |
| | | `POST /inspectionTask/addOrEditInspectionTask` |
| | | |
| | | | åæ®µ | ç±»å | å¿
å¡« | 说æ | |
| | | |---|---|---|---| |
| | | | id | long | å¦ | æå¼=ä¿®æ¹ï¼æ å¼=æ°å¢ | |
| | | | taskId | int | 建议å¿
å¡« | 设å¤IDï¼ç¨äºå¼å¸¸èªå¨å»ºç»´ä¿®åï¼ | |
| | | | taskName | string | 建议å¿
å¡« | 设å¤åç§° | |
| | | | inspectionProject | string | å¦ | å·¡æ£é¡¹ç® | |
| | | | inspectorId | string | å¦ | å·¡æ£äººIDï¼æ¯æéå·åé | |
| | | | inspectionResult | string | æ¯ | `0`=å¼å¸¸ï¼`1`=æ£å¸¸ | |
| | | | abnormalDescription | string | æ¡ä»¶å¿
å¡« | å¼å¸¸æ¶å¿
å¡« | |
| | | | acceptanceUserId | long | å¦ | éªæ¶äººID | |
| | | | acceptanceName | string | å¦ | éªæ¶äººå§å | |
| | | | commonFileListDTO | array | æ¡ä»¶å¿
å¡« | éä»¶ç»1ï¼å¼å¸¸æ¶ä¸ç»è³å°ä¸ç»æå¾ï¼ | |
| | | | commonFileListAfterDTO | array | æ¡ä»¶å¿
å¡« | éä»¶ç»2ï¼å¼å¸¸æ¶ä¸ç»è³å°ä¸ç»æå¾ï¼ | |
| | | | commonFileListBeforeDTO | array | æ¡ä»¶å¿
å¡« | éä»¶ç»3ï¼å¼å¸¸æ¶ä¸ç»è³å°ä¸ç»æå¾ï¼ | |
| | | |
| | | å¼å¸¸ç¤ºä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "taskId": 1001, |
| | | "taskName": "ç©ºåæºA-01", |
| | | "inspectionProject": "润æ»ç³»ç»", |
| | | "inspectorId": "12", |
| | | "inspectionResult": "0", |
| | | "abnormalDescription": "çµæºå¼åï¼æ¸©ååé«", |
| | | "acceptanceUserId": 20, |
| | | "commonFileListDTO": [ |
| | | { |
| | | "id": 90001, |
| | | "application": "file" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | æ£å¸¸ç¤ºä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "taskId": 1001, |
| | | "taskName": "ç©ºåæºA-01", |
| | | "inspectionProject": "ç¹æ£", |
| | | "inspectorId": "12", |
| | | "inspectionResult": "1", |
| | | "acceptanceUserId": 20 |
| | | } |
| | | ``` |
| | | |
| | | ### 3.2 å表æ¥å£ |
| | | |
| | | `GET /inspectionTask/list` |
| | | |
| | | è¿åå
嫿°å¢åæ®µï¼ |
| | | |
| | | - `inspectionProject` |
| | | - `inspectionResult` |
| | | - `abnormalDescription` |
| | | - `deviceRepairId` |
| | | - `acceptanceUserId` |
| | | - `acceptanceName` |
| | | |
| | | ## 4. 宿¶ä»»å¡æ¥å£ |
| | | |
| | | ### 4.1 ä¿åæ¥å£ |
| | | |
| | | `POST /timingTask/addOrEditTimingTask` |
| | | |
| | | æ°å¢/æ´æ°åæ®µï¼ |
| | | |
| | | | åæ®µ | ç±»å | å¿
å¡« | 说æ | |
| | | |---|---|---|---| |
| | | | inspectionProject | string | å¦ | å·¡æ£é¡¹ç® | |
| | | | remarks | string | å¦ | 夿³¨ | |
| | | | isEnabled | int | å¦ | `0`=ç¦ç¨ï¼`1`=å¯ç¨ï¼ä¸ä¼ é»è®¤å¯ç¨ | |
| | | |
| | | 示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "taskName": "ç©ºåæºA-01宿¶å·¡æ£", |
| | | "inspectionProject": "æåº¦å·¡æ£", |
| | | "taskId": 1001, |
| | | "inspectorIds": "12,13", |
| | | "frequencyType": "DAILY", |
| | | "frequencyDetail": "09:00", |
| | | "remarks": "éç¹æ£æ¥è½´æ¿æ¸©åº¦", |
| | | "isEnabled": 1 |
| | | } |
| | | ``` |
| | | |
| | | ### 4.2 å¯ç¨ç¶æè¡ä¸º |
| | | |
| | | 1. `isEnabled=1`ï¼ä»»å¡è¿å
¥è°åº¦ï¼æé¢æ¬¡èªå¨çæå·¡æ£è®°å½ã |
| | | 2. `isEnabled=0`ï¼ä»»å¡ä¸è°åº¦ï¼å·²åå¨è°åº¦ä¼è¢«ç§»é¤ã |
| | | |
| | | ### 4.3 夿³¨å¸¦å
¥è§å |
| | | |
| | | 宿¶ä»»å¡èªå¨çæå·¡æ£è®°å½æ¶ï¼ |
| | | |
| | | 1. å·¡æ£è®°å½å¤æ³¨åºå®åç¼ï¼`èªå¨çæèªå®æ¶ä»»å¡ID: {id}` |
| | | 2. å½å®æ¶ä»»å¡ `remarks` é空æ¶ï¼æ¼æ¥ä¸ºï¼ |
| | | `èªå¨çæèªå®æ¶ä»»å¡ID: {id}ï¼{remarks}` |
| | | |
| | | ## 5. å¼å¸¸èªå¨å»ºç»´ä¿®åè§å |
| | | |
| | | å½å·¡æ£è®°å½ `inspectionResult=0` æ¶ï¼ |
| | | |
| | | 1. è¥ `deviceRepairId` 为空ï¼å端èªå¨å建 `device_repair`ï¼ |
| | | - `deviceLedgerId`ï¼æ¥èª `taskId` |
| | | - `deviceName`ï¼ä¼å
`taskName`ï¼å¦åå设å¤å°è´¦åç§° |
| | | - `remark`ï¼å¼å¸¸æè¿° |
| | | - `status`ï¼`0`ï¼å¾
ç»´ä¿®ï¼ |
| | | 2. è¥å·²æå
³èç»´ä¿®åï¼ä»
åæ¥æ´æ°ç»´ä¿®å `remark`ã |
| | | |
| | | ## 6. å端æ¹é 建议 |
| | | |
| | | 1. å·¡æ£è¡¨åæ°å¢ `inspectionProject` è¾å
¥æ¡ã |
| | | 2. å·¡æ£è¡¨åä¿çâæ£å¸¸/å¼å¸¸âè卿 ¡éªï¼ |
| | | - å¼å¸¸æ¶å¼ºå¶å¼å¸¸æè¿° + è³å°ä¸ç»å¾çã |
| | | 3. 宿¶ä»»å¡è¡¨åæ°å¢âæ¯å¦å¯ç¨âå¼å
³å¹¶æ å° `isEnabled`ã |
| | | 4. 宿¶ä»»å¡è¡¨åæ°å¢ `inspectionProject` ä¸ `remarks` è¾å
¥é¡¹ã |
| | | 5. å·¡æ£å表å±ç¤º `inspectionProject` å `deviceRepairId`ï¼æ¯æè·³è½¬ç»´ä¿®å详æ
ï¼ã |
| | | |
| | | ## 7. èè°éªæ¶æ¸
å |
| | | |
| | | 1. å·¡æ£æ°å¢/ä¿®æ¹å¯æ£ç¡®æäº¤ `inspectionProject` å¹¶å¨åè¡¨åæ¾ã |
| | | 2. å¼å¸¸å·¡æ£ï¼ææè¿°+æå¾ï¼ä¿åæåå¹¶åå¡« `deviceRepairId`ã |
| | | 3. å¼å¸¸å·¡æ£ç¼ºæè¿°æç¼ºå¾çæ¶è¢«æ¦æªã |
| | | 4. 宿¶ä»»å¡ `isEnabled=0` æ¶ä¸å触åèªå¨å·¡æ£è®°å½ã |
| | | 5. 宿¶ä»»å¡ `isEnabled=1` æ¶æé¢æ¬¡çæå·¡æ£è®°å½ã |
| | | 6. 宿¶ä»»å¡æ `remarks` æ¶ï¼èªå¨å·¡æ£è®°å½å¤æ³¨å¸¦ä¸è¯¥å
容ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # å¶é æºè½å©æå端èè°ææ¡£ï¼`manufacturing-ai`ï¼ |
| | | |
| | | > æ´æ°æ¥æï¼2026-05-16 |
| | | > éç¨æ¨¡åï¼ç产ç°åºã计åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤ç |
| | | > è½åèå´ï¼æ¥ãé®ãåãé¢è¦ãåæ |
| | | |
| | | ## 1. æ¥å£æ»è§ |
| | | |
| | | 1. æµå¼å¯¹è¯ï¼`POST /manufacturing-ai/chat` |
| | | 2. ä¼è¯å表ï¼`GET /manufacturing-ai/history/sessions` |
| | | 3. ä¼è¯æ¶æ¯ï¼`GET /manufacturing-ai/history/messages/{memoryId}` |
| | | 4. å é¤ä¼è¯ï¼`DELETE /manufacturing-ai/history/{memoryId}` |
| | | |
| | | 说æï¼ |
| | | - `/chat` 为 **SSE/æµå¼ææ¬** è¿åï¼`text/stream;charset=utf-8`ï¼ã |
| | | - å½ä¸âæ¥/é¢è¦/åæ/åâå·¥å
·æ¶ï¼æµå¼æç»å
å®¹æ¯ **JSON å符串**ï¼ä¸æ¯ `AjaxResult`ï¼ã |
| | | - æªå½ä¸å·¥å
·æ¶ï¼è¿åæ®éèªç¶è¯è¨ææ¬ã |
| | | |
| | | ## 2. é´æä¸è¯·æ±å¤´ |
| | | |
| | | - ç»ä¸ä½¿ç¨ç³»ç»ç»å½æï¼`Authorization` ä¸ç°ææ¥å£ä¸è´ï¼ã |
| | | - `POST /manufacturing-ai/chat` 请æ±å¤´ï¼`Content-Type: application/json`ã |
| | | |
| | | ## 3. å¯¹è¯æ¥å£ |
| | | |
| | | ### 3.1 è¯·æ± |
| | | |
| | | ```http |
| | | POST /manufacturing-ai/chat |
| | | Content-Type: application/json |
| | | ``` |
| | | |
| | | ```json |
| | | { |
| | | "memoryId": "mfg-ai-001", |
| | | "message": "æ¥è®¾å¤è¥¿é¨ååé¢å¨çç»´ä¿®æ
åµ" |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | | åæ®µ | ç±»å | å¿
å¡« | 说æ | |
| | | | --- | --- | --- | --- | |
| | | | memoryId | string | æ¯ | ä¼è¯ IDï¼å端çæå¹¶å¤ç¨ | |
| | | | message | string | æ¯ | ç¨æ·è¾å
¥ | |
| | | |
| | | ### 3.2 è¿åï¼æµå¼ï¼ |
| | | |
| | | ```http |
| | | Content-Type: text/stream;charset=utf-8 |
| | | ``` |
| | | |
| | | å端å¤çå»ºè®®ï¼ |
| | | 1. ææµæ¼æ¥å®æ´ææ¬ã |
| | | 2. å°è¯ `JSON.parse(fullText)`ï¼ |
| | | - æåï¼æç»æåç»ææ¸²æã |
| | | - å¤±è´¥ï¼ææ®éèå¤©ææ¬æ¸²æã |
| | | |
| | | ## 4. ç»æåååºåè®® |
| | | |
| | | ### 4.1 éç¨ç»æ |
| | | |
| | | ```json |
| | | { |
| | | "success": true, |
| | | "type": "manufacturing_device_repair_list", |
| | | "description": "å·²è¿å设å¤ç»´ä¿®è®°å½ã", |
| | | "summary": {}, |
| | | "data": {}, |
| | | "charts": {} |
| | | } |
| | | ``` |
| | | |
| | | ### 4.2 `type` æä¸¾ |
| | | |
| | | | type | åºæ¯ | |
| | | | --- | --- | |
| | | | manufacturing_site_snapshot | ç产ç°åºæ¦è§ | |
| | | | manufacturing_plan_list | çäº§è®¡åæ¥è¯¢ | |
| | | | manufacturing_workorder_list | å·¥åæ¥è¯¢ | |
| | | | manufacturing_device_list | 设å¤å°è´¦æ¥è¯¢ | |
| | | | manufacturing_device_repair_list | 设å¤ç»´ä¿®è®°å½æ¥è¯¢ | |
| | | | manufacturing_quality_list | è´¨éæ¥è¯¢ | |
| | | | manufacturing_material_list | ç©æåºåæ¥è¯¢ | |
| | | | manufacturing_exception_list | å¼å¸¸å¤çæ¥è¯¢ | |
| | | | manufacturing_warning | é¢è¦çæ¿ | |
| | | | manufacturing_analysis | ç»è¥åæ | |
| | | | manufacturing_action_plan | åç建议ï¼å¨ä½å¡ï¼ | |
| | | |
| | | ## 5. âæ¥âè½åèè°è¦ç¹ |
| | | |
| | | ### 5.1 设å¤ç¸å
³è·¯ç±è§åï¼å
³é®ï¼ |
| | | |
| | | - å½ç¨æ·è¾å
¥å
å« `ç»´ä¿®/æ¥ä¿®/æ£ä¿®/ç»´æ¤`ï¼è®¾å¤åä¼è¿å `manufacturing_device_repair_list`ï¼æ¥ `device_repair`ï¼ã |
| | | - æªå
å«ä»¥ä¸è¯æ¶ï¼è¿å `manufacturing_device_list`ï¼æ¥è®¾å¤å°è´¦ï¼ã |
| | | |
| | | 示ä¾ï¼ |
| | | - `æ¥è®¾å¤A-01` -> `manufacturing_device_list` |
| | | - `æ¥è®¾å¤A-01ç»´ä¿®æ
åµ` -> `manufacturing_device_repair_list` |
| | | |
| | | ### 5.2 ç»´ä¿®è®°å½æ¶é´è¿æ»¤è§åï¼å
³é®ï¼ |
| | | |
| | | - ç¨æ·æç¡®å¸¦æ¶é´æ¡ä»¶ï¼å¦âæ¬æ/ä¸å¨/è¿7天/2026-05-01 å° 2026-05-16âï¼æææ¶é´è¿æ»¤ç»´ä¿®è®°å½ã |
| | | - æªå¸¦æ¶é´æ¡ä»¶æ¶ï¼ä¸é»è®¤æè¿ 30 å¤©æªæï¼é¿å
åå²ç»´ä¿®è®°å½è¢«è¯¯è¿æ»¤ã |
| | | |
| | | ### 5.3 å
³é®è¯å¤çè§åï¼è®¾å¤/ç»´ä¿®ï¼ |
| | | |
| | | - ç³»ç»ä¼æ¸
æ´åªé³è¯ï¼`æ¥è¯¢/æ¥ç/请/设å¤/ç»´ä¿®æ
åµ/è®°å½/ä¿¡æ¯` çã |
| | | - åæ¶ä¼éè¿è®¾å¤å°è´¦å¹é
`deviceLedgerId` å
åºï¼ååæ¥ç»´ä¿®è®°å½ï¼éä½âææ°æ®ä½æ¥ä¸å°âçæ¦çã |
| | | |
| | | ### 5.4 åè¡¨ç»æçº¦å® |
| | | |
| | | - åè¡¨æ°æ®ç»ä¸å¨ `data.items` |
| | | - ç»è®¡æè¦å¨ `summary` |
| | | |
| | | 常ç¨åæ®µï¼ |
| | | |
| | | | type | 常ç¨å段 | |
| | | | --- | --- | |
| | | | manufacturing_plan_list | `mpsNo`, `requiredDate`, `status` | |
| | | | manufacturing_workorder_list | `workOrderNo`, `planStartTime`, `planEndTime`, `status` | |
| | | | manufacturing_device_list | `deviceName`, `deviceModel`, `pendingRepairCount` | |
| | | | manufacturing_device_repair_list | `deviceName`, `deviceModel`, `repairTime`, `repairName`, `maintenanceName`, `status`, `createTime` | |
| | | |
| | | ## 6. âé¢è¦âèè°è¦ç¹ |
| | | |
| | | - `type = manufacturing_warning` |
| | | - é¢è¦æç»å¨ `data.items`ï¼æ¯é¡¹å
å«ï¼ |
| | | - `level`ï¼`high` / `medium` |
| | | - `title` |
| | | - `count` |
| | | - `detail` |
| | | |
| | | ç¶æå£å¾ï¼ |
| | | - 设å¤âå¾
ç»´ä¿®âç»è®¡æ `status = 0` 计ç®ï¼ä¸åæå
¶ä»ç¶æè®¡å
¥å¾
ç»´ä¿®ï¼ã |
| | | |
| | | ## 7. âåæâèè°è¦ç¹ |
| | | |
| | | - `type = manufacturing_analysis` |
| | | - å
³é®ææ å¨ `summary` |
| | | - ææ å¡å¨ `data.coreMetrics` |
| | | - å¾è¡¨é
ç½®å¨ `charts`ï¼ |
| | | - `charts.domainBarOption` |
| | | - `charts.qualityPieOption` |
| | | |
| | | å¾è¡¨é
ç½®å¯ç´æ¥ç» ECharts 使ç¨ã |
| | | |
| | | ## 8. âåâè½åèè°è¦ç¹ |
| | | |
| | | å½åâåâ为 **åç建议模å¼**ï¼AI è¾åºå¨ä½å¡ï¼å端确认åè°ç¨ç®æ ä¸å¡æ¥å£ï¼ã |
| | | |
| | | - `type = manufacturing_action_plan` |
| | | - å¨ä½å¡æ°ç»ï¼`data.actionCards` |
| | | |
| | | å¨ä½å¡åæ®µï¼ |
| | | |
| | | | åæ®µ | 说æ | |
| | | | --- | --- | |
| | | | code | å¨ä½ç¼ç | |
| | | | name | å¨ä½åç§° | |
| | | | method | è¯·æ±æ¹æ³ | |
| | | | targetApi | ç®æ ä¸å¡æ¥å£ | |
| | | | requiredFields | å¿
å¡«åæ®µ | |
| | | | examplePayload | 示ä¾åæ° | |
| | | | description | 说æ | |
| | | |
| | | å
ç½®å¨ä½ç¤ºä¾ï¼ |
| | | 1. `POST /productionOperationTask/assign` |
| | | 2. `POST /device/repair` |
| | | 3. `POST /quality/qualityUnqualified/deal` |
| | | 4. `POST /stockInventory/addstockInventory` |
| | | 5. `POST /procurementExceptionRecord/add` |
| | | |
| | | ## 9. ä¼è¯ç®¡çæ¥å£ |
| | | |
| | | ### 9.1 ä¼è¯å表 |
| | | |
| | | ```http |
| | | GET /manufacturing-ai/history/sessions |
| | | ``` |
| | | |
| | | `AjaxResult.data` åæ®µï¼ |
| | | - `memoryId` |
| | | - `title` |
| | | - `lastMessage` |
| | | - `messageCount` |
| | | - `lastChatTime` |
| | | |
| | | ### 9.2 ä¼è¯æ¶æ¯ |
| | | |
| | | ```http |
| | | GET /manufacturing-ai/history/messages/{memoryId} |
| | | ``` |
| | | |
| | | `AjaxResult.data` åæ®µï¼ |
| | | - `role`ï¼`user` / `assistant` / `system` / `tool` |
| | | - `content` |
| | | - `filePaths` |
| | | |
| | | ### 9.3 å é¤ä¼è¯ |
| | | |
| | | ```http |
| | | DELETE /manufacturing-ai/history/{memoryId} |
| | | ``` |
| | | |
| | | è¿åæ å `AjaxResult`ã |
| | | |
| | | ## 10. é误ä¸è¾¹ç |
| | | |
| | | `/chat` 常è§è¿åææ¬ï¼ |
| | | - `memoryIdä¸è½ä¸ºç©º` |
| | | - `messageä¸è½ä¸ºç©º` |
| | | |
| | | 建议å端åéåå
åå¿
å¡«æ ¡éªã |
| | | |
| | | ## 11. å端èè°æµç¨å»ºè®® |
| | | |
| | | 1. ç»å½åå建并å¤ç¨ `memoryId`ã |
| | | 2. è°ç¨ `/manufacturing-ai/chat`ï¼æ SSE æ¼æ¥å®æ´ææ¬ã |
| | | 3. å
å°è¯ JSON è§£æï¼ |
| | | - æåï¼æ `type` è·¯ç±å°å¯¹åº UIï¼å表/é¢è¦/åæ/å¨ä½å¡ï¼ã |
| | | - å¤±è´¥ï¼ææ®éèå¤©æ¶æ¯å±ç¤ºã |
| | | 4. âåâåºæ¯ç±ç¨æ·ç¡®è®¤å¨ä½å¡åï¼å端è°ç¨ `targetApi` 宿ä¸å¡æäº¤ã |
| | | 5. éè¿å岿¥å£åä¼è¯åæ¾ä¸å é¤ã |
| | | |
| | | ## 12. å端éæçº¦æï¼æ¬æ¬¡è¡¥å
ï¼ |
| | | |
| | | ### 12.1 æºè½ä½æ°å¢ä¸å¼¹çªåæ¥è§åï¼å¼ºå¶ï¼ |
| | | |
| | | 1. å½ `src/views/aiIndustrialBrain/index.vue` æ°å¢æºè½ä½ï¼`agents`ï¼é»è¾æ¶ï¼å¿
é¡»åæ¥ç¡®è®¤å¼¹çªå©æå¯ç¨æ§ã |
| | | 2. å¼¹çªå©æç»ä¸ç± `src/components/AIChatSidebar/assistants/index.js` ç `assistantRegistry` 注åã |
| | | 3. æ°å¢æºè½ä½ç `key` è¥è¦å¨å¼¹çªä¸å¯ç¨ï¼å¿
é¡»å¨ `assistantRegistry` 䏿ä¾ååé
ç½®ã |
| | | 4. æªå¨ `assistantRegistry` 注åçæºè½ä½ï¼å¼¹çªæ¾ç¤ºä¸º `pending`ï¼å¼åä¸ï¼æã |
| | | |
| | | ### 12.2 çäº§å©ææ¥å
¥çº¦å® |
| | | |
| | | 1. çäº§å©æé
ç½®ä½äº `src/components/AIChatSidebar/assistants/productionAssistant.js`ï¼`apiBase = /manufacturing-ai`ã |
| | | 2. AI å·¥ä¸å¤§èä¸ç产æºè½ä½è¿å
¥å¼¹çªåï¼é»è®¤ä½¿ç¨ `production` 婿ã |
| | | 3. å
¨å±å³ä¾§å¯¹è¯æ¡å©æåæ¢å表已å
å«ï¼ |
| | | - `general`ï¼å¾
åå©çï¼ |
| | | - `purchase`ï¼éè´å©çï¼ |
| | | - `production`ï¼ç产å©çï¼ |
| | | |
| | | ### 12.3 åæ®µä¸æåå±ç¤ºè§å |
| | | |
| | | 1. é¢åä¸å¡ç¨æ·çåæ®µåãæ ç¾ãå¿
å¡«æç¤ºä¸ç´æ¥å±ç¤ºè±æ keyã |
| | | 2. `requiredFields`ã`missingFields` æç¤ºé转æ¢ä¸ºä¸æè·¯å¾æ ç¾ï¼ç¤ºä¾ï¼`缺å°å¿
å¡«åæ®µï¼å·¥åå·ã计åç»ææ¶é´`ï¼ã |
| | | 3. ç»æåå表ååãæè¦ææ ãå¨ä½å¡å段ä¼å
æ¾ç¤ºä¸æï¼è±æ key ä»
ç¨äºæ¥å£éä¿¡ä¸è°è¯ã |
| | | |
| | | ## 13. æ¬æ¬¡æ´æ°è®°å½ï¼2026-05-16ï¼ |
| | | |
| | | 1. æ°å¢è®¾å¤ç»´ä¿®è®°å½è¿åç±»åï¼`manufacturing_device_repair_list`ã |
| | | 2. ä¿®æ£è®¾å¤åæå¾åæµï¼`ç»´ä¿®/æ¥ä¿®/æ£ä¿®/ç»´æ¤` 走维修记å½ï¼ä¸å误走设å¤å表ã |
| | | 3. ä¿®æ£ç»´ä¿®è®°å½æ¶é´è¿æ»¤ï¼ä»
å¨ç¨æ·æç¡®æ¶é´æ¡ä»¶æ¶çæã |
| | | 4. ä¿®æ£å¾
ç»´ä¿®ç»è®¡å£å¾ï¼æ `status = 0` ç»è®¡ã |
| | | 5. æ°å¢ AI å·¥ä¸å¤§èæºè½ä½ä¸å¼¹çªåæ¥ç»´æ¤è§åï¼æ°å¢æºè½ä½å¿
é¡»åæ¥æ³¨åå¼¹çªå©æã |
| | | 6. çäº§å©æå·²æ¥å
¥å·¥ä¸å¤§èå¼¹çªä¸å
¨å±å³ä¾§å¯¹è¯æ¡å©æåæ¢ã |
| | | 7. å¢å åæ®µä¸æåå±ç¤ºçº¦æï¼é¿å
è±æåæ®µå¯¹ä¸å¡ç¨æ·ç´åºã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # éå®å©æå端èè°ææ¡£ï¼`/sales-ai`ï¼ |
| | | > æ´æ°æ¶é´ï¼2026-05-18 |
| | | > éç¨æ¨¡åï¼å®¢æ·æ¡£æ¡ï¼ç§æµ·/å
¬æµ·ï¼ãé宿¥ä»·ãéå®å°è´¦ãéå®éè´§ã客æ·å¾æ¥ãåè´§å°è´¦ãææ ç»è®¡ |
| | | > éç¹è½åï¼å®¢æ·æµå¤±é£é©åæã忬¾ä¸æ¥ä»·çç¥å»ºè®® |
| | | |
| | | ## 1. æ¥å£æ»è§ |
| | | |
| | | 1. æµå¼å¯¹è¯ï¼`POST /sales-ai/chat` |
| | | 2. ä¼è¯å表ï¼`GET /sales-ai/history/sessions` |
| | | 3. ä¼è¯æ¶æ¯ï¼`GET /sales-ai/history/messages/{memoryId}` |
| | | 4. å é¤ä¼è¯ï¼`DELETE /sales-ai/history/{memoryId}` |
| | | |
| | | 说æï¼ |
| | | - `/chat` è¿å `text/stream;charset=utf-8`ï¼SSE ææ¬æµï¼ã |
| | | - å½ä¸å·¥å
·æ¶ï¼æç»å
容为 **JSON å符串**ï¼é `AjaxResult`ï¼ã |
| | | - æªå½ä¸å·¥å
·æ¶ï¼è¿åæ®éä¸æææ¬ã |
| | | |
| | | ## 2. å¯¹è¯æ¥å£ |
| | | |
| | | ### 2.1 è¯·æ± |
| | | |
| | | ```http |
| | | POST /sales-ai/chat |
| | | Content-Type: application/json |
| | | ``` |
| | | |
| | | ```json |
| | | { |
| | | "memoryId": "sales-ai-001", |
| | | "message": "帮æåå®¢æ·æµå¤±é£é©åæï¼è¿90天ï¼å10æ¡" |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | | åæ®µ | ç±»å | å¿
å¡« | 说æ | |
| | | | --- | --- | --- | --- | |
| | | | `memoryId` | string | æ¯ | ä¼è¯ IDï¼å端çæå¹¶å¤ç¨ | |
| | | | `message` | string | æ¯ | ç¨æ·è¾å
¥ | |
| | | |
| | | ### 2.2 è¿åå¤ç |
| | | |
| | | å端建议æµç¨ï¼ |
| | | 1. å
ææµæ¼æ¥å®æ´ææ¬ `fullText`ã |
| | | 2. å°è¯ `JSON.parse(fullText)`ï¼ |
| | | - æåï¼æ `type` è·¯ç±å°ç»æåç»ä»¶ã |
| | | - å¤±è´¥ï¼ææ®éèå¤©ææ¬å±ç¤ºã |
| | | |
| | | ## 3. ç»æåååºåè®® |
| | | |
| | | ### 3.1 éç¨ç»æ |
| | | |
| | | ```json |
| | | { |
| | | "success": true, |
| | | "type": "sales_dashboard", |
| | | "description": "å·²è¿åé宿æ ç»è®¡", |
| | | "summary": {}, |
| | | "data": {}, |
| | | "charts": {} |
| | | } |
| | | ``` |
| | | |
| | | ### 3.2 `type` æä¸¾ |
| | | |
| | | | type | åºæ¯ | |
| | | | --- | --- | |
| | | | `sales_customer_profile_list` | å®¢æ·æ¡£æ¡ï¼ç§æµ·/å
¬æµ·ï¼ | |
| | | | `sales_quotation_list` | é宿¥ä»· | |
| | | | `sales_ledger_list` | éå®å°è´¦ | |
| | | | `sales_return_list` | éå®éè´§ | |
| | | | `sales_customer_interaction_list` | 客æ·å¾æ¥ï¼åæ¬¾ï¼ | |
| | | | `sales_shipping_list` | åè´§å°è´¦ | |
| | | | `sales_dashboard` | ææ ç»è®¡ | |
| | | | `sales_customer_churn_risk` | å®¢æ·æµå¤±é£é©åæ | |
| | | | `sales_collection_quote_strategy` | 忬¾ä¸æ¥ä»·çç¥å»ºè®® | |
| | | |
| | | ## 4. èåè½åæ å°ï¼å¯¹åºè¥é管çï¼ |
| | | |
| | | 1. å®¢æ·æ¡£æ¡ï¼ç§æµ·ï¼ï¼ç¤ºä¾æé® `æ¥è¯¢ç§æµ·å®¢æ·æ¡£æ¡å10æ¡` |
| | | 2. å®¢æ·æ¡£æ¡ï¼å
¬æµ·ï¼ï¼ç¤ºä¾æé® `æ¥è¯¢å
¬æµ·å®¢æ·æ¡£æ¡` |
| | | 3. é宿¥ä»·ï¼ç¤ºä¾æé® `æ¥è¯¢æ¬æé宿¥ä»·` |
| | | 4. éå®å°è´¦ï¼ç¤ºä¾æé® `æ¥è¯¢æ¬æéå®å°è´¦` |
| | | 5. éå®éè´§ï¼ç¤ºä¾æé® `æ¥è¯¢è¿30天éå®éè´§` |
| | | 6. 客æ·å¾æ¥ï¼ç¤ºä¾æé® `æ¥è¯¢è¿30天客æ·åæ¬¾å¾æ¥` |
| | | 7. åè´§å°è´¦ï¼ç¤ºä¾æé® `æ¥è¯¢æ¬æåè´§å°è´¦` |
| | | 8. ææ ç»è®¡ï¼ç¤ºä¾æé® `æ¥çé宿æ ç»è®¡` |
| | | |
| | | ## 5. éç¹è½åèè° |
| | | |
| | | ### 5.1 å®¢æ·æµå¤±é£é©åæï¼`sales_customer_churn_risk`ï¼ |
| | | |
| | | æ°æ®ä½ç½®ï¼ |
| | | - å表ï¼`data.items` |
| | | - æ±æ»ï¼`summary.highRiskCount / mediumRiskCount / lowRiskCount` |
| | | - å¾è¡¨ï¼`charts.riskLevelPieOption`ã`charts.riskScoreBarOption` |
| | | |
| | | å项常ç¨åæ®µï¼ |
| | | - `customerName` |
| | | - `riskLevel`ï¼`high`/`medium`/`low`ï¼ |
| | | - `riskScore`ï¼0-100ï¼ |
| | | - `pendingAmount` |
| | | - `pendingRate` |
| | | - `daysSinceLastOrder` |
| | | - `riskReasons`ï¼å符串æ°ç»ï¼ |
| | | |
| | | ### 5.2 忬¾ä¸æ¥ä»·çç¥å»ºè®®ï¼`sales_collection_quote_strategy`ï¼ |
| | | |
| | | æ°æ®ä½ç½®ï¼ |
| | | - çç¥å¡ï¼`data.items` |
| | | - æ±æ»ï¼`summary.highPriorityCount / mediumPriorityCount / lowPriorityCount` |
| | | - å¾è¡¨ï¼`charts.pendingAmountBarOption`ã`charts.priorityPieOption` |
| | | |
| | | å项常ç¨åæ®µï¼ |
| | | - `customerName` |
| | | - `priority`ï¼`high`/`medium`/`low`ï¼ |
| | | - `pendingAmount` |
| | | - `quoteConversionRate` |
| | | - `collectionStrategy` |
| | | - `quotationStrategy` |
| | | - `nextAction` |
| | | |
| | | ## 6. ææ ç»è®¡èè°ï¼`sales_dashboard`ï¼ |
| | | |
| | | å
³é®åæ®µï¼ |
| | | - `summary.contractAmountTotal` |
| | | - `summary.receivedAmountTotal` |
| | | - `summary.pendingAmountTotal` |
| | | - `summary.shipRate` |
| | | |
| | | å¾è¡¨å段ï¼å¯ç´æ¥ç» EChartsï¼ï¼ |
| | | - `charts.amountBarOption` |
| | | - `charts.shippingPieOption` |
| | | - `charts.customerTopBarOption` |
| | | - `charts.contractTrendLineOption` |
| | | |
| | | éå æ°æ®ï¼ |
| | | - `data.topCustomers` |
| | | - `data.contractTrend` |
| | | |
| | | ## 7. ä¼è¯å岿¥å£ |
| | | |
| | | ### 7.1 ä¼è¯å表 |
| | | |
| | | ```http |
| | | GET /sales-ai/history/sessions |
| | | ``` |
| | | |
| | | è¿å `AjaxResult.data` åæ®µï¼ |
| | | - `memoryId` |
| | | - `title` |
| | | - `lastMessage` |
| | | - `messageCount` |
| | | - `lastChatTime` |
| | | |
| | | ### 7.2 ä¼è¯æ¶æ¯ |
| | | |
| | | ```http |
| | | GET /sales-ai/history/messages/{memoryId} |
| | | ``` |
| | | |
| | | è¿å `AjaxResult.data` åæ®µï¼ |
| | | - `role`ï¼`user` / `assistant` / `system` / `tool` |
| | | - `content` |
| | | - `filePaths`ï¼å½åéå®å©ææªä½¿ç¨æä»¶åæï¼å¯å¿½ç¥ï¼ |
| | | |
| | | ### 7.3 å é¤ä¼è¯ |
| | | |
| | | ```http |
| | | DELETE /sales-ai/history/{memoryId} |
| | | ``` |
| | | |
| | | è¿åæ å `AjaxResult`ã |
| | | |
| | | ## 8. å端æ¥å
¥çº¦æ |
| | | |
| | | 1. æ°å¢å©æé
ç½®æ¶ï¼`assistantRegistry` å¿
须注å `sales`ï¼æä½ æ¹çº¦å® keyï¼ï¼å¹¶æå `apiBase = /sales-ai`ã |
| | | 2. ç»æåæ¸²æå¿
é¡»åºäº `type` ååï¼ä¸è¦ä»
é å
³é®è¯ã |
| | | 3. è天渲æéä¿çâææ¬å
åºâï¼é¿å
JSON è§£æå¤±è´¥æ¶é¡µé¢ç©ºç½ã |
| | | 4. ä¸å¡å±ç¤ºåæ®µå»ºè®®ä¸æåï¼ä¸ç´æ¥å±ç¤ºè±æå段 keyã |
| | | |
| | | ## 9. èè°éªæ¶æ¸
å |
| | | |
| | | 1. è½æ£å¸¸æµå¼æ¥æ¶ `/sales-ai/chat` ååºå¹¶æ¼æ¥ææ¬ã |
| | | 2. è½æ `type` æ£ç¡®æ¸²æ 9 ç±»ç»æåç»æã |
| | | 3. è½æ£ç¡®å±ç¤ºâå®¢æ·æµå¤±é£é©åæâåâ忬¾ä¸æ¥ä»·çç¥å»ºè®®â两个éç¹åºæ¯ã |
| | | 4. ä¼è¯å表ãä¼è¯æ¶æ¯ãå é¤ä¼è¯å
¨é¾è·¯å¯ç¨ã |
| | | 5. `memoryId` å¤ç¨åå¯åçåå²ï¼ä¸ä¼ä¸²ä¼è¯ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # é¦é¡µçäº§çæ¿å端èè°ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-20 |
| | | 模åï¼`/home`ï¼é¦é¡µï¼ |
| | | |
| | | ## 1. æ¥å£æ¸
å |
| | | |
| | | 1. `GET /home/productionOverview`ï¼ç产æ»è§ |
| | | 2. `GET /home/productionRealtimeBoard`ï¼çäº§å®æ¶çæ¿ |
| | | 3. `GET /home/productionOrderProgress`ï¼ç产订åè¿åº¦ |
| | | 4. `GET /home/todayProductionPlan`ï¼ä»æ¥ç产计å |
| | | |
| | | æææ¥å£ç»ä¸è¿å `AjaxResult`ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": {} |
| | | } |
| | | ``` |
| | | |
| | | ## 2. ç产æ»è§ |
| | | |
| | | ### 2.1 è¯·æ± |
| | | |
| | | ```http |
| | | GET /home/productionOverview |
| | | ``` |
| | | |
| | | ### 2.2 è¿å `data` |
| | | |
| | | ```json |
| | | { |
| | | "totalOutput": 1280.00, |
| | | "totalScrap": 25.00, |
| | | "yieldRate": 98.08 |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | - `totalOutput`ï¼ç´¯è®¡äº§åºï¼ä»¶ï¼åæ ¼æ°ï¼ |
| | | - `totalScrap`ï¼ç´¯è®¡æ¥åºï¼ä»¶ï¼ |
| | | - `yieldRate`ï¼è¯çï¼0-100ï¼å端å±ç¤ºæ¶å¯æ¼æ¥ `%`ï¼ |
| | | |
| | | ## 3. çäº§å®æ¶çæ¿ |
| | | |
| | | ### 3.1 è¯·æ± |
| | | |
| | | ```http |
| | | GET /home/productionRealtimeBoard |
| | | ``` |
| | | |
| | | ### 3.2 è¿å `data` |
| | | |
| | | ```json |
| | | { |
| | | "deviceOee": { |
| | | "value": 74.00, |
| | | "compareYesterday": 2.50 |
| | | }, |
| | | "orderAchievementRate": { |
| | | "value": 81.30, |
| | | "compareYesterday": -1.20 |
| | | }, |
| | | "defectRate": { |
| | | "value": 1.40, |
| | | "compareYesterday": 0.30 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | - `value`ï¼å½æ¥ææ å¼ï¼0-100ï¼ |
| | | - `compareYesterday`ï¼è¾æ¨æ¥ååå¼ï¼å¯æ£å¯è´ï¼åç«¯ææ£è´å³å®ç®å¤´æ¹ååé¢è²ï¼ |
| | | |
| | | ## 4. ç产订åè¿åº¦ |
| | | |
| | | ### 4.1 è¯·æ± |
| | | |
| | | ```http |
| | | GET /home/productionOrderProgress?tab=all&pageNum=1&pageSize=10 |
| | | ``` |
| | | |
| | | åæ°ï¼ |
| | | |
| | | - `tab`ï¼`all` / `inProgress` / `completed` / `paused` |
| | | - `pageNum`ï¼é¡µç ï¼é»è®¤ `1`ï¼ |
| | | - `pageSize`ï¼æ¯é¡µæ¡æ°ï¼é»è®¤ `10`ï¼æå¤§ `50`ï¼ |
| | | |
| | | ### 4.2 è¿å `data` |
| | | |
| | | ```json |
| | | { |
| | | "tab": "all", |
| | | "total": 24, |
| | | "pageNum": 1, |
| | | "pageSize": 10, |
| | | "inProgressCount": 6, |
| | | "completedCount": 12, |
| | | "pausedCount": 2, |
| | | "records": [ |
| | | { |
| | | "orderNo": "MO-20260518-001", |
| | | "productName": "æºè½æ§å¶å¨", |
| | | "plannedQuantity": 1000.00, |
| | | "completedQuantity": 860.00, |
| | | "completionRate": 86.00, |
| | | "dueDate": "2026-05-20", |
| | | "status": 2, |
| | | "statusLabel": "è¿è¡ä¸" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | - `completionRate`ï¼å®æçï¼0-100ï¼ |
| | | - `status`ï¼åç«¯ç¶æç ï¼`1`å¾
å¼å§ï¼`2`è¿è¡ä¸ï¼`3`已宿ï¼`4`å·²æåï¼ |
| | | - `statusLabel`ï¼ç¶æä¸æå±ç¤ºå¼ |
| | | |
| | | ## 5. 仿¥ç产计å |
| | | |
| | | ### 5.1 è¯·æ± |
| | | |
| | | ```http |
| | | GET /home/todayProductionPlan?limit=4 |
| | | ``` |
| | | |
| | | åæ°ï¼ |
| | | |
| | | - `limit`ï¼è¿åæ¡æ°ï¼é»è®¤ `4`ï¼æå¤§ `20`ï¼ |
| | | |
| | | ### 5.2 è¿å `data` |
| | | |
| | | ```json |
| | | { |
| | | "total": 9, |
| | | "records": [ |
| | | { |
| | | "orderNo": "MO-20260518-004", |
| | | "productName": "ç»æä»¶A", |
| | | "plannedQuantity": 1200.00, |
| | | "dueDate": "2026-05-15", |
| | | "status": 2, |
| | | "statusLabel": "è¿è¡ä¸" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | ## 6. å端å±ç¤ºçº¦å® |
| | | |
| | | - ç¾åæ¯å段ç»ä¸æ¯æ°å¼ï¼å¦ `74.00`ï¼ï¼å端èªè¡æ¼æ¥ `%`ã |
| | | - æææ°å¼ä¿ç两ä½å°æ°ã |
| | | - `dueDate` å¯è½ä¸º `null`ï¼å端éå
åºå±ç¤ºï¼å¦ `--`ï¼ã |
| | | - `compareYesterday` æ£è´é½å¯è½åºç°ï¼å»ºè®®æ `>0` ä¸åã`<0` ä¸éã`=0` æå¹³å¤çã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # é¦é¡µçäº§çæ¿æ§è½ä¼ååç«¯åæ´ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-20 |
| | | éç¨é¡µé¢ï¼é¦é¡µ |
| | | æ¶ååºåï¼ |
| | | |
| | | 1. ç产订åè¿åº¦ |
| | | 2. 仿¥ç产计å |
| | | |
| | | ## 1. æ¬æ¬¡ä¼åç®æ |
| | | |
| | | éå¯¹å¤§æ°æ®éåºæ¯ï¼è®¢åæ°éå¤ãç产åå²é¿ï¼ä¼åæ¥è¯¢æ§è½ï¼éä½é¦é¡µæ¥å£ååºæ¶é´åå
åå ç¨ã |
| | | |
| | | ## 2. æ¶åæ¥å£ |
| | | |
| | | 1. `GET /home/productionOrderProgress` |
| | | 2. `GET /home/todayProductionPlan` |
| | | |
| | | ## 3. å端æ¯å¦éè¦æ¹ä»£ç |
| | | |
| | | ç»è®ºï¼**æ å¼ºå¶æ¹å¨ï¼æ¥å£å
¥åä¸è¿åç»æä¿æå
¼å®¹**ã |
| | | ä½ ç°æé¡µé¢å¯ä»¥ç´æ¥èè°ï¼ä¸éè¦æ¹å段æ å°ã |
| | | |
| | | ## 4. æ¥å£è¯´æï¼ä¿æä¸åï¼ |
| | | |
| | | ### 4.1 ç产订åè¿åº¦ |
| | | |
| | | 请æ±ï¼ |
| | | |
| | | ```http |
| | | GET /home/productionOrderProgress?tab=all&pageNum=1&pageSize=10 |
| | | ``` |
| | | |
| | | åæ°ï¼ |
| | | |
| | | - `tab`ï¼`all` / `inProgress` / `completed` / `paused` |
| | | - `pageNum`ï¼é¡µç ï¼é»è®¤ `1` |
| | | - `pageSize`ï¼æ¯é¡µæ¡æ°ï¼é»è®¤ `10`ï¼æå¤§ `50` |
| | | |
| | | è¿å `data`ï¼ç»æä¸åï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "tab": "all", |
| | | "total": 1200, |
| | | "pageNum": 1, |
| | | "pageSize": 10, |
| | | "inProgressCount": 180, |
| | | "completedCount": 900, |
| | | "pausedCount": 20, |
| | | "records": [ |
| | | { |
| | | "orderNo": "MO-20260518-001", |
| | | "productName": "æºè½æ§å¶å¨", |
| | | "plannedQuantity": 1000.00, |
| | | "completedQuantity": 860.00, |
| | | "completionRate": 86.00, |
| | | "dueDate": "2026-05-20", |
| | | "status": 2, |
| | | "statusLabel": "è¿è¡ä¸" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | ### 4.2 仿¥ç产计å |
| | | |
| | | 请æ±ï¼ |
| | | |
| | | ```http |
| | | GET /home/todayProductionPlan?limit=4 |
| | | ``` |
| | | |
| | | åæ°ï¼ |
| | | |
| | | - `limit`ï¼è¿åæ¡æ°ï¼é»è®¤ `4`ï¼æå¤§ `20` |
| | | |
| | | è¿å `data`ï¼ç»æä¸åï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "total": 230, |
| | | "records": [ |
| | | { |
| | | "orderNo": "MO-20260518-004", |
| | | "productName": "ç»æä»¶A", |
| | | "plannedQuantity": 1200.00, |
| | | "dueDate": "2026-05-15", |
| | | "status": 2, |
| | | "statusLabel": "è¿è¡ä¸" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | ## 5. å端ä¼åç¹ï¼ä¾åç«¯ç¥æï¼ |
| | | |
| | | 1. 订åè¿åº¦ä¸ä»æ¥è®¡åæ¹ä¸ºè½»é SQLï¼ä»
æ¥è¯¢é¦é¡µå¿
éåæ®µã |
| | | 2. 廿äºé¦é¡µæ¥è¯¢è·¯å¾ä¸ä¸å¿
è¦ç大å
³èãå¾çå¡«å
å对象è£
é
ã |
| | | 3. ç¶æç»è®¡æ¹ä¸ºæ°æ®åºèå计æ°ï¼ä¸åéæ¡æå计ç®ã |
| | | 4. å页䏿¡æ°ä¸éä¿çï¼`pageSize <= 50`, `limit <= 20`ï¼ã |
| | | |
| | | ## 6. å端建议 |
| | | |
| | | 1. 忢 `tab` æ¶ä¿çç°æè°ç¨æ¹å¼å³å¯ã |
| | | 2. `dueDate` å¯è½ä¸ºç©ºï¼ç»§ç»æ `--` å
åºå±ç¤ºã |
| | | 3. ç¾åæ¯å段ä»ä¸ºæ°å¼ï¼å端继ç»è¿½å `%` å±ç¤ºã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # éè´æºè½ä½ä¼ååç«¯åæ´ææ¡£ |
| | | |
| | | ## 1. åæ´èæ¯ |
| | | |
| | | æ¬æ¬¡é对éè´æºè½ä½åäºå¯¹é½ä¼åï¼åèéå®/审æ¹/å¶é æºè½ä½ï¼ï¼ |
| | | |
| | | 1. æå `quickPrompts` å½ä¸ç¨³å®æ§ã |
| | | 2. å¢å¼ºç¸å¯¹æ¶é´è¯å«ï¼ä»å¤©/æ¨å¤©/æ¬å¨/ä¸å¨/æ¬æ/䏿/ä»å¹´/å»å¹´/è¿N天çï¼ã |
| | | 3. å¢å ä¸å¡æå¾æªè¯å«æ¶çç»æåå
åºååºï¼é¿å
ç¼é æ°æ®ã |
| | | 4. è¡¥å
å¾
仿¬¾æ¥è¯¢çæ±æ»å段ï¼ä¾¿äºåç«¯ç´æ¥æ¸²æç»è®¡å¡çã |
| | | |
| | | ## 2. æ¥å£å½±åæ¦è§ |
| | | |
| | | | æ¥å£ | æ¹æ³ | æ¯å¦æ¹è·¯å¾ | æ¯å¦æ¹å
¥å | æ¯å¦æ¹è¿åç»æ | |
| | | | --- | --- | --- | --- | --- | |
| | | | `/purchase-ai/chat` | POST(SSE) | å¦ | å¦ | æ¯ï¼æ°å¢å
åº JSON ç±»åï¼ | |
| | | | `/purchase-ai/analyze-files` | POST(SSE) | å¦ | å¦ | å¦ï¼ä»
å
é¨æç¤ºè¯å¢å¼ºï¼ | |
| | | |
| | | ## 3. æ°å¢å
åºååºï¼éç¹ï¼ |
| | | |
| | | å½ç¨æ·ææ¾å¨é®éè´ä¸å¡ï¼ä½æ¡ä»¶ä¸å
å䏿ªå½ä¸å¯æ§è¡æå¾æ¶ï¼`/purchase-ai/chat` ä¼ç´æ¥è¿åç»æå JSONï¼è䏿¯èªç±ææ¬ï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "success": false, |
| | | "type": "purchase_intent_not_recognized", |
| | | "description": "æªè¯å«å°å¯æ§è¡çéè´æ¥è¯¢æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é æ°æ®ï¼è¯·è¡¥å
æç¡®æ¶é´èå´ãä¾åºåãéè´ååå·æç©æååæ¥è¯¢ã", |
| | | "summary": {}, |
| | | "data": { |
| | | "quickPrompts": [ |
| | | "æ¬æéè´é颿åååçç©ææåªäºï¼", |
| | | "åªäºéè´è®¢åè¿æªå
¥åºï¼", |
| | | "æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼", |
| | | "帮æç»è®¡å¾
仿¬¾éè´åï¼", |
| | | "ååºæ¬æéè´éè´§æ
åµ" |
| | | ] |
| | | }, |
| | | "charts": {} |
| | | } |
| | | ``` |
| | | |
| | | å端å¤çå»ºè®®ï¼ |
| | | |
| | | 1. å½ `type === "purchase_intent_not_recognized"` æ¶ï¼å±ç¤º `description`ã |
| | | 2. 读å `data.quickPrompts` ä½ä¸ºå¿«æ·æé®æé®ï¼å¯ç´æ¥åå¡«è¾å
¥æ¡ï¼ã |
| | | |
| | | ## 4. å¾
仿¬¾è¿åæ°å¢æ±æ»å段 |
| | | |
| | | æ¥å£ç±»åï¼`type = "purchase_pending_payment_list"` |
| | | ä½ç½®ï¼`summary` |
| | | |
| | | æ°å¢åæ®µï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | | --- | --- | --- | |
| | | | pendingOrderCount | number | å¾
仿¬¾è®¢åæ° | |
| | | | totalContractAmount | number | å¾
仿¬¾è®¢åååæ»é¢ | |
| | | | totalPaidAmount | number | 已仿¬¾æ»é¢ | |
| | | | totalPendingAmount | number | å¾
仿¬¾æ»é¢ | |
| | | |
| | | 说æï¼åæå段ä»ä¿çï¼å
¼å®¹ï¼ï¼æ¬æ¬¡ä¸ºå¢éåæ®µï¼ä¸ç ´åç°ææ¸²æã |
| | | |
| | | ## 5. æ¶é´å£å¾ä¼å |
| | | |
| | | éè´æºè½ä½ç°å¨ç»ä¸æä¸å½æ¶åºå¨ææ¥ææ¢ç®ç¸å¯¹æ¶é´ï¼æ¯æï¼ |
| | | |
| | | - ä»å¤©ãæ¨å¤© |
| | | - æ¬å¨ãä¸å¨ |
| | | - æ¬æã䏿 |
| | | - ä»å¹´ãå»å¹´ |
| | | - è¿N天/å¨/æ/å¹´ãè¿åå¹´ãè¿å个æ |
| | | |
| | | å端æ éæ¹ä¼ åï¼ä½å±ç¤ºæ¶é´èå´æ¶è¯·ä»¥å端è¿å `summary.startDate/endDate/timeRange` 为åã |
| | | |
| | | ## 6. å端èè°æ£æ¥æ¸
å |
| | | |
| | | 1. `chat` æµå¼ç»ææ¼æ¥åï¼ä¼å
æ JSON è§£æã |
| | | 2. è¦çæ°ç±»å `purchase_intent_not_recognized` ç UI å¤çã |
| | | 3. å¾
仿¬¾é¡µé¢è¯»åå¹¶å±ç¤º `summary.totalPendingAmount` çæ°å¢å段ã |
| | | 4. éªè¯ä»¥ä¸å¿«æ·é®é¢å¯ç¨³å®è¿åç»æåç»æï¼ |
| | | - æ¬æéè´é颿åååçç©ææåªäºï¼ |
| | | - åªäºéè´è®¢åè¿æªå
¥åºï¼ |
| | | - æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼ |
| | | - 帮æç»è®¡å¾
仿¬¾éè´åï¼ |
| | | - ååºæ¬æéè´éè´§æ
åµ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # é¦é¡µ HomeController æ¥å£å级åç«¯åæ´ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-21 |
| | | éç¨æ¨¡åï¼é¦é¡µï¼`/home`ï¼ |
| | | |
| | | ## 1. åæ´æ¦è§ |
| | | |
| | | æ¬æ¬¡ä¸º **å
¼å®¹å¼å级**ï¼æ§è°ç¨æ¹å¼ä»å¯ç¨ã |
| | | éç¹æ¯ç»çäº§çæ¿æ¥å£å¢å æ´æç¡®ççéåæ°ï¼ä¾¿äºåç«¯ææ¥æåç¶ææ¥è¯¢ã |
| | | |
| | | æ¶åæ¥å£ï¼ |
| | | |
| | | 1. `GET /home/productionOrderProgress` |
| | | 2. `GET /home/todayProductionPlan` |
| | | |
| | | ## 2. åæ°åæ´ |
| | | |
| | | ### 2.1 ç产订åè¿åº¦ `GET /home/productionOrderProgress` |
| | | |
| | | æ§åæ°ï¼ä»å
¼å®¹ï¼ï¼ |
| | | |
| | | - `tab`ï¼`all` / `inProgress` / `completed` / `paused` |
| | | - `pageNum`ï¼é»è®¤ `1` |
| | | - `pageSize`ï¼é»è®¤ `10`ï¼æå¤§ `50` |
| | | |
| | | æ°å¢åæ°ï¼ |
| | | |
| | | - `status`ï¼å¯éï¼ï¼ç¶æçéï¼ä¼å
级é«äº `tab` |
| | | å¯éå¼ï¼`all` / `waiting` / `inProgress` / `completed` / `paused` / `1` / `2` / `3` / `4` |
| | | - `bizDate`ï¼å¯éï¼ï¼ä¸å¡æ¥æçéï¼æ ¼å¼ `yyyy-MM-dd`ï¼æè®¢åå建æ¶é´è¿æ»¤ï¼ |
| | | |
| | | åæ°ä¼å
çº§ï¼ |
| | | |
| | | 1. å¦æä¼ äº `status`ï¼å端ä¼å
æ `status` è§£æï¼ |
| | | 2. æªä¼ `status` æ¶ï¼æ²¿ç¨åæ `tab` è¡ä¸ºï¼ |
| | | 3. `status` æ `bizDate` æ ¼å¼é误æ¶è¿å失败信æ¯ã |
| | | |
| | | 请æ±ç¤ºä¾ï¼ |
| | | |
| | | ```http |
| | | GET /home/productionOrderProgress?status=completed&bizDate=2026-05-20&pageNum=1&pageSize=10 |
| | | ``` |
| | | |
| | | ### 2.2 仿¥ç产计å `GET /home/todayProductionPlan` |
| | | |
| | | æ§åæ°ï¼ä»å
¼å®¹ï¼ï¼ |
| | | |
| | | - `limit`ï¼é»è®¤ `4`ï¼æå¤§ `20` |
| | | |
| | | æ°å¢åæ°ï¼ |
| | | |
| | | - `planDate`ï¼å¯éï¼ï¼è®¡åæ¥æçéï¼æ ¼å¼ `yyyy-MM-dd`ï¼æ `plan_complete_time` è¿æ»¤ï¼ |
| | | |
| | | 请æ±ç¤ºä¾ï¼ |
| | | |
| | | ```http |
| | | GET /home/todayProductionPlan?limit=6&planDate=2026-05-21 |
| | | ``` |
| | | |
| | | ## 3. è¿åç»æåæ´ |
| | | |
| | | ### 3.1 `productionOrderProgress` è¿åæ°å¢å段 |
| | | |
| | | æ°å¢ï¼ |
| | | |
| | | - `status`ï¼æ ååç¶æåæ¾ï¼`all` / `waiting` / `inProgress` / `completed` / `paused`ï¼ |
| | | - `bizDate`ï¼æ¥æçéåæ¾ï¼æªä¼ æ¶ä¸º `null`ï¼ |
| | | - `waitingCount`ï¼å¾
å¼å§è®¢åæ°é |
| | | |
| | | å
¼å®¹ä¿çï¼ |
| | | |
| | | - `tab` åæ®µç»§ç»è¿åï¼è页颿 éæ¹å¨å¯ç»§ç»ä½¿ç¨ï¼ |
| | | |
| | | è¿å示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "tab": "completed", |
| | | "status": "completed", |
| | | "bizDate": "2026-05-20", |
| | | "total": 24, |
| | | "pageNum": 1, |
| | | "pageSize": 10, |
| | | "waitingCount": 3, |
| | | "inProgressCount": 6, |
| | | "completedCount": 12, |
| | | "pausedCount": 2, |
| | | "records": [] |
| | | } |
| | | ``` |
| | | |
| | | ### 3.2 `todayProductionPlan` è¿åæ°å¢å段 |
| | | |
| | | æ°å¢ï¼ |
| | | |
| | | - `planDate`ï¼æ¥æçéåæ¾ï¼æªä¼ æ¶ä¸º `null`ï¼ |
| | | |
| | | è¿å示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "planDate": "2026-05-21", |
| | | "total": 9, |
| | | "records": [] |
| | | } |
| | | ``` |
| | | |
| | | ## 4. å端æ¹é 建议 |
| | | |
| | | 1. æ°é¡µé¢å»ºè®®ä¼å
ä¼ `status`ï¼éæ¥æ¿ä»£ `tab`ã |
| | | 2. éè¦ææ¥æå¤ççæ¿æ¶ï¼ä½¿ç¨ `bizDate` / `planDate`ã |
| | | 3. è页é¢å¯ä¸æ¹ï¼ç»§ç»æ²¿ç¨ååæ°ä¹è½æ£å¸¸èè°ã |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # StockInRecord å表æºåå·å端èè°ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-22 |
| | | éç¨æ¥å£ï¼`GET /stockInRecord/listPage` |
| | | |
| | | ## 1. åæ´è¯´æ |
| | | |
| | | æ¬æ¬¡å¯¹å
¥åºç®¡çå表æ¥å£å¢å è¿ååæ®µ `sourceOrderNo`ï¼æºåå·ï¼ï¼ç¨äºåææåºæ¯å±ç¤ºéè´æ¥æºåå·ã |
| | | |
| | | çææ¡ä»¶ï¼ |
| | | |
| | | - å½è¯·æ±åæ° `topParentProductId = 278` æ¶ï¼å端è¿å `sourceOrderNo`ã |
| | | - å
¶ä» `topParentProductId` åºæ¯ä¸ï¼è¯¥å段è¿å `null`ï¼æä¸å±ç¤ºï¼ã |
| | | |
| | | ## 2. åæ®µå®ä¹ |
| | | |
| | | æ°å¢åæ®µï¼ |
| | | |
| | | - `sourceOrderNo`ï¼`string`ï¼æºåå·ï¼éè´ååå·ï¼ã |
| | | |
| | | ## 3. åå¼è§å |
| | | |
| | | ä»
å¨ `topParentProductId = 278` æ¶æâæ¥æºâ计ç®ï¼ |
| | | |
| | | 1. æ¥æº = `éè´-å
¥åº`ï¼`recordType = 7`ï¼ |
| | | - å
æ `recordId` æ¥éè´äº§å表 `sales_ledger_product`ï¼`type=2`ï¼ï¼ |
| | | - åéè¿ `sales_ledger_product.sales_ledger_id` æ¥éè´å°è´¦è¡¨ `purchase_ledger`ï¼ |
| | | - è¿å `purchase_ledger.purchase_contract_number` ä½ä¸º `sourceOrderNo`ã |
| | | - å
¼å®¹å
åºï¼è¥æªå½ä¸éè´äº§åé¾è·¯ï¼åæ `recordId` ç´æ¥æ¥ `purchase_ledger.id` ååå·ã |
| | | |
| | | 2. æ¥æº = `éè´-è´¨æ£-åæ ¼å
¥åº`ï¼`recordType = 10`ï¼ |
| | | - å
æ `recordId` æ¥è´¨æ£è¡¨ `quality_inspect`ï¼ |
| | | - åéè¿ `quality_inspect.purchase_ledger_id` æ¥éè´å°è´¦è¡¨ `purchase_ledger`ï¼ |
| | | - è¿å `purchase_ledger.purchase_contract_number` ä½ä¸º `sourceOrderNo`ã |
| | | |
| | | ## 4. è¿åç¤ºä¾ |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": { |
| | | "records": [ |
| | | { |
| | | "id": 1024, |
| | | "recordType": "7", |
| | | "productName": "éæ", |
| | | "model": "T2-30x3", |
| | | "sourceOrderNo": "CG-2026-00128" |
| | | }, |
| | | { |
| | | "id": 1025, |
| | | "recordType": "10", |
| | | "productName": "éæ", |
| | | "model": "T2-30x3", |
| | | "sourceOrderNo": "CG-2026-00131" |
| | | } |
| | | ], |
| | | "total": 2 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## 5. å端èè°å»ºè®® |
| | | |
| | | 1. åè¡¨åæ°å¢âæºåå·âï¼è¯»ååæ®µ `sourceOrderNo`ã |
| | | 2. 建议ä»
å¨ `topParentProductId = 278` ç页é¢/ç鿡件ä¸å±ç¤ºè¯¥åã |
| | | 3. å½ `sourceOrderNo` 为空æ¶å±ç¤º `--`ï¼é¿å
空ç½ã |
| | | |
| | | ## 6. å彿¸
å |
| | | |
| | | 1. `topParentProductId=278` + `recordType=7`ï¼åºè¿åéè´ååå·ã |
| | | 2. `topParentProductId=278` + `recordType=10`ï¼åºè¿åéè´ååå·ï¼ç»è´¨æ£é¾è·¯ï¼ã |
| | | 3. `topParentProductId!=278`ï¼`sourceOrderNo` åºä¸º `null` æå端ä¸å±ç¤ºã |
| | | 4. åæå段ï¼`productName/model/unit/createBy` çï¼ä¸åå½±åã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # StockInRecord å表æºåå·å端èè°ææ¡£ï¼`topParentProductId=276`è¡¥å
ï¼ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-22 |
| | | éç¨æ¥å£ï¼`GET /stockInRecord/listPage` |
| | | |
| | | ## 1. åæ´è¯´æ |
| | | |
| | | å¨å·²æ `sourceOrderNo` åºç¡ä¸ï¼æ°å¢ `topParentProductId = 276` çæºåå·æº¯æºé»è¾ï¼ |
| | | |
| | | - æ ¹æ®âæ¥æºï¼recordTypeï¼+ recordIdâæº¯æºï¼ |
| | | - ä¼å
è¿åéå®åå·ï¼éå®ååå·ï¼ï¼ |
| | | - è¥éå®åå·ä¸ºç©ºï¼ååéè¿åç产订åå·ï¼ |
| | | - ä¸èèèªå®ä¹å
¥åºï¼`recordType=0/9`ï¼ã |
| | | |
| | | ## 2. è¿ååæ®µ |
| | | |
| | | åæ®µæ æ°å¢ï¼ç»§ç»ä½¿ç¨ï¼ |
| | | |
| | | - `sourceOrderNo`ï¼`string`ï¼æºåå·ã |
| | | |
| | | ## 3. 276 åºæ¯åå¼è§å |
| | | |
| | | 请æ±åæ°æ»¡è¶³ `topParentProductId = 276` æ¶ï¼ |
| | | |
| | | 1. `recordType = 14/15`ï¼éå®éè´§-åæ ¼/ä¸åæ ¼å
¥åºï¼ |
| | | - `stock_in_record.record_id -> return_sale_product.id -> return_management.shipping_id -> shipping_info.sales_ledger_id -> sales_ledger.sales_contract_no` |
| | | - è¿åéå®ååå·ã |
| | | |
| | | 2. `recordType = 2/5`ï¼ç产æ¥å·¥-å
¥åº/æ¥åºï¼ |
| | | - `stock_in_record.record_id -> production_product_main.id -> production_operation_task.production_order_id -> production_order` |
| | | - å
å该ç产订åå
³èéå®ååå·ï¼ç±ç产计åå
³èéå®å°è´¦èåï¼ï¼ |
| | | - éå®ååå·ä¸ºç©ºæ¶ï¼è¿å `production_order.nps_no`ã |
| | | |
| | | 3. `recordType = 6`ï¼è´¨æ£-åæ ¼å
¥åºï¼ |
| | | - `stock_in_record.record_id -> quality_inspect.id -> quality_inspect.product_main_id -> production_product_main -> production_operation_task -> production_order` |
| | | - å
åéå®ååå·ï¼ç©ºååé `production_order.nps_no`ã |
| | | |
| | | 4. `recordType = 4/11`ï¼ä¸åæ ¼å¤ç-æ¥åº/è®©æ¥æ¾è¡ï¼ |
| | | - `stock_in_record.record_id -> quality_unqualified.id -> quality_unqualified.inspect_id -> quality_inspect -> production_product_main -> production_operation_task -> production_order` |
| | | - å
åéå®ååå·ï¼ç©ºååé `production_order.nps_no`ã |
| | | |
| | | 5. `recordType = 20/22`ï¼é¢æéæ/ç产éæ-åæ ¼å
¥åºï¼ |
| | | - `stock_in_record.record_id -> production_order_pick.id -> production_order` |
| | | - å
åéå®ååå·ï¼ç©ºååé `production_order.nps_no`ã |
| | | |
| | | 6. `recordType = 0/9`ï¼èªå®ä¹å
¥åºï¼ |
| | | - ä¸å䏿º¯æºï¼`sourceOrderNo = null`ã |
| | | |
| | | ## 4. å
¶ä»åºæ¯è¯´æ |
| | | |
| | | - `topParentProductId = 278` çéè´é¾è·¯æºåå·é»è¾ä¿æä¸åã |
| | | - å
¶ä» `topParentProductId` ä¸è§¦åæ¬æ¬¡ 276 è§åï¼`sourceOrderNo` 为空ã |
| | | |
| | | ## 5. å端èè°å»ºè®® |
| | | |
| | | 1. å¨ `topParentProductId=276` çåè¡¨åºæ¯å±ç¤ºâæºåå·âåï¼è¯»å `sourceOrderNo`ã |
| | | 2. 建议空å¼ç»ä¸å±ç¤º `--`ã |
| | | 3. ä¸éè¦æ°å¢è¯·æ±åæ°ï¼æ²¿ç¨ç°æ `/stockInRecord/listPage`ã |
| | | |
| | | ## 6. å彿¸
å |
| | | |
| | | 1. `topParentProductId=276` + `recordType=14/15`ï¼åºè¿åéå®ååå·ã |
| | | 2. `topParentProductId=276` + `recordType=2/5/6/4/11/20/22`ï¼ä¼å
éå®ååå·ï¼ç¼ºå¤±æ¶è¿åç产订åå·ã |
| | | 3. `topParentProductId=276` + `recordType=0/9`ï¼`sourceOrderNo` 为空ã |
| | | 4. `topParentProductId=278`ï¼ä»æéè´é¾è·¯è¿åéè´ååå·ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # è´¢å¡å©ææé®ä¼ååç«¯åæ´ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-22 |
| | | éç¨æ¨¡åï¼è´¢å¡æºè½å©æï¼`/financial-ai`ï¼ |
| | | |
| | | ## 1. èæ¯ |
| | | |
| | | å½åé¦é¡µè´¢å¡å©æå¿«æ·æé®ä¸ºï¼ |
| | | |
| | | 1. `çææ¬å¨ç»è¥å¨æ¥` |
| | | 2. `为ä»ä¹å©æ¶¦ä¸é` |
| | | 3. `åªä¸ªå®¢æ·æèµé±` |
| | | |
| | | é®é¢ç¹ï¼ |
| | | |
| | | - 第 3 æ¡é®æ³å¨é¨ååºæ¯ä¸æå¾å½ä¸ä¸ç¨³å®ï¼å®¹æèµ°æ®éææ¬åçï¼å¯¼è´å¾è¡¨é¾æ¥ä»¥åå§ Markdown ææ¬å±ç¤ºï¼å¦ ``ï¼ã |
| | | - å¿«æ·æé®ç¼ºå°æ¶é´èå´ååæç®æ ï¼ç»æç¨³å®æ§ä¸å¯è§£éæ§è¾å¼±ã |
| | | |
| | | ## 2. åç«¯å¿«æ·æé®ææ¡ä¼åï¼å¿
æ¹ï¼ |
| | | |
| | | 建议å°é»è®¤ä¸æ¡å¿«æ·æé®è°æ´ä¸ºï¼ |
| | | |
| | | 1. `çææ¬å¨ç»è¥å¨æ¥ï¼å©æ¶¦ä¸ç°éæµï¼` |
| | | 2. `åææ¬æå©æ¶¦ä¸éåå ` |
| | | 3. `è¿30天åªä¸ªå®¢æ·å©æ¶¦è´¡ç®æé«` |
| | | |
| | | 说æï¼ |
| | | |
| | | - 䏿¡é®æ³å带æ¶é´èå´æåæç®æ ï¼å端å½ä¸æ´ç¨³å®ã |
| | | - 第 3 æ¡ä¸âæèµé±å®¢æ·âè¯ä¹ä¸è´ï¼ä½â婿¶¦è´¡ç®æé«âæ´æç¡®ï¼éåç´æ¥é©±å¨å©æ¶¦åæç»æé¡µã |
| | | |
| | | ## 3. ä¸å端è½åæ å° |
| | | |
| | | | å¿«æ·æé® | 颿å½ä¸è½å | 颿 `type` | |
| | | | --- | --- | --- | |
| | | | çææ¬å¨ç»è¥å¨æ¥ï¼å©æ¶¦ä¸ç°éæµï¼ | ç»è¥æ¥åçæ | `financial_operation_report` | |
| | | | åææ¬æå©æ¶¦ä¸éåå | 订å婿¶¦åæ | `financial_order_profit_analysis` | |
| | | | è¿30天åªä¸ªå®¢æ·å©æ¶¦è´¡ç®æé« | 订å婿¶¦åæ | `financial_order_profit_analysis` | |
| | | |
| | | åç«¯å·²åæ¥å¢å¼ºâæèµé±å®¢æ·/客æ·å©æ¶¦æé«/婿¶¦è´¡ç®æé«âçåä¹é®æ³è¯å«ï¼å端æä»¥ä¸ææ¡æ¹é åå¯ç´æ¥èè°ã |
| | | |
| | | ## 4. è天å
容渲æå
åºï¼å»ºè®®æ¹ï¼ |
| | | |
| | | é对è天è¿åææ¬ä¸åºç°çå¾è¡¨ Markdown 龿¥ï¼`https://local/generate_chart?options=...`ï¼ï¼å»ºè®®å端å¢å å
åºå¤çï¼ |
| | | |
| | | 1. è¯å« Markdown å¾çè¯æ³ä¸ç `local/generate_chart` 龿¥ã |
| | | 2. è§£æ `options` åæ°å¹¶è½¬æ¢ä¸º ECharts `option` 忏²æå¾è¡¨ç»ä»¶ã |
| | | 3. è§£æå¤±è´¥æ¶ä¸å±ç¤ºåå§é¿é¾æ¥ææ¬ï¼å±ç¤ºç»ä¸ç©ºææç¤ºã |
| | | |
| | | ## 5. èè°å彿¸
å |
| | | |
| | | 1. ç¹å»å¿«æ·æé® `çææ¬å¨ç»è¥å¨æ¥ï¼å©æ¶¦ä¸ç°éæµï¼` |
| | | - æ ¡éªè¿å `type=financial_operation_report`ï¼å¹¶æ£å¸¸æ¸²ææè¦/建议/å¾è¡¨ã |
| | | 2. ç¹å»å¿«æ·æé® `åææ¬æå©æ¶¦ä¸éåå ` |
| | | - æ ¡éªè¿å `type=financial_order_profit_analysis`ï¼å¹¶å±ç¤ºäºæè®¢åä¸å®¢æ·å©æ¶¦æè¡ã |
| | | 3. ç¹å»å¿«æ·æé® `è¿30天åªä¸ªå®¢æ·å©æ¶¦è´¡ç®æé«` |
| | | - æ ¡éªè¿å `type=financial_order_profit_analysis`ï¼`summary.topCustomerByProfit` æå¼ã |
| | | 4. æå·¥è¾å
¥ `åªä¸ªå®¢æ·æèµé±`ã`åªä¸ªå®¢æ·å©æ¶¦æé«` |
| | | - æ ¡éªä»å½ä¸ `financial_order_profit_analysis`ï¼ä¸ååºç°åå§å¾è¡¨ Markdown 龿¥ç´åºã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # è´¢å¡æ¨¡åå级å AI 模ååç«¯åæ´èè°ææ¡£ï¼éè´/éå®/ç产/å¾
åï¼ |
| | | |
| | | æ´æ°æ¥æï¼2026-05-22 |
| | | éç¨èå´ï¼`/sales-ai`ã`/purchase-ai`ã`/manufacturing-ai`ã`/xiaozhi`ï¼å®¡æ¹å¾
åï¼ |
| | | |
| | | ## 1. åæ´æ»è§ |
| | | |
| | | | 模å | 坹夿¥å£ | æ¯å¦éè¦å端æ¹é | ç»è®º | |
| | | | --- | --- | --- | --- | |
| | | | éå® AI | `POST /sales-ai/chat` | æ¯ | è´¢å¡å£å¾åæ¢å°æ°æ¶æ¬¾æ¨¡åï¼é¨å `type` çåæ®µè¯ä¹åå | |
| | | | éè´ AI | `POST /purchase-ai/chat` | æ¯ | 仿¬¾/å票/å¾
仿¬¾è®¡ç®åæ¢å°æ°è´¢å¡é¾è·¯ï¼ç»è®¡å¼ä»å 使¹ä¸ºçå®å¼ | |
| | | | ç产 AI | `POST /manufacturing-ai/chat` | å¦ | å·²æ ¸æ¥ï¼æ æ§è´¢å¡é»è¾ä¾èµï¼æ åæ®µåæ´ | |
| | | | å¾
å AI | `POST /xiaozhi/chat` | å¦ | å·²æ ¸æ¥ï¼æ æ§è´¢å¡é»è¾ä¾èµï¼æ åæ®µåæ´ | |
| | | |
| | | ## 2. éå® AI åæ´ï¼`/sales-ai/chat`ï¼ |
| | | |
| | | ### 2.1 `type = sales_return_list`ï¼éå®é款/忬¾è®°å½ï¼ |
| | | |
| | | å½åè¿åæ°æ®æ¥æºç»ä¸ä¸ºæ°è´¢å¡è¡¨ `account_sales_collection`ï¼ä¸åèµ°æ§æ¶æ¬¾éè´§é»è¾ã |
| | | |
| | | `data.items[]` å
³é®åæ®µï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | | --- | --- | --- | |
| | | | id | number | æ¶æ¬¾è®°å½ID | |
| | | | refundId | string | æ å° `collectionNumber`ï¼å端å¯ç»§ç»ä½ä¸ºâ鿬¾/忬¾åå·âå±ç¤º | |
| | | | collectionNumber | string | æ¶æ¬¾åå· | |
| | | | paymentMethod | string | æ¶æ¬¾æ¹å¼ | |
| | | | actualAmount | number | æ¶æ¬¾éé¢ï¼ä¸ `collectionAmount` åå¼ï¼ | |
| | | | collectionAmount | number | æ¶æ¬¾éé¢ï¼æ¨è主å±ç¤ºåæ®µï¼ | |
| | | | customerId | number | 客æ·ID | |
| | | | remark | string | 夿³¨ | |
| | | | createTime | string | æ¶æ¬¾æ¥æï¼yyyy-MM-ddï¼ | |
| | | |
| | | `summary` å¢éå
³æ³¨ï¼ |
| | | - `returnAmount`ï¼æ¶é´èå´å
é颿±æ»ï¼æ `collectionAmount` ç»è®¡ï¼ |
| | | |
| | | ### 2.2 `type = sales_customer_interaction_list`ï¼å®¢æ·å¾æ¥ï¼ |
| | | |
| | | å½åè¿ååºäºæ°é¾è·¯ï¼ |
| | | `account_sales_collection.stock_out_record_ids -> stock_out_record(record_type=13) -> shipping_info -> sales_ledger` |
| | | |
| | | è¿å约å®ï¼ |
| | | - æ æ°æ®æ¶ï¼`description = "no_customer_interactions"` |
| | | - ææ°æ®æ¶ï¼`description = "ok"` |
| | | |
| | | `summary` å
³é®åæ®µï¼ |
| | | - `totalReceiptAmount` |
| | | - `customerCount` |
| | | |
| | | `data.items[]` å
³é®åæ®µï¼ |
| | | - `salesLedgerId` |
| | | - `salesContractNo` |
| | | - `customerName` |
| | | - `projectName` |
| | | - `receiptPaymentDate` |
| | | - `receiptPaymentAmount` |
| | | - `receiptPaymentType` |
| | | - `collectionNumber` |
| | | - `registrant` |
| | | - `remark` |
| | | |
| | | ### 2.3 `type = sales_ledger_list`ï¼éå®å°è´¦ï¼ |
| | | |
| | | åæ®µç»æä¸åï¼ä½éé¢å£å¾å·²åæ¢ï¼ |
| | | - `receivedAmount` ç±æ°æ¶æ¬¾æ¨¡åæ±æ»å¾å°ï¼ |
| | | - `pendingAmount = max(0, invoicedAmount - receivedAmount)`ï¼ |
| | | - è¥æ¶æ¬¾è®°å½æªæ¾å¼å
³èå°è´¦ï¼åæå®¢æ·ç»´åº¦å
åºå½éã |
| | | |
| | | å端æ¹é å»ºè®®ï¼ |
| | | - 䏿¹å段åï¼ |
| | | - éç¹åå½âå·²æ¶éé¢/å¾
忬¾éé¢âæ¯å¦ä¸è´¢å¡å°è´¦ä¸è´ã |
| | | |
| | | ## 3. éè´ AI åæ´ï¼`/purchase-ai/chat`ï¼ |
| | | |
| | | ### 3.1 `type = purchase_stats`ï¼éè´ç»è®¡ï¼ |
| | | |
| | | 以ä¸å段已ä»å ä½å¼æ¹ä¸ºçå®ç»è®¡å¼ï¼ |
| | | - `summary.paymentCount` |
| | | - `summary.invoiceCount` |
| | | - `summary.paymentAmount` |
| | | - `summary.invoiceAmount` |
| | | |
| | | å票éé¢å£å¾ï¼ |
| | | - ä¼å
`taxInclusivePrice` |
| | | - è¥ä¸ºç©º/0ï¼åä½¿ç¨ `taxExclusivelPrice + taxPrice` |
| | | |
| | | ### 3.2 `type = purchase_pending_payment_list`ï¼å¾
仿¬¾éè´åï¼ |
| | | |
| | | æ ¸å¿è®¡ç®å·²åæ¢å°æ°è´¢å¡é¾è·¯ï¼ |
| | | |
| | | `account_purchase_payment -> account_payment_application -> stock_in_record -> (purchase_ledger / quality_inspect) -> purchase_ledger_id` |
| | | |
| | | æ å°è§åï¼ |
| | | 1. `stock_in_record.record_type = 7`ï¼`record_id` ç´æ¥è§ä¸º `purchase_ledger_id` |
| | | 2. `stock_in_record.record_type = 10`ï¼éè¿ `quality_inspect.id = record_id` å `quality_inspect.purchase_ledger_id` |
| | | |
| | | éé¢å段å£å¾ï¼ |
| | | - `paidAmount`ï¼æ°é¾è·¯ç´¯è®¡å·²ä»æ¬¾éé¢ |
| | | - `pendingAmount = contractAmount - paidAmount`ï¼<=0 çè®°å½ä¸è¿åï¼ |
| | | |
| | | `summary` å
³é®å段ï¼å为çå®å¼ï¼ï¼ |
| | | - `pendingOrderCount` |
| | | - `totalContractAmount` |
| | | - `totalPaidAmount` |
| | | - `totalPendingAmount` |
| | | |
| | | ### 3.3 æ°æ®æ¸
æ´ä¿®å¤ |
| | | |
| | | å·²ä¿®å¤ `record_type` å¸¦ç©ºæ ¼å¯¼è´çæ å°ä¸¢å¤±é®é¢ï¼å端ç»ä¸ `trim()` åå夿 `7/10`ï¼ã |
| | | |
| | | ## 4. ç产 AI / å¾
å AI æ ¸æ¥ç»è®º |
| | | |
| | | å·²æ ¸æ¥ä»¥ä¸æ¨¡å代ç ï¼æªåç°æ§è´¢å¡é»è¾è¦åç¹ï¼ |
| | | - `ManufacturingAgentTools`ï¼çäº§ï¼ |
| | | - `ApproveTodoTools`ï¼å¾
å审æ¹ï¼ |
| | | |
| | | ç»è®ºï¼ |
| | | - å¯¹å¤ `type` ä¸åæ®µç»ææ åæ´ï¼ |
| | | - å端æ éåå
¼å®¹æ¹é ï¼ä»
éå䏿¬¡åå½éªè¯ã |
| | | |
| | | ## 5. å端èè°è¦ç¹ |
| | | |
| | | 1. `/sales-ai/chat`ã`/purchase-ai/chat` ç»§ç»æ SSE ææ¬æµæ¼æ¥åå JSON è§£æã |
| | | 2. æ `type` è·¯ç±æ¸²æï¼ä¸è¦ä»
ä¾èµ `description` ææ¡ã |
| | | 3. `sales_customer_interaction_list` éå
¼å®¹ `description` æä¸¾ï¼`ok` / `no_customer_interactions`ã |
| | | 4. `sales_return_list` éé¢å±ç¤ºç»ä¸ç¨ `collectionAmount`ï¼`actualAmount` ä¿çå
¼å®¹ï¼ã |
| | | 5. `purchase_pending_payment_list` çæ±æ»å¡çè¯·ç´æ¥è¯»å `summary.totalPendingAmount` çåæ®µï¼ä¸ååç«¯äºæ¬¡ä¼°ç®ã |
| | | |
| | | ## 6. å彿¸
åï¼å»ºè®®ï¼ |
| | | |
| | | ### éå® |
| | | 1. æé®ï¼âè¿30天åªä¸ªè®¢å忬¾æå°â |
| | | - æ ¡éª `sales_ledger_list` ç `receivedAmount/pendingAmount`ã |
| | | 2. æé®ï¼âæ¥è¯¢æ¬æéå®é款â |
| | | - æ ¡éª `sales_return_list` ç `collectionNumber/collectionAmount/returnAmount`ã |
| | | 3. æé®ï¼âæ¥è¯¢æ¬æå®¢æ·å¾æ¥â |
| | | - æ ¡éª `sales_customer_interaction_list` ç `totalReceiptAmount/customerCount`ã |
| | | |
| | | ### éè´ |
| | | 1. æé®ï¼âç»è®¡æ¬æéè´æ°æ®â |
| | | - æ ¡éª `purchase_stats` ç `paymentCount/invoiceCount/paymentAmount/invoiceAmount` éåºå®0ã |
| | | 2. æé®ï¼âååºå¾
仿¬¾éè´åâ |
| | | - æ ¡éª `purchase_pending_payment_list` ç `paidAmount/pendingAmount` ä¸è´¢å¡å®é
ä¸è´ã |
| | | |
| | | ### ç产/å¾
å |
| | | 1. ç产æé®ï¼âæ¥è¯¢æ¬å¨è®¾å¤ç»´ä¿®è®°å½â |
| | | 2. å¾
åæé®ï¼âæ¥è¯¢æçå¾
审æ¹å表â |
| | | - æ ¡éªè¿åç»æä¸å级åä¸è´ï¼æ åæ®µç ´åï¼ã |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # éè´å
¥åºç¶æå端èè°ææ¡£ |
| | | æ´æ°æ¶é´ï¼2026-05-22 |
| | | éç¨çæ¬ï¼æ¬æ¬¡åç«¯åæ´å |
| | | |
| | | ## 1. åæ´èå´ |
| | | |
| | | 1. `GET /purchaseLedger/listPage` |
| | | - æ°å¢æ¥è¯¢æ¡ä»¶ï¼`stockInStatus`ï¼å
¥åºç¶æï¼ |
| | | - æ°å¢è¿ååæ®µï¼`stockInStatus`ï¼å
¥åºç¶æï¼ |
| | | 2. `GET /salesLedgerProduct/list` |
| | | - æ°å¢è¿ååæ®µï¼`stockInApprovalStatus`ï¼æ¯ä¸ªäº§åçå
¥åºå®¡æ ¸ç¶æï¼ |
| | | |
| | | --- |
| | | |
| | | ## 2. å
¥åºç¶ææä¸¾ï¼ä¸¤æ¥å£ä¸è´ï¼ |
| | | |
| | | - `å¾
å
¥åº` |
| | | - `å
¥åºä¸` |
| | | - `å®å
¨å
¥åº` |
| | | |
| | | 说æï¼å端çéå¼è¯·ç´æ¥ä½¿ç¨ä»¥ä¸ä¸ææä¸¾å¼ã |
| | | |
| | | --- |
| | | |
| | | ## 3. æ¥å£ä¸ï¼`GET /purchaseLedger/listPage` |
| | | |
| | | ### 3.1 æ°å¢è¯·æ±åæ° |
| | | |
| | | - `stockInStatus`ï¼`string`ï¼å¯é |
| | | å¯ä¼ å¼ï¼`å¾
å
¥åº` / `å
¥åºä¸` / `å®å
¨å
¥åº` |
| | | |
| | | ### 3.2 æ°å¢è¿ååæ®µ |
| | | |
| | | - `stockInStatus`ï¼`string`ï¼éè´å°è´¦ç»´åº¦å
¥åºç¶æ |
| | | |
| | | ### 3.3 ç¶æè®¡ç®è§åï¼éè´å°è´¦ç»´åº¦ï¼ |
| | | |
| | | 以该éè´å°è´¦ä¸ `sales_ledger_product.type = 2` çéè´äº§å为计ç®èå´ï¼ |
| | | |
| | | 1. å
¨é¨äº§åé½è¾¾å°âå®å
¨å
¥åºâ => å°è´¦ç¶æ `å®å
¨å
¥åº` |
| | | 2. è³å°æä¸ä¸ªäº§ååå¨âå®¡æ ¸éè¿å
¥åºâï¼ä½æªå
¨é¨å®å
¨å
¥åº => å°è´¦ç¶æ `å
¥åºä¸` |
| | | 3. 没æä»»ä½äº§ååå¨âå®¡æ ¸éè¿å
¥åºâ => å°è´¦ç¶æ `å¾
å
¥åº` |
| | | |
| | | âå®¡æ ¸éè¿å
¥åºâç»è®¡å£å¾ï¼`stock_in_record.approval_status = 1`ï¼å¹¶æä»¥ä¸æ¥æºæº¯æºï¼ |
| | | - `record_type = 7`ï¼éè´-å
¥åºï¼ï¼æéè´å°è´¦+产åå
³èç»è®¡ |
| | | - `record_type = 10`ï¼éè´-è´¨æ£-åæ ¼å
¥åºï¼ï¼éè¿ `quality_inspect` åæº¯å°éè´å°è´¦+产åç»è®¡ |
| | | |
| | | ### 3.4 è¿å示ä¾ï¼èéï¼ |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": { |
| | | "records": [ |
| | | { |
| | | "id": 1201, |
| | | "purchaseContractNumber": "CG20260522001", |
| | | "supplierName": "XXä¾åºå", |
| | | "stockInStatus": "å
¥åºä¸" |
| | | } |
| | | ], |
| | | "total": 1 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 4. æ¥å£äºï¼`GET /salesLedgerProduct/list` |
| | | |
| | | ### 4.1 æ°å¢è¿ååæ®µ |
| | | |
| | | - `stockInApprovalStatus`ï¼`string`ï¼å½å产åè¡çå
¥åºå®¡æ ¸ç¶æ |
| | | |
| | | ### 4.2 ç¶æè®¡ç®è§åï¼äº§åç»´åº¦ï¼ |
| | | |
| | | ä»
å½äº§å `type = 2`ï¼éè´äº§åï¼æ¶è®¡ç®å¹¶è¿åï¼ |
| | | |
| | | 1. å®¡æ ¸éè¿å
¥åºæ°é `<= 0` => `å¾
å
¥åº` |
| | | 2. å®¡æ ¸éè¿å
¥åºæ°é `>= 产åéè´æ°é` => `å®å
¨å
¥åº` |
| | | 3. å
¶ä»æ
åµ => `å
¥åºä¸` |
| | | |
| | | å
¶ä¸âå®¡æ ¸éè¿å
¥åºæ°éâç»è®¡åæ ·åºäºï¼ |
| | | - `stock_in_record.approval_status = 1` |
| | | - æ¥æº `record_type = 7 / 10` çæº¯æºå
³èé»è¾ |
| | | |
| | | `type != 2` ç产åï¼è¯¥å段è¿å `null`ã |
| | | |
| | | ### 4.3 è¿å示ä¾ï¼èéï¼ |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": [ |
| | | { |
| | | "id": 5566, |
| | | "type": 2, |
| | | "productCategory": "éæ", |
| | | "specificationModel": "T2-30x3", |
| | | "quantity": 100, |
| | | "stockInApprovalStatus": "å¾
å
¥åº" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 5. å端æ¹é 建议 |
| | | |
| | | 1. éè´å°è´¦å表æ°å¢âå
¥åºç¶æâçé项ï¼å¼åºå®ï¼`å¾
å
¥åº/å
¥åºä¸/å®å
¨å
¥åº`ã |
| | | 2. éè´å°è´¦å表æ°å¢âå
¥åºç¶æâåï¼å±ç¤º `stockInStatus`ã |
| | | 3. éè´äº§åå表æ°å¢âå
¥åºå®¡æ ¸ç¶æâåï¼å±ç¤º `stockInApprovalStatus`ã |
| | | 4. 空å¼ï¼å¦ `type != 2`ï¼å»ºè®®å±ç¤ºä¸º `--`ã |
| | | |
| | | --- |
| | | |
| | | ## 6. èè°æ£æ¥æ¸
å |
| | | |
| | | 1. `/purchaseLedger/listPage` ä¸ä¼ `stockInStatus`ï¼åºæ£å¸¸è¿åå
¨é¨æ°æ®ï¼å¹¶å¸¦ `stockInStatus`ã |
| | | 2. `/purchaseLedger/listPage?stockInStatus=å¾
å
¥åº`ï¼ä»
è¿åå¾
å
¥åºå°è´¦ã |
| | | 3. `/purchaseLedger/listPage?stockInStatus=å
¥åºä¸`ï¼ä»
è¿åå
¥åºä¸å°è´¦ã |
| | | 4. `/purchaseLedger/listPage?stockInStatus=å®å
¨å
¥åº`ï¼ä»
è¿åå®å
¨å
¥åºå°è´¦ã |
| | | 5. `/salesLedgerProduct/list` å¨éè´äº§åï¼`type=2`ï¼ä¸è¿å `stockInApprovalStatus`ã |
| | | 6. `/salesLedgerProduct/list` å¨ééè´äº§åï¼`type!=2`ï¼ä¸ `stockInApprovalStatus` 为 `null`ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # é¦é¡µè´¢å¡æ¥å£å级åç«¯åæ´ææ¡£ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-22 |
| | | éç¨æ¨¡åï¼é¦é¡µï¼`/home`ï¼ |
| | | |
| | | ## 1. åæ´æ¦è§ |
| | | |
| | | æ¬æ¬¡ä¸º **å
¼å®¹å¼å级**ï¼æ¥å£ URLã请æ±åæ°ãè¿ååæ®µä¿æä¸åã |
| | | 主è¦åæ´æ¯é¦é¡µè´¢å¡æ°æ®ä»å ä½/åæåé»è¾ï¼åæ¢ä¸ºæè´¢å¡ç宿°æ®å£å¾è®¡ç®ã |
| | | |
| | | æ¶åæ¥å£ï¼ |
| | | |
| | | 1. `GET /home/statisticsReceivablePayable` |
| | | 2. `GET /home/monthlyIncome` |
| | | 3. `GET /home/monthlyExpenditure` |
| | | |
| | | ## 2. åæ°è¯´æï¼æ æ°å¢ï¼ |
| | | |
| | | ### 2.1 `GET /home/statisticsReceivablePayable` |
| | | |
| | | - `type`ï¼`1` æ¬å¨ï¼`2` æ¬æï¼`3` æ¬å£åº¦ï¼é»è®¤ `1`ï¼ |
| | | |
| | | ### 2.2 `GET /home/monthlyIncome` |
| | | |
| | | - æ åæ° |
| | | |
| | | ### 2.3 `GET /home/monthlyExpenditure` |
| | | |
| | | - æ åæ° |
| | | |
| | | ## 3. è¿ååæ®µå£å¾åæ´ |
| | | |
| | | ### 3.1 åºæ¶åºä»ç»è®¡ `statisticsReceivablePayable` |
| | | |
| | | è¿ååæ®µä¸åï¼ |
| | | |
| | | - `receivableMoney` |
| | | - `payableMoney` |
| | | - `advanceMoney` |
| | | - `prepayMoney` |
| | | |
| | | æ°å£å¾ï¼ |
| | | |
| | | - `receivableMoney = max(éå®ååéé¢å计 - æ¶æ¬¾éé¢å计, 0)` |
| | | - `payableMoney = max(éè´ååéé¢å计 - 仿¬¾éé¢å计, 0)` |
| | | - `advanceMoney = æ¶æ¬¾éé¢å计` |
| | | - `prepayMoney = 仿¬¾éé¢å计` |
| | | |
| | | 以ä¸éé¢åæ `type` å¯¹åºæ¶é´èå´ç»è®¡ï¼ä¿ç两ä½å°æ°ã |
| | | |
| | | è¿å示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "receivableMoney": 128000.00, |
| | | "payableMoney": 76000.00, |
| | | "advanceMoney": 42000.00, |
| | | "prepayMoney": 31000.00 |
| | | } |
| | | ``` |
| | | |
| | | ### 3.2 æåº¦æ¶å
¥ `monthlyIncome` |
| | | |
| | | è¿ååæ®µä¸åï¼ |
| | | |
| | | - `monthlyIncome` |
| | | - `collectionRate` |
| | | - `overdueNum` |
| | | - `overdueRate` |
| | | |
| | | æ°å£å¾ï¼ |
| | | |
| | | - `monthlyIncome`ï¼å½ææ¶æ¬¾å计 |
| | | - `collectionRate`ï¼`å½ææ¶æ¬¾å计 / 彿éå®ååéé¢å计 * 100` |
| | | - `overdueNum`ï¼åå²åºæ¶å¯¹è´¦åï¼`account_statement.account_type=1`ï¼ä¸ï¼æ©äºå½æä¸ `closing_balance > 0` çæ°é |
| | | - `overdueRate`ï¼`overdueNum / åå²åºæ¶å¯¹è´¦åæ»æ° * 100` |
| | | |
| | | è¿å示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "monthlyIncome": 89500.00, |
| | | "collectionRate": "62.80", |
| | | "overdueNum": 4, |
| | | "overdueRate": "18.18" |
| | | } |
| | | ``` |
| | | |
| | | ### 3.3 æåº¦æ¯åº `monthlyExpenditure` |
| | | |
| | | è¿ååæ®µä¸åï¼ |
| | | |
| | | - `monthlyExpenditure` |
| | | - `paymentRate` |
| | | - `grossProfit` |
| | | - `profitMarginRate` |
| | | |
| | | æ°å£å¾ï¼ |
| | | |
| | | - `monthlyExpenditure`ï¼å½æä»æ¬¾å计 |
| | | - `paymentRate`ï¼`彿仿¬¾å计 / 彿éè´ååéé¢å计 * 100` |
| | | - `grossProfit`ï¼`å½ææ¶æ¬¾å计 - 彿仿¬¾å计` |
| | | - `profitMarginRate`ï¼`grossProfit / å½ææ¶æ¬¾å计 * 100` |
| | | |
| | | è¿å示ä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "monthlyExpenditure": 73400.00, |
| | | "paymentRate": "57.34", |
| | | "grossProfit": 16100.00, |
| | | "profitMarginRate": "17.99" |
| | | } |
| | | ``` |
| | | |
| | | ## 4. å端èè°è¯´æ |
| | | |
| | | 1. åç«¯åæ®µæ å°æ éè°æ´ï¼å¯ç´æ¥æ²¿ç¨ç°æè§£æé»è¾ã |
| | | 2. ç¾åæ¯å段ä»ä¸ºä¸å¸¦ `%` çå符串ï¼å端å¦éå±ç¤º `%` 请继ç»åç«¯æ¼æ¥ã |
| | | 3. æ¬æ¬¡å端è¿åå¼ç±çå®è´¢å¡æ°æ®é©±å¨ï¼å»ºè®®éç¹åå½å¡çæ±æ»ä¸è¶å¿å¾çæ°å¼èå¨ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | ALTER TABLE approve_process |
| | | ADD COLUMN start_date_time datetime DEFAULT NULL COMMENT 'åºå·®å¼å§æ¶é´', |
| | | ADD COLUMN end_date_time datetime DEFAULT NULL COMMENT 'åºå·®ç»ææ¶é´'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # çäº§å·¥åºæ°å¢è®¡å人ååè®¡åæ§è¡äººååæ®µ |
| | | ALTER TABLE technology_routing_operation |
| | | ADD plan_person bigint NULL COMMENT '计å人åID', |
| | | ADD executor bigint NULL COMMENT 'è®¡åæ§è¡äººåID'; |
| | | |
| | | ALTER TABLE production_order_routing_operation |
| | | ADD plan_person bigint NULL COMMENT '计å人åID', |
| | | ADD executor bigint NULL COMMENT 'è®¡åæ§è¡äººåID'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # è´¢å¡æºè½ä½å端èè°ææ¡£ |
| | | |
| | | ## 1. 模å说æ |
| | | |
| | | è´¢å¡æºè½ä½å端已æ°å¢ç»ä¸å
¥å£ `financial-ai`ï¼ç¨äºä¸è´¢ä¸ä½ååæï¼è¦çï¼ |
| | | |
| | | - æºè½ææ¬æ ¸ç® |
| | | - 订å婿¶¦åæ |
| | | - åºåèµéåæ |
| | | - åºæ¶åºä»ä¸ç°éæµé¢æµ |
| | | - ç»è¥å¼å¸¸é¢è¦ |
| | | - AI ç»è¥é©¾é©¶è± |
| | | - æ¥æ¥/卿¥èªå¨çæ |
| | | - è´¢å¡ç¥è¯æ£ç´¢ï¼è½»é RAG ä¸ä¸æï¼ |
| | | |
| | | æ¥å£éç¨ **SSE æµå¼è¾åº**ï¼å·¥å
·å½ä¸æ¶è¿åç»æå JSON å符串ã |
| | | |
| | | ## 2. æ¥å£æ¸
å |
| | | |
| | | ### 2.1 å¯¹è¯æ¥å£ï¼SSEï¼ |
| | | |
| | | - `POST /financial-ai/chat` |
| | | - `Content-Type: application/json` |
| | | - `Accept: text/stream;charset=utf-8` |
| | | |
| | | 请æ±ä½ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "memoryId": "finance-uuid-001", |
| | | "message": "æ¥è¯¢è¿30å¤©äºæè®¢å" |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | - `memoryId`ï¼ä¼è¯å¯ä¸æ è¯ï¼åç«¯çæ UUIDï¼åä¼è¯å¤ç¨ï¼ |
| | | - `message`ï¼èªç¶è¯è¨é®é¢ |
| | | |
| | | --- |
| | | |
| | | ### 2.2 ä¼è¯å表 |
| | | |
| | | - `GET /financial-ai/history/sessions` |
| | | |
| | | --- |
| | | |
| | | ### 2.3 ä¼è¯æ¶æ¯ |
| | | |
| | | - `GET /financial-ai/history/messages/{memoryId}` |
| | | |
| | | --- |
| | | |
| | | ### 2.4 å é¤ä¼è¯ |
| | | |
| | | - `DELETE /financial-ai/history/{memoryId}` |
| | | |
| | | ## 3. SSE è¿åå¤çè§è |
| | | |
| | | ### 3.1 è¿åå½¢æ |
| | | |
| | | - æ®éé®çï¼æµå¼ææ¬ç段 |
| | | - å·¥å
·å½ä¸ï¼å®æ´ JSON å符串ï¼é叏䏿¬¡æ§è¾åºï¼ä¹å¯è½åçï¼ |
| | | |
| | | å端建议å¤çæµç¨ï¼ |
| | | |
| | | 1. å° SSE åçæé¡ºåºæ¼æ¥æ `rawText` |
| | | 2. 对 `rawText` å°è¯ `JSON.parse` |
| | | 3. è¥å¯è§£æï¼æ `type` å忏²æå¾è¡¨/è¡¨æ ¼ |
| | | 4. è¥ä¸å¯è§£æï¼ææ®éææ¬å±ç¤º |
| | | |
| | | ### 3.2 ç»æå JSON éç¨æ ¼å¼ |
| | | |
| | | ```json |
| | | { |
| | | "success": true, |
| | | "type": "financial_order_profit_analysis", |
| | | "description": "å·²å®æè®¢å婿¶¦åæ", |
| | | "summary": {}, |
| | | "data": {}, |
| | | "charts": {} |
| | | } |
| | | ``` |
| | | |
| | | åæ®µè¯´æï¼ |
| | | |
| | | - `type`ï¼ç»æç±»åï¼å端渲æååé®ï¼ |
| | | - `summary`ï¼å¤´é¨ææ |
| | | - `data`ï¼è¡¨æ ¼æç»/建议å表 |
| | | - `charts`ï¼ECharts `option` æ°æ® |
| | | |
| | | ## 4. type ä¸åç«¯é¡µé¢æ å° |
| | | |
| | | 建议æ `type` å»ºç«æ¸²æçç¥ï¼ |
| | | |
| | | - `financial_cost_accounting`ï¼ææ¬æ ¸ç®é¡µ |
| | | - `financial_order_profit_analysis`ï¼è®¢å婿¶¦é¡µ |
| | | - `financial_inventory_capital_analysis`ï¼åºåèµé页 |
| | | - `financial_cashflow_forecast`ï¼ç°éæµé¡µ |
| | | - `financial_business_anomaly_warning`ï¼é£é©é¢è¦é¡µ |
| | | - `financial_business_cockpit`ï¼ç»è¥é©¾é©¶è± |
| | | - `financial_operation_report`ï¼æ¥æ¥å¨æ¥é¡µ |
| | | - `financial_rag_knowledge`ï¼ç¥è¯æ£ç´¢/å£å¾è¯´æå¡ç |
| | | |
| | | ## 5. å
³é®æ°æ®å段ï¼èè°éç¹ï¼ |
| | | |
| | | ### 5.1 ææ¬/婿¶¦ç±» |
| | | |
| | | - `data.orders[]`ï¼ |
| | | - `salesContractNo` |
| | | - `customerName` |
| | | - `revenue` |
| | | - `materialCost` |
| | | - `laborCost` |
| | | - `depreciationCost` |
| | | - `scrapCost` |
| | | - `totalCost` |
| | | - `profit` |
| | | - `profitRate` |
| | | - `riskLevel` |
| | | - `reasons` |
| | | - `suggestion` |
| | | |
| | | ### 5.2 åºåèµéç±» |
| | | |
| | | - `data.items[]`ï¼ |
| | | - `productName` |
| | | - `model` |
| | | - `quantity` |
| | | - `inventoryValue` |
| | | - `stagnantDays` |
| | | - `overstock` |
| | | - `riskLevel` |
| | | |
| | | ### 5.3 ç°éæµç±» |
| | | |
| | | - `data.actualMonthly[]` / `data.forecastMonthly[]`ï¼ |
| | | - `month` |
| | | - `income` |
| | | - `expense` |
| | | - `netFlow` |
| | | - `data.receivableRiskTop[]` / `data.payablePressureTop[]` |
| | | |
| | | ### 5.4 å¼å¸¸é¢è¦ç±» |
| | | |
| | | - `data.items[]`ï¼ |
| | | - `riskLevel` |
| | | - `type` |
| | | - `message` |
| | | - `detail` |
| | | |
| | | ### 5.5 æ¥åç±» |
| | | |
| | | - `data.headline` |
| | | - `data.conclusions[]` |
| | | - `data.riskSuggestions[]` |
| | | - `data.orderProfitTop[]` |
| | | |
| | | ## 6. å¾è¡¨èè°è§è |
| | | |
| | | `charts` å
åæ®µå为 ECharts `option`ï¼å¯ç´æ¥åç»å¾è¡¨ç»ä»¶ã |
| | | |
| | | 常è§åæ®µï¼ |
| | | |
| | | - æ±ç¶å¾ï¼`orderProfitBarOption` / `processCostBarOption` / `inventoryValueTopOption` |
| | | - 饼å¾ï¼`costCompositionPieOption` / `inventoryAgingPieOption` / `anomalyLevelPieOption` |
| | | - è¶å¿å¾ï¼`cashFlowTrendOption` |
| | | - 仪表çï¼`fundGapGaugeOption` / `inventoryTurnoverGauge` |
| | | |
| | | ## 7. æ¨èå端é®å¥ï¼å彿µè¯ï¼ |
| | | |
| | | 1. `æ¥çæ¬æç»è¥é©¾é©¶è±` |
| | | 2. `æ¥è¯¢è¿30å¤©äºæè®¢å` |
| | | 3. `åæè¿30天åºåèµéå ç¨` |
| | | 4. `颿µæªæ¥3个æç°éæµ` |
| | | 5. `çææ¬å¨ç»è¥å¨æ¥` |
| | | 6. `为ä»ä¹å©æ¶¦ä¸é` |
| | | 7. `åªä¸ªå®¢æ·æèµé±` |
| | | 8. `åªä¸ªå·¥åºææ¬æé«` |
| | | |
| | | ## 8. å¼å¸¸ä¸å
åºå¤ç |
| | | |
| | | - `memoryId` 为空ï¼è¿åææ¬ `memoryIdä¸è½ä¸ºç©º` |
| | | - `message` 为空ï¼è¿åææ¬ `messageä¸è½ä¸ºç©º` |
| | | - æ æ°æ®åºæ¯ï¼`success=true` ä¸ `data.items=[]`ï¼å端æç©ºæå±ç¤º |
| | | - é JSON æµè¿åï¼ææ®éèå¤©ææ¬å±ç¤º |
| | | |
| | | ## 9. èè°å»ºè®® |
| | | |
| | | 1. å
å `type` ååå¨ï¼ä¿è¯ææç»æåç»æå¯è½å°ï¼ |
| | | 2. ååæè¦å¡çï¼`summary`ï¼+ è¡¨æ ¼ï¼`data`ï¼+ å¾è¡¨ï¼`charts`ï¼ |
| | | 3. æåè¡¥ä¼è¯åå²ä¸å é¤è½åï¼å½¢æå®æ´å¯¹è¯éç¯ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ç¥è¯åºåéæ£ç´¢åè½æ°æ®åºåæ´ |
| | | -- æ§è¡åè¯·ç¡®ä¿ knowledge_base 表已åå¨ |
| | | |
| | | -- 1. knowledge_base 表å¢å åæ®µ |
| | | ALTER TABLE knowledge_base |
| | | ADD COLUMN IF NOT EXISTS file_count INT DEFAULT 0 COMMENT 'æä»¶æ°é', |
| | | ADD COLUMN IF NOT EXISTS total_chunk_count INT DEFAULT 0 COMMENT 'æ»åçæ°é', |
| | | ADD COLUMN IF NOT EXISTS description VARCHAR(500) COMMENT 'ç¥è¯åºæè¿°'; |
| | | |
| | | -- 2. å建ç¥è¯åºæä»¶åéè®°å½è¡¨ |
| | | CREATE TABLE IF NOT EXISTS knowledge_base_vector ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主é®ID', |
| | | knowledge_base_id BIGINT NOT NULL COMMENT 'å
³èç¥è¯åºID', |
| | | storage_blob_id BIGINT NOT NULL COMMENT 'å
³èæä»¶blob ID', |
| | | file_name VARCHAR(255) NOT NULL COMMENT 'æä»¶åç§°', |
| | | file_type VARCHAR(50) NOT NULL COMMENT 'æä»¶ç±»å(docx/pdf/xlsx/txtç)', |
| | | vector_status TINYINT DEFAULT 0 COMMENT 'åéåç¶æ: 0-å¾
å¤ç, 1-å¤çä¸, 2-已宿, 3-失败', |
| | | vector_error VARCHAR(500) COMMENT 'åéå失败åå ', |
| | | chunk_count INT DEFAULT 0 COMMENT 'åçæ°é', |
| | | namespace VARCHAR(100) COMMENT 'åéå½å空é´', |
| | | create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | create_user INT COMMENT 'å建人', |
| | | update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | update_user INT COMMENT 'æ´æ°äºº', |
| | | tenant_id BIGINT COMMENT 'ç§æ·ID', |
| | | dept_id BIGINT COMMENT 'é¨é¨ID', |
| | | INDEX idx_knowledge_base_id (knowledge_base_id), |
| | | INDEX idx_storage_blob_id (storage_blob_id), |
| | | INDEX idx_vector_status (vector_status) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ç¥è¯åºæä»¶åéè®°å½è¡¨'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # å端èè°ææ¡£ï¼è®¾å¤æ¥ä¿® / 设å¤ä¿å
»å®æ¶ä»»å¡ / è´¢å¡ç§ç®æ»è´¦ï¼ |
| | | |
| | | ## 1. åæ´èå´ |
| | | |
| | | æ¬æ¬¡èè°æ¶å 3 个模åï¼ |
| | | |
| | | 1. è´¢å¡æ¨¡åï¼ç§ç®æ»è´¦å»æåè¯åå·ãæè¦ï¼åªè¿å 1 æ¡åè®¡æ°æ®ã |
| | | 2. 设å¤ä¿å
»å®æ¶ä»»å¡ï¼æ°å¢ `ä¿å
»äºº` åæ®µï¼å®æ¶ä»»å¡çæä¿å
»è®°å½æ¶å¸¦å
¥ã |
| | | 3. è®¾å¤æ¥ä¿®ï¼ç¡®è®¤æ¥ä¿®åæ°å¢éªæ¶å®¡æ¹ï¼éªæ¶éè¿åæç®å®ç»ã |
| | | |
| | | --- |
| | | |
| | | ## 2. æ¥å£æ¸
å |
| | | |
| | | ### 2.1 è´¢å¡-ç§ç®æ»è´¦ |
| | | |
| | | - **GET** `/financial/ledger/general` |
| | | - 说æï¼è¿åç§ç®æ»è´¦å计ï¼ä»
1 æ¡è®°å½ã |
| | | |
| | | #### 请æ±åæ°ï¼Queryï¼ |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |---|---|---|---| |
| | | | `subjectCode` | string | æ¯ | ç§ç®ç¼ç | |
| | | | `startMonth` | string | æ¯ | å¼å§æä»½ï¼æ ¼å¼ `YYYY-MM` | |
| | | | `endMonth` | string | æ¯ | ç»ææä»½ï¼æ ¼å¼ `YYYY-MM` | |
| | | |
| | | #### è¿åç»æ |
| | | |
| | | `R<List<FinLedgerRowVo>>` |
| | | |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": [ |
| | | { |
| | | "rowType": "yearly_total", |
| | | "date": "2026-05-31", |
| | | "debit": 12000.00, |
| | | "credit": 8000.00, |
| | | "direction": "å", |
| | | "balance": 4000.00 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | #### èè°æ³¨æ |
| | | |
| | | 1. `data` åºå®åªæ 1 æ¡ï¼å计ï¼ã |
| | | 2. `voucherNo`ã`summary` ä¸è¿åï¼ä¸åå±ç¤ºåè¯åå·ãæè¦ï¼ã |
| | | |
| | | --- |
| | | |
| | | ### 2.2 设å¤ä¿å
»å®æ¶ä»»å¡ï¼æ°å¢ä¿å
»äººï¼ |
| | | |
| | | - åºç¡è·¯å¾ï¼`/deviceMaintenanceTask` |
| | | - ç¸å
³æ¥å£ï¼ |
| | | - **POST** `/add` |
| | | - **POST** `/update` |
| | | - **GET** `/listPage` |
| | | |
| | | #### æ°å¢å段 |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |---|---|---| |
| | | | `maintenancePerson` | string | ä¿å
»äºº | |
| | | |
| | | #### æ°å¢/æ´æ°è¯·æ±ç¤ºä¾ |
| | | |
| | | ```json |
| | | { |
| | | "id": 1, |
| | | "taskName": "ç©ºåæºä¿å
»ä»»å¡", |
| | | "taskId": 1001, |
| | | "maintenancePerson": "å¼ ä¸", |
| | | "frequencyType": "MONTHLY", |
| | | "frequencyDetail": "10,09:00", |
| | | "remarks": "æ¯æä¾è¡ä¿å
»" |
| | | } |
| | | ``` |
| | | |
| | | #### 宿¶ä»»å¡ä¸åè¡ä¸º |
| | | |
| | | 宿¶ä»»å¡æ§è¡åï¼ç³»ç»èªå¨å建ä¿å
»è®°å½ï¼`device_maintenance`ï¼æ¶ä¼åå
¥ï¼ |
| | | |
| | | - `maintenanceActuallyName = maintenancePerson` |
| | | |
| | | å³å端å¨å®æ¶ä»»å¡éç»´æ¤çä¿å
»äººï¼ä¼èªå¨å¸¦å
¥å°ä¿å
»è®°å½ã |
| | | |
| | | --- |
| | | |
| | | ### 2.3 è®¾å¤æ¥ä¿®ï¼ç¡®è®¤åéªæ¶å®¡æ¹ï¼ |
| | | |
| | | - åºç¡è·¯å¾ï¼`/device/repair` |
| | | |
| | | #### ç¶æå®ä¹ |
| | | |
| | | | ç¶æå¼ | å«ä¹ | |
| | | |---|---| |
| | | | `0` | å¾
ç»´ä¿® | |
| | | | `3` | å¾
éªæ¶ | |
| | | | `1` | å®ç» | |
| | | | `2` | 失败 | |
| | | |
| | | #### 2.3.1 维修确认ï¼å确认æ¥ä¿®ï¼ |
| | | |
| | | - **POST** `/device/repair/repair` |
| | | - 说æï¼æäº¤åç¶æä» `å¾
ç»´ä¿®(0)` è¿å
¥ `å¾
éªæ¶(3)`ï¼ä¸åç´æ¥å®ç»ã |
| | | |
| | | 请æ±ç¤ºä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "id": 10001, |
| | | "maintenanceName": "æå", |
| | | "maintenanceTime": "2026-05-14 10:30:00", |
| | | "maintenanceResult": "æ´æ¢è½´æ¿å¹¶è¯è¿è¡æ£å¸¸", |
| | | "sparePartsUseList": [ |
| | | { |
| | | "id": 501, |
| | | "quantity": 2 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | 常è§å¤±è´¥æç¤ºï¼ç¨äºå端弹çªï¼ï¼ |
| | | |
| | | - `æ¥ä¿®è®°å½ä¸åå¨` |
| | | - `该æ¥ä¿®å·²å®ç»ï¼ä¸è½éå¤ç¡®è®¤ç»´ä¿®` |
| | | - `该æ¥ä¿®å·²æäº¤éªæ¶å®¡æ¹` |
| | | - `å¤ä»¶ xxx æ°éä¸è¶³` |
| | | |
| | | #### 2.3.2 éªæ¶å®¡æ¹ï¼æ°å¢ï¼ |
| | | |
| | | - **POST** `/device/repair/acceptance` |
| | | - 说æï¼ä»
`å¾
éªæ¶(3)` å¯å®¡æ¹ï¼å®¡æ¹éè¿åç¶ææ¹ä¸º `å®ç»(1)`ã |
| | | |
| | | 请æ±åæ°ï¼Bodyï¼ï¼ |
| | | |
| | | | åæ®µ | ç±»å | å¿
å¡« | 说æ | |
| | | |---|---|---|---| |
| | | | `id` | long | æ¯ | æ¥ä¿®è®°å½ID | |
| | | | `acceptanceName` | string | æ¯ | éªæ¶äºº | |
| | | | `acceptanceTime` | string | æ¯ | éªæ¶æ¶é´ï¼æ ¼å¼ `yyyy-MM-dd HH:mm:ss` | |
| | | | `acceptanceRemark` | string | æ¯ | éªæ¶å¤æ³¨ | |
| | | |
| | | 请æ±ç¤ºä¾ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "id": 10001, |
| | | "acceptanceName": "çäº", |
| | | "acceptanceTime": "2026-05-14 11:00:00", |
| | | "acceptanceRemark": "ç»´ä¿®é¡¹æ ¸éªéè¿ï¼è®¾å¤è¿è¡æ£å¸¸" |
| | | } |
| | | ``` |
| | | |
| | | 常è§å¤±è´¥æç¤ºï¼ |
| | | |
| | | - `æ¥ä¿®è®°å½idä¸è½ä¸ºç©º` |
| | | - `æ¥ä¿®è®°å½ä¸åå¨` |
| | | - `该æ¥ä¿®æªè¿å
¥å¾
éªæ¶ç¶æï¼ä¸è½å®¡æ¹` |
| | | - `éªæ¶äººä¸è½ä¸ºç©º` |
| | | - `éªæ¶æ¶é´ä¸è½ä¸ºç©º` |
| | | - `éªæ¶å¤æ³¨ä¸è½ä¸ºç©º` |
| | | |
| | | #### 2.3.3 æ®éæ´æ°æ¥å£éå¶ |
| | | |
| | | - **PUT** `/device/repair` |
| | | - éå¶ï¼ä¸è½éè¿æ®éæ´æ°ç´æ¥æç¶ææ¹æ `å®ç»(1)`ï¼å¿
é¡»èµ°éªæ¶å®¡æ¹æ¥å£ï¼ã |
| | | - 失败æç¤ºï¼`请å
æäº¤éªæ¶å®¡æ¹ï¼éªæ¶éè¿åæå¯å®ç»` |
| | | |
| | | --- |
| | | |
| | | ## 3. è¿ååæ®µåæ´ï¼æ¥ä¿®å表/详æ
ï¼ |
| | | |
| | | 以䏿¥å£è¿åå·²æ°å¢éªæ¶åæ®µï¼ |
| | | |
| | | - **GET** `/device/repair/page` |
| | | - **GET** `/device/repair/{id}` |
| | | |
| | | æ°å¢è¿ååæ®µï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |---|---|---| |
| | | | `acceptanceName` | string | éªæ¶äºº | |
| | | | `acceptanceTime` | string | éªæ¶æ¶é´ | |
| | | | `acceptanceRemark` | string | éªæ¶å¤æ³¨ | |
| | | |
| | | --- |
| | | |
| | | ## 4. å端æ¹é 建议 |
| | | |
| | | 1. æ¥ä¿®å表å¢å ç¶æå¼ `3=å¾
éªæ¶` çå±ç¤ºææ¡ä¸çé项ã |
| | | 2. â确认维修âæé®è°ç¨ `/device/repair/repair`ï¼æååå·æ°ä¸ºå¾
éªæ¶ç¶æã |
| | | 3. æ°å¢âéªæ¶å®¡æ¹âå¼¹çªï¼å¿
å¡«ï¼ |
| | | - éªæ¶äºº |
| | | - éªæ¶æ¶é´ |
| | | - éªæ¶å¤æ³¨ |
| | | 4. ç¦æ¢å¨æ®éç¼è¾é¡µç´æ¥å°ç¶æç½®ä¸ºå®ç»ã |
| | | 5. 设å¤ä¿å
»å®æ¶ä»»å¡æ°å¢âä¿å
»äººâè¾å
¥é¡¹ï¼å¹¶å¨å表/详æ
å±ç¤ºã |
| | | 6. ç§ç®æ»è´¦é¡µé¢æåè¡å计渲æï¼ä¸åæ¾ç¤ºåè¯åå·ãæè¦åã |
| | | |
| | | --- |
| | | |
| | | ## 5. èè°æ£æ¥æ¸
å |
| | | |
| | | 1. ç§ç®æ»è´¦æ¥è¯¢è¿å `data.length === 1`ï¼ä¸æ `voucherNo/summary`ã |
| | | 2. æ°å¢ä¿å
»å®æ¶ä»»å¡æ¶ä¼ `maintenancePerson`ï¼å表è½åæ¾ã |
| | | 3. 宿¶ä»»å¡è§¦ååï¼çæçä¿å
»è®°å½ `maintenanceActuallyName` ä¸å®æ¶ä»»å¡ä¿å
»äººä¸è´ã |
| | | 4. æ¥ä¿®åæµç¨ï¼`0å¾
ç»´ä¿® -> 3å¾
éªæ¶ -> 1å®ç»`ã |
| | | 5. å¾
éªæ¶åæ®æªå¡«éªæ¶äºº/éªæ¶æ¶é´/éªæ¶å¤æ³¨æ¶ï¼å端è¿å对åºé误æç¤ºã |
| | | 6. å°è¯éè¿ `PUT /device/repair` ç´æ¥è®¾ä¸ºå®ç»æ¶ï¼å端è¿åæ¦æªæç¤ºã |
| | | |
| | | --- |
| | | |
| | | ## 6. æ°æ®åºåæ´ï¼èè°åç¡®è®¤ï¼ |
| | | |
| | | ```sql |
| | | ALTER TABLE maintenance_task |
| | | ADD COLUMN maintenance_person VARCHAR(100) NULL COMMENT 'ä¿å
»äºº'; |
| | | |
| | | ALTER TABLE device_repair |
| | | ADD COLUMN acceptance_name VARCHAR(100) NULL COMMENT 'éªæ¶äºº', |
| | | ADD COLUMN acceptance_time DATETIME NULL COMMENT 'éªæ¶æ¶é´', |
| | | ADD COLUMN acceptance_remark VARCHAR(500) NULL COMMENT 'éªæ¶å¤æ³¨'; |
| | | ``` |
| | | |
| | | > è¥æªæ§è¡ä»¥ä¸ SQLï¼ç¸å
³æ¥å£ä¼åºç°å段ä¸åå¨å¼å¸¸ã |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # ç¥è¯åºRAGåéæ£ç´¢åè½å®ç°ææ¡£ |
| | | |
| | | ## ä¸ãåè½æ¦è¿° |
| | | |
| | | åºäº RAGï¼Retrieval-Augmented Generationï¼ææ¯å®ç°ç¥è¯åºé®çåè½ï¼æ¯æï¼ |
| | | - ç¥è¯åºç®¡çï¼CRUDï¼ |
| | | - æä»¶ä¸ä¼ ä¸åéåå¤ç |
| | | - åºäºåéæ£ç´¢çæºè½é®ç |
| | | - å¤ç§æä»¶æ ¼å¼æ¯æï¼txtãmdãdocxãxlsxãxlsãpdfï¼ |
| | | |
| | | ## äºãææ¯æ¶æ |
| | | |
| | | ### 2.1 ææ¯æ |
| | | | ç»ä»¶ | ææ¯ | |
| | | |------|------| |
| | | | åéæ°æ®åº | Pinecone | |
| | | | Embedding模å | é¿éäº DashScope text-embedding-v3 | |
| | | | LLM | é¿éäºéä¹åé® qwen-max | |
| | | | æ¡æ¶ | langchain4j | |
| | | | ORM | MyBatis-Plus | |
| | | |
| | | ### 2.2 æ¶æå¾ |
| | | |
| | | ``` |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â å端åºç¨ â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â |
| | | â¼ |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â Controller Layer â |
| | | â âââââââââââââââââââââââ âââââââââââââââââââââââââââââââ â |
| | | â â KnowledgeBaseCtrl â â KnowledgeChatController â â |
| | | â â (ç¥è¯åºç®¡ç) â â (ç¥è¯åºé®ç) â â |
| | | â âââââââââââââââââââââââ âââââââââââââââââââââââââââââââ â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â |
| | | â¼ |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â Service Layer â |
| | | â âââââââââââââââââââââââ âââââââââââââââââââââââââââââââ â |
| | | â âKnowledgeBaseService â â KnowledgeRagService â â |
| | | â â (ç¥è¯åºCRUD) â â (åéå/æ£ç´¢) â â |
| | | â âââââââââââââââââââââââ âââââââââââââââââââââââââââââââ â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â |
| | | â¼ |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â AI Layer â |
| | | â âââââââââââââââââââââââ âââââââââââââââââââââââââââââââ â |
| | | â â KnowledgeChatAgent â â EmbeddingStore (Pinecone) â â |
| | | â â (é®çAgent) â â (åéåå¨) â â |
| | | â âââââââââââââââââââââââ âââââââââââââââââââââââââââââââ â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ä¸ãå端å®ç° |
| | | |
| | | ### 3.1 æ°æ®åºè®¾è®¡ |
| | | |
| | | #### 3.1.1 ç¥è¯åºè¡¨ï¼knowledge_baseï¼ |
| | | |
| | | ```sql |
| | | CREATE TABLE knowledge_base ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | title VARCHAR(255) COMMENT 'ç¥è¯æ é¢', |
| | | type VARCHAR(50) COMMENT 'ç¥è¯ç±»å', |
| | | scenario VARCHAR(255) COMMENT 'éç¨åºæ¯', |
| | | efficiency VARCHAR(20) COMMENT 'è§£å³æç', |
| | | problem TEXT COMMENT 'é®é¢æè¿°', |
| | | solution TEXT COMMENT 'è§£å³æ¹æ¡', |
| | | key_points TEXT COMMENT 'å
³é®è¦ç¹', |
| | | creator VARCHAR(100) COMMENT 'å建人', |
| | | usage_count INT DEFAULT 0 COMMENT 'ä½¿ç¨æ¬¡æ°', |
| | | file_count INT DEFAULT 0 COMMENT 'æä»¶æ°é', |
| | | total_chunk_count INT DEFAULT 0 COMMENT 'æ»åçæ°é', |
| | | description VARCHAR(500) COMMENT 'ç¥è¯åºæè¿°', |
| | | create_time DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | create_user INT, |
| | | update_user INT, |
| | | tenant_id BIGINT, |
| | | dept_id BIGINT |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ç¥è¯åºè¡¨'; |
| | | ``` |
| | | |
| | | #### 3.1.2 ç¥è¯åºåéè®°å½è¡¨ï¼knowledge_base_vectorï¼ |
| | | |
| | | ```sql |
| | | CREATE TABLE knowledge_base_vector ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主é®ID', |
| | | knowledge_base_id BIGINT NOT NULL COMMENT 'å
³èç¥è¯åºID', |
| | | storage_blob_id BIGINT NOT NULL COMMENT 'å
³èæä»¶blob ID', |
| | | file_name VARCHAR(255) NOT NULL COMMENT 'æä»¶åç§°', |
| | | file_type VARCHAR(50) NOT NULL COMMENT 'æä»¶ç±»å', |
| | | vector_status TINYINT DEFAULT 0 COMMENT 'åéåç¶æ: 0-å¾
å¤ç, 1-å¤çä¸, 2-已宿, 3-失败', |
| | | vector_error VARCHAR(500) COMMENT 'åéå失败åå ', |
| | | chunk_count INT DEFAULT 0 COMMENT 'åçæ°é', |
| | | namespace VARCHAR(100) COMMENT 'åéå½å空é´', |
| | | create_time DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | create_user INT, |
| | | update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | update_user INT, |
| | | tenant_id BIGINT, |
| | | dept_id BIGINT, |
| | | INDEX idx_knowledge_base_id (knowledge_base_id), |
| | | INDEX idx_storage_blob_id (storage_blob_id), |
| | | INDEX idx_vector_status (vector_status) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ç¥è¯åºæä»¶åéè®°å½è¡¨'; |
| | | ``` |
| | | |
| | | ### 3.2 Mavenä¾èµ |
| | | |
| | | ```xml |
| | | <!-- langchain4j BOM --> |
| | | <dependencyManagement> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>dev.langchain4j</groupId> |
| | | <artifactId>langchain4j-bom</artifactId> |
| | | <version>1.0.0-beta3</version> |
| | | <type>pom</type> |
| | | <scope>import</scope> |
| | | </dependency> |
| | | </dependencies> |
| | | </dependencyManagement> |
| | | |
| | | <dependencies> |
| | | <!-- langchain4j æ ¸å¿ --> |
| | | <dependency> |
| | | <groupId>dev.langchain4j</groupId> |
| | | <artifactId>langchain4j-spring-boot-starter</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- Pinecone åéæ°æ®åº --> |
| | | <dependency> |
| | | <groupId>dev.langchain4j</groupId> |
| | | <artifactId>langchain4j-pinecone</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- é¿éäº DashScope --> |
| | | <dependency> |
| | | <groupId>dev.langchain4j</groupId> |
| | | <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId> |
| | | </dependency> |
| | | </dependencies> |
| | | ``` |
| | | |
| | | ### 3.3 é
ç½®æä»¶ï¼application.ymlï¼ |
| | | |
| | | ```yaml |
| | | # Pinecone åéæ°æ®åºé
ç½® |
| | | pinecone: |
| | | api-key: your-pinecone-api-key |
| | | index: your-index-name |
| | | namespace: knowledge-base |
| | | |
| | | # langchain4j é
ç½® |
| | | langchain4j: |
| | | community: |
| | | dashscope: |
| | | streaming-chat-model: |
| | | api-key: your-dashscope-api-key |
| | | model-name: "qwen-max" |
| | | embedding-model: |
| | | api-key: your-dashscope-api-key |
| | | model-name: "text-embedding-v3" |
| | | ``` |
| | | |
| | | ### 3.4 æ ¸å¿ä»£ç å®ç° |
| | | |
| | | #### 3.4.1 å®ä½ç±» |
| | | |
| | | **KnowledgeBase.java** |
| | | ```java |
| | | @Data |
| | | @TableName("knowledge_base") |
| | | public class KnowledgeBase implements Serializable { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private String title; |
| | | private String type; |
| | | private String scenario; |
| | | private String efficiency; |
| | | private String problem; |
| | | private String solution; |
| | | private String keyPoints; |
| | | private String creator; |
| | | private Integer usageCount; |
| | | private Integer fileCount; |
| | | private Integer totalChunkCount; |
| | | private String description; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long tenantId; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| | | ``` |
| | | |
| | | **KnowledgeBaseVector.java** |
| | | ```java |
| | | @Data |
| | | @TableName("knowledge_base_vector") |
| | | public class KnowledgeBaseVector implements Serializable { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Long knowledgeBaseId; |
| | | private Long storageBlobId; |
| | | private String fileName; |
| | | private String fileType; |
| | | private Integer vectorStatus; |
| | | private String vectorError; |
| | | private Integer chunkCount; |
| | | private String namespace; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long tenantId; |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | // åéåç¶æå¸¸é |
| | | public static final int STATUS_PENDING = 0; |
| | | public static final int STATUS_PROCESSING = 1; |
| | | public static final int STATUS_COMPLETED = 2; |
| | | public static final int STATUS_FAILED = 3; |
| | | } |
| | | ``` |
| | | |
| | | #### 3.4.2 EmbeddingStoreé
ç½® |
| | | |
| | | **EmbeddingStoreConfig.java** |
| | | ```java |
| | | @Configuration |
| | | public class EmbeddingStoreConfig { |
| | | |
| | | @Value("${pinecone.api-key}") |
| | | private String pineconeApiKey; |
| | | |
| | | @Value("${pinecone.index}") |
| | | private String indexName; |
| | | |
| | | @Value("${pinecone.namespace}") |
| | | private String namespace; |
| | | |
| | | @Bean |
| | | public Pinecone pinecone() { |
| | | return new Pinecone.Builder(pineconeApiKey).build(); |
| | | } |
| | | |
| | | @Bean |
| | | public Index pineconeIndex(Pinecone pinecone) { |
| | | return pinecone.getIndexConnection(indexName); |
| | | } |
| | | |
| | | @Bean |
| | | public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) { |
| | | return PineconeEmbeddingStore.builder() |
| | | .apiKey(pineconeApiKey) |
| | | .index(indexName) |
| | | .nameSpace(namespace) |
| | | .createIndex(PineconeServerlessIndexConfig.builder() |
| | | .cloud("AWS") |
| | | .region("us-east-1") |
| | | .dimension(embeddingModel.dimension()) |
| | | .build()) |
| | | .build(); |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | #### 3.4.3 RAGæå¡å®ç° |
| | | |
| | | **KnowledgeRagService.java** |
| | | ```java |
| | | public interface KnowledgeRagService { |
| | | void processVectorAsync(Long vectorId); |
| | | void processVector(Long vectorId); |
| | | List<String> searchRelevantContent(String namespace, String query, int maxResults); |
| | | void deleteEmbeddings(String namespace, Long storageBlobId); |
| | | } |
| | | ``` |
| | | |
| | | **KnowledgeRagServiceImpl.java**ï¼æ ¸å¿å®ç°ï¼ |
| | | ```java |
| | | @Slf4j |
| | | @Service |
| | | public class KnowledgeRagServiceImpl implements KnowledgeRagService { |
| | | |
| | | private final KnowledgeBaseVectorService knowledgeBaseVectorService; |
| | | private final StorageBlobService storageBlobService; |
| | | private final EmbeddingModel embeddingModel; |
| | | private final EmbeddingStore<TextSegment> embeddingStore; |
| | | private final FileProperties fileProperties; |
| | | private final Index pineconeIndex; |
| | | |
| | | @Value("${pinecone.namespace}") |
| | | private String namespace; |
| | | |
| | | private static final int CHUNK_SIZE = 500; |
| | | private static final int CHUNK_OVERLAP = 100; |
| | | private static final long CHUNK_THRESHOLD_BYTES = 80L * 1024 * 1024; |
| | | private static final int EMBEDDING_MAX_LENGTH = 8000; |
| | | |
| | | @Override |
| | | @Async("threadPoolTaskExecutor") |
| | | public void processVectorAsync(Long vectorId) { |
| | | processVector(vectorId); |
| | | } |
| | | |
| | | @Override |
| | | public void processVector(Long vectorId) { |
| | | KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId); |
| | | if (vector == null) return; |
| | | |
| | | try { |
| | | // æ´æ°ç¶æä¸ºå¤çä¸ |
| | | knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_PROCESSING, null, null); |
| | | |
| | | // è·åæä»¶å
容 |
| | | StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId()); |
| | | File file = getFile(blob); |
| | | String content = extractFileContent(file, vector.getFileName()); |
| | | |
| | | if (content == null || content.trim().isEmpty()) { |
| | | throw new RuntimeException("æä»¶å
容为空"); |
| | | } |
| | | |
| | | // ææ¬åç |
| | | List<TextSegment> chunks; |
| | | boolean needChunk = file.length() > CHUNK_THRESHOLD_BYTES || content.length() > EMBEDDING_MAX_LENGTH; |
| | | if (needChunk) { |
| | | chunks = splitText(content, vector); |
| | | } else { |
| | | Map<String, Object> metadata = buildMetadata(vector); |
| | | chunks = List.of(TextSegment.from(content, new Metadata(metadata))); |
| | | } |
| | | |
| | | // çæåµå
¥åéå¹¶åå¨ |
| | | int chunkCount = 0; |
| | | for (TextSegment chunk : chunks) { |
| | | Embedding embedding = embeddingModel.embed(chunk).content(); |
| | | embeddingStore.add(embedding, chunk); |
| | | chunkCount++; |
| | | } |
| | | |
| | | // æ´æ°ç¶æä¸ºå®æ |
| | | knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_COMPLETED, chunkCount, null); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("åéåå¤ç失败", e); |
| | | knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_FAILED, null, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<String> searchRelevantContent(String namespace, String query, int maxResults) { |
| | | Embedding queryEmbedding = embeddingModel.embed(query).content(); |
| | | EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() |
| | | .queryEmbedding(queryEmbedding) |
| | | .maxResults(maxResults) |
| | | .minScore(0.7) |
| | | .build(); |
| | | EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest); |
| | | return searchResult.matches().stream() |
| | | .map(match -> match.embedded().text()) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteEmbeddings(String namespace, Long storageBlobId) { |
| | | Struct filter = Struct.newBuilder() |
| | | .putFields("storageBlobId", Value.newBuilder() |
| | | .setStructValue(Struct.newBuilder() |
| | | .putFields("$eq", Value.newBuilder() |
| | | .setNumberValue(storageBlobId.doubleValue()) |
| | | .build())) |
| | | .build()) |
| | | .build(); |
| | | pineconeIndex.delete(new ArrayList<>(), false, this.namespace, filter); |
| | | } |
| | | |
| | | private String extractFileContent(File file, String fileName) throws Exception { |
| | | String ext = getFileExtension(fileName); |
| | | if (isPlainText(ext)) { |
| | | return readFileWithEncoding(file); |
| | | } |
| | | if ("docx".equals(ext)) { |
| | | return extractDocx(file); |
| | | } |
| | | if ("xlsx".equals(ext) || "xls".equals(ext)) { |
| | | return extractExcel(file); |
| | | } |
| | | return readFileWithEncoding(file); |
| | | } |
| | | |
| | | // ... å
¶ä»è¾
婿¹æ³ |
| | | } |
| | | ``` |
| | | |
| | | #### 3.4.4 ç¥è¯åºé®çAgent |
| | | |
| | | **KnowledgeChatAgent.java** |
| | | ```java |
| | | @AiService( |
| | | wiringMode = EXPLICIT, |
| | | streamingChatModel = "qwenStreamingChatModel", |
| | | chatMemoryProvider = "chatMemoryProvider" |
| | | ) |
| | | public interface KnowledgeChatAgent { |
| | | |
| | | @SystemMessage(""" |
| | | ä½ æ¯ä¼ä¸ç¥è¯åºé®ç婿ã |
| | | ä½ éè¦åºäºæä¾çç¥è¯åºå
容åçç¨æ·é®é¢ã |
| | | éµå¾ªä»¥ä¸è§åï¼ |
| | | 1. ä¸¥æ ¼åºäºç¥è¯åºå
容åçï¼ä¸è¦ç¼é ä¿¡æ¯ |
| | | 2. 妿ç¥è¯åºä¸æ²¡æç¸å
³ä¿¡æ¯ï¼æç¡®åç¥ç¨æ· |
| | | 3. åçè¦åç¡®ãç®æ´ãææ¡ç |
| | | 4. å¼ç¨æ¥æºæ¶æ³¨æ"æ ¹æ®ç¥è¯åºå
容" |
| | | """) |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage); |
| | | } |
| | | ``` |
| | | |
| | | #### 3.4.5 Controllerå± |
| | | |
| | | **KnowledgeBaseController.java** |
| | | ```java |
| | | @RestController |
| | | @RequestMapping("/knowledgeBase") |
| | | @Tag(name = "ç¥è¯åºç®¡ç") |
| | | public class KnowledgeBaseController { |
| | | |
| | | @GetMapping("/getList") |
| | | public AjaxResult getList(@RequestParam(defaultValue = "1") long current, |
| | | @RequestParam(defaultValue = "10") long size, |
| | | KnowledgeBase knowledgeBase) { |
| | | Page page = new Page(current, size); |
| | | return AjaxResult.success(knowledgeBaseService.listpage(page, knowledgeBase)); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase) { |
| | | return AjaxResult.success(knowledgeBaseService.save(knowledgeBase)); |
| | | } |
| | | |
| | | @PostMapping("/update") |
| | | public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase) { |
| | | return AjaxResult.success(knowledgeBaseService.updateById(knowledgeBase)); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | public AjaxResult delete(@RequestBody List<Long> ids) { |
| | | return AjaxResult.success(knowledgeBaseService.removeByIds(ids)); |
| | | } |
| | | |
| | | @GetMapping("/vector/status/{knowledgeBaseId}") |
| | | @Operation(summary = "æ¥è¯¢ç¥è¯åºæä»¶åéåç¶æ") |
| | | public AjaxResult getVectorStatus(@PathVariable Long knowledgeBaseId) { |
| | | return AjaxResult.success(knowledgeBaseVectorService.getVectorStatusByKnowledgeBaseId(knowledgeBaseId)); |
| | | } |
| | | |
| | | @PostMapping("/vector/reprocess/{vectorId}") |
| | | @Operation(summary = "éæ°åéåæä»¶") |
| | | public AjaxResult reprocessVector(@PathVariable Long vectorId) { |
| | | knowledgeBaseVectorService.reprocessVector(vectorId); |
| | | return AjaxResult.success("已鿰æäº¤åéåä»»å¡"); |
| | | } |
| | | |
| | | @PostMapping("/file/save") |
| | | @Operation(summary = "ä¿åç¥è¯åºæä»¶å
³è") |
| | | public AjaxResult saveKnowledgeBaseFiles(@RequestBody KnowledgeBaseFileDTO dto) { |
| | | // ä¿åéä»¶å
³è并触ååéå |
| | | // ... |
| | | } |
| | | |
| | | @DeleteMapping("/file/delete") |
| | | @Operation(summary = "å é¤ç¥è¯åºæä»¶") |
| | | public AjaxResult deleteKnowledgeBaseFiles(@RequestBody List<Long> vectorIds) { |
| | | knowledgeBaseVectorService.deleteVectors(vectorIds); |
| | | return AjaxResult.success(); |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **KnowledgeChatController.java** |
| | | ```java |
| | | @RestController |
| | | @RequestMapping("/ai/knowledge") |
| | | @Tag(name = "ç¥è¯åºé®ç") |
| | | public class KnowledgeChatController { |
| | | |
| | | private final KnowledgeChatAgent knowledgeChatAgent; |
| | | private final KnowledgeRagService knowledgeRagService; |
| | | private final KnowledgeBaseService knowledgeBaseService; |
| | | |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | @Operation(summary = "ç¥è¯åºé®ç") |
| | | public Flux<String> chat(@RequestBody KnowledgeChatRequest request) { |
| | | // æ£ç´¢ç¸å
³å
容 |
| | | String namespace = "kb-" + request.getKnowledgeBaseId(); |
| | | List<String> relevantContents = knowledgeRagService.searchRelevantContent( |
| | | namespace, request.getQuestion(), 5); |
| | | |
| | | if (relevantContents.isEmpty()) { |
| | | return Flux.just("ç¥è¯åºä¸æªæ¾å°ç¸å
³å
容"); |
| | | } |
| | | |
| | | // æå»ºä¸ä¸æ |
| | | StringBuilder context = new StringBuilder(); |
| | | context.append("以䏿¯ä»ç¥è¯åºä¸æ£ç´¢å°çç¸å
³å
容ï¼\n\n"); |
| | | for (int i = 0; i < relevantContents.size(); i++) { |
| | | context.append("ãå
容").append(i + 1).append("ã\n"); |
| | | context.append(relevantContents.get(i)).append("\n\n"); |
| | | } |
| | | context.append("---\n请åºäºä»¥ä¸ç¥è¯åºå
容åçï¼\n").append(request.getQuestion()); |
| | | |
| | | return knowledgeChatAgent.chat(request.getMemoryId(), context.toString()); |
| | | } |
| | | |
| | | @GetMapping("/list") |
| | | @Operation(summary = "ç¥è¯åºå表") |
| | | public AjaxResult listKnowledgeBases() { |
| | | return AjaxResult.success(knowledgeBaseService.list()); |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## åãAPIæ¥å£ææ¡£ |
| | | |
| | | ### 4.1 ç¥è¯åºç®¡çæ¥å£ |
| | | |
| | | | æ¥å£ | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------|------| |
| | | | è·åå表 | GET | /knowledgeBase/getList | å页æ¥è¯¢ç¥è¯åºå表 | |
| | | | æ°å¢ç¥è¯åº | POST | /knowledgeBase/add | å建ç¥è¯åº | |
| | | | æ´æ°ç¥è¯åº | POST | /knowledgeBase/update | æ´æ°ç¥è¯åºä¿¡æ¯ | |
| | | | å é¤ç¥è¯åº | DELETE | /knowledgeBase/delete | æ¹éå é¤ç¥è¯åº | |
| | | | æ¥è¯¢åéåç¶æ | GET | /knowledgeBase/vector/status/{id} | æ¥è¯¢æä»¶åéåç¶æ | |
| | | | éæ°åéå | POST | /knowledgeBase/vector/reprocess/{id} | éæ°å¤ç失败çæä»¶ | |
| | | | ä¿åæä»¶å
³è | POST | /knowledgeBase/file/save | ä¸ä¼ æä»¶åå
³èå°ç¥è¯åº | |
| | | | å 餿件 | DELETE | /knowledgeBase/file/delete | å é¤ç¥è¯åºæä»¶ | |
| | | |
| | | ### 4.2 ç¥è¯åºé®çæ¥å£ |
| | | |
| | | | æ¥å£ | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------|------| |
| | | | ç¥è¯åºé®ç | POST | /ai/knowledge/chat | æµå¼è¿åé®çç»æ | |
| | | | ç¥è¯åºå表 | GET | /ai/knowledge/list | è·åå¯éç¥è¯åºå表 | |
| | | |
| | | ### 4.3 æ¥å£è¯¦ç»è¯´æ |
| | | |
| | | #### 4.3.1 ä¿åç¥è¯åºæä»¶å
³è |
| | | |
| | | **请æ±** |
| | | ```json |
| | | POST /knowledgeBase/file/save |
| | | { |
| | | "knowledgeBaseId": 1, |
| | | "storageBlobIds": [100, 101, 102] |
| | | } |
| | | ``` |
| | | |
| | | **ååº** |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå" |
| | | } |
| | | ``` |
| | | |
| | | #### 4.3.2 ç¥è¯åºé®ç |
| | | |
| | | **请æ±** |
| | | ```json |
| | | POST /ai/knowledge/chat |
| | | Content-Type: application/json |
| | | |
| | | { |
| | | "knowledgeBaseId": 1, |
| | | "memoryId": "session-uuid", |
| | | "question": "å¦ä½å¤çåºåçç¹å·®å¼ï¼" |
| | | } |
| | | ``` |
| | | |
| | | **ååº**ï¼SSEæµå¼ï¼ |
| | | ``` |
| | | æ ¹æ®ç¥è¯åºå
容ï¼åºåçç¹å·®å¼çå¤çæµç¨å¦ä¸ï¼ |
| | | |
| | | 1. åç°å·®å¼åï¼é¦å
æ ¸å¯¹çç¹è®°å½... |
| | | 2. æ£æ¥æ¯å¦ææ¼çæéç... |
| | | 3. ... |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## äºãå端å®ç° |
| | | |
| | | ### 5.1 ç¥è¯åºç®¡çé¡µé¢ |
| | | |
| | | ```vue |
| | | <template> |
| | | <div class="knowledge-base"> |
| | | <!-- å表 --> |
| | | <el-table :data="tableData" border> |
| | | <el-table-column prop="title" label="ç¥è¯æ é¢" /> |
| | | <el-table-column prop="type" label="ç¥è¯ç±»å" /> |
| | | <el-table-column prop="fileCount" label="æä»¶æ°é" /> |
| | | <el-table-column prop="totalChunkCount" label="åçæ°é" /> |
| | | <el-table-column label="æä½"> |
| | | <template #default="{ row }"> |
| | | <el-button @click="handleEdit(row)">ç¼è¾</el-button> |
| | | <el-button @click="handleFiles(row)">æä»¶ç®¡ç</el-button> |
| | | <el-button @click="handleChat(row)">é®ç</el-button> |
| | | <el-button type="danger" @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { getKnowledgeBaseList, deleteKnowledgeBase } from '@/api/knowledge' |
| | | |
| | | const tableData = ref([]) |
| | | |
| | | const loadData = async () => { |
| | | const res = await getKnowledgeBaseList({ current: 1, size: 10 }) |
| | | tableData.value = res.data.records |
| | | } |
| | | |
| | | onMounted(loadData) |
| | | </script> |
| | | ``` |
| | | |
| | | ### 5.2 æä»¶ä¸ä¼ ä¸åéåç¶æ |
| | | |
| | | ```vue |
| | | <template> |
| | | <div class="file-manager"> |
| | | <!-- æä»¶ä¸ä¼ --> |
| | | <el-upload |
| | | :action="uploadUrl" |
| | | :on-success="handleUploadSuccess" |
| | | multiple |
| | | > |
| | | <el-button type="primary">ä¸ä¼ æä»¶</el-button> |
| | | </el-upload> |
| | | |
| | | <!-- æä»¶å表ä¸åéåç¶æ --> |
| | | <el-table :data="fileList"> |
| | | <el-table-column prop="fileName" label="æä»¶å" /> |
| | | <el-table-column label="åéåç¶æ"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.vectorStatus)"> |
| | | {{ getStatusText(row.vectorStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="chunkCount" label="åçæ°" /> |
| | | <el-table-column label="æä½"> |
| | | <template #default="{ row }"> |
| | | <el-button v-if="row.vectorStatus === 3" @click="reprocess(row)"> |
| | | éæ°å¤ç |
| | | </el-button> |
| | | <el-button type="danger" @click="deleteFile(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload' |
| | | |
| | | // ä¸ä¼ æååä¿åå
³è |
| | | const uploadedBlobIds = ref([]) |
| | | |
| | | const handleUploadSuccess = (response, file) => { |
| | | if (response.code === 200) { |
| | | uploadedBlobIds.value.push(response.data.id) |
| | | } |
| | | } |
| | | |
| | | // ä¿åæä»¶å
³è |
| | | const saveFiles = async () => { |
| | | await saveKnowledgeBaseFiles({ |
| | | knowledgeBaseId: props.knowledgeBaseId, |
| | | storageBlobIds: uploadedBlobIds.value |
| | | }) |
| | | // å·æ°æä»¶å表 |
| | | loadFileList() |
| | | } |
| | | |
| | | // ç¶æææ¬æ å° |
| | | const getStatusText = (status) => { |
| | | const map = { |
| | | 0: 'å¾
å¤ç', |
| | | 1: 'å¤çä¸', |
| | | 2: '已宿', |
| | | 3: '失败' |
| | | } |
| | | return map[status] || 'æªç¥' |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { |
| | | 0: 'info', |
| | | 1: 'warning', |
| | | 2: 'success', |
| | | 3: 'danger' |
| | | } |
| | | return map[status] || 'info' |
| | | } |
| | | </script> |
| | | ``` |
| | | |
| | | ### 5.3 ç¥è¯åºé®ççé¢ |
| | | |
| | | ```vue |
| | | <template> |
| | | <div class="knowledge-chat"> |
| | | <!-- ç¥è¯åºéæ© --> |
| | | <el-select v-model="selectedKbId" placeholder="éæ©ç¥è¯åº"> |
| | | <el-option |
| | | v-for="kb in knowledgeBases" |
| | | :key="kb.id" |
| | | :label="kb.title" |
| | | :value="kb.id" |
| | | /> |
| | | </el-select> |
| | | |
| | | <!-- 对è¯åºå --> |
| | | <div class="chat-messages"> |
| | | <div |
| | | v-for="(msg, index) in messages" |
| | | :key="index" |
| | | :class="['message', msg.role]" |
| | | > |
| | | <div class="content">{{ msg.content }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¾å
¥æ¡ --> |
| | | <el-input |
| | | v-model="inputQuestion" |
| | | placeholder="请è¾å
¥é®é¢" |
| | | @keyup.enter="sendMessage" |
| | | > |
| | | <template #append> |
| | | <el-button @click="sendMessage" :loading="loading">åé</el-button> |
| | | </template> |
| | | </el-input> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { getKnowledgeBaseList, knowledgeChat } from '@/api/knowledge' |
| | | |
| | | const knowledgeBases = ref([]) |
| | | const selectedKbId = ref(null) |
| | | const messages = ref([]) |
| | | const inputQuestion = ref('') |
| | | const loading = ref(false) |
| | | const memoryId = ref(crypto.randomUUID()) |
| | | |
| | | const sendMessage = async () => { |
| | | if (!inputQuestion.value.trim()) return |
| | | if (!selectedKbId.value) { |
| | | ElMessage.warning('è¯·éæ©ç¥è¯åº') |
| | | return |
| | | } |
| | | |
| | | // æ·»å ç¨æ·æ¶æ¯ |
| | | messages.value.push({ |
| | | role: 'user', |
| | | content: inputQuestion.value |
| | | }) |
| | | |
| | | loading.value = true |
| | | |
| | | try { |
| | | // æµå¼è¯·æ± |
| | | const response = await fetch('/api/ai/knowledge/chat', { |
| | | method: 'POST', |
| | | headers: { 'Content-Type': 'application/json' }, |
| | | body: JSON.stringify({ |
| | | knowledgeBaseId: selectedKbId.value, |
| | | memoryId: memoryId.value, |
| | | question: inputQuestion.value |
| | | }) |
| | | }) |
| | | |
| | | // å¤çSSEæµå¼ååº |
| | | const reader = response.body.getReader() |
| | | const decoder = new TextDecoder() |
| | | let aiContent = '' |
| | | |
| | | messages.value.push({ role: 'assistant', content: '' }) |
| | | |
| | | while (true) { |
| | | const { done, value } = await reader.read() |
| | | if (done) break |
| | | |
| | | const text = decoder.decode(value) |
| | | aiContent += text |
| | | messages.value[messages.value.length - 1].content = aiContent |
| | | } |
| | | } finally { |
| | | loading.value = false |
| | | inputQuestion.value = '' |
| | | } |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | const res = await getKnowledgeBaseList() |
| | | knowledgeBases.value = res.data |
| | | }) |
| | | </script> |
| | | ``` |
| | | |
| | | ### 5.4 APIå°è£
|
| | | |
| | | ```javascript |
| | | // api/knowledge.js |
| | | import request from '@/utils/request' |
| | | |
| | | // è·åç¥è¯åºå表 |
| | | export function getKnowledgeBaseList(params) { |
| | | return request({ |
| | | url: '/knowledgeBase/getList', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢ç¥è¯åº |
| | | export function addKnowledgeBase(data) { |
| | | return request({ |
| | | url: '/knowledgeBase/add', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // æ´æ°ç¥è¯åº |
| | | export function updateKnowledgeBase(data) { |
| | | return request({ |
| | | url: '/knowledgeBase/update', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // å é¤ç¥è¯åº |
| | | export function deleteKnowledgeBase(ids) { |
| | | return request({ |
| | | url: '/knowledgeBase/delete', |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | | |
| | | // è·åæä»¶åéåç¶æ |
| | | export function getVectorStatus(knowledgeBaseId) { |
| | | return request({ |
| | | url: `/knowledgeBase/vector/status/${knowledgeBaseId}`, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // éæ°åéå |
| | | export function reprocessVector(vectorId) { |
| | | return request({ |
| | | url: `/knowledgeBase/vector/reprocess/${vectorId}`, |
| | | method: 'post' |
| | | }) |
| | | } |
| | | |
| | | // ä¿åæä»¶å
³è |
| | | export function saveKnowledgeBaseFiles(data) { |
| | | return request({ |
| | | url: '/knowledgeBase/file/save', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // å 餿件 |
| | | export function deleteKnowledgeBaseFiles(vectorIds) { |
| | | return request({ |
| | | url: '/knowledgeBase/file/delete', |
| | | method: 'delete', |
| | | data: vectorIds |
| | | }) |
| | | } |
| | | |
| | | // ç¥è¯åºé®çï¼æµå¼ï¼ |
| | | export async function knowledgeChat(data) { |
| | | const response = await fetch('/api/ai/knowledge/chat', { |
| | | method: 'POST', |
| | | headers: { 'Content-Type': 'application/json' }, |
| | | body: JSON.stringify(data) |
| | | }) |
| | | return response.body |
| | | } |
| | | |
| | | // è·åç¥è¯åºå表ï¼é®çç¨ï¼ |
| | | export function getKnowledgeBaseListForChat() { |
| | | return request({ |
| | | url: '/ai/knowledge/list', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## å
ãæ ¸å¿æµç¨ |
| | | |
| | | ### 6.1 æä»¶ä¸ä¼ ä¸åéåæµç¨ |
| | | |
| | | ``` |
| | | 1. å端è°ç¨ /common/upload ä¸ä¼ æä»¶ â è¿å storageBlobId |
| | | 2. å端è°ç¨ /knowledgeBase/file/save å
³èæä»¶å°ç¥è¯åº |
| | | 3. å端å建 KnowledgeBaseVector è®°å½ï¼ç¶æï¼å¾
å¤çï¼ |
| | | 4. åç«¯å¼æ¥è°ç¨ KnowledgeRagService.processVectorAsync() |
| | | âââ æ´æ°ç¶æä¸º"å¤çä¸" |
| | | âââ æåæä»¶å
å®¹ï¼æ¯æå¤ç§æ ¼å¼ï¼ |
| | | âââ èªå¨æ£æµæä»¶ç¼ç ï¼UTF-8/GBKï¼ |
| | | âââ ææ¬åçï¼å¤§æä»¶æé¿å
容æåçï¼ |
| | | âââ çæ Embedding åé |
| | | âââ åå¨å° Pinecone |
| | | âââ æ´æ°ç¶æä¸º"宿"æ"失败" |
| | | ``` |
| | | |
| | | ### 6.2 ç¥è¯åºé®çæµç¨ |
| | | |
| | | ``` |
| | | 1. ç¨æ·éæ©ç¥è¯åºï¼è¾å
¥é®é¢ |
| | | 2. å端è°ç¨ /ai/knowledge/chatï¼æµå¼æ¥å£ï¼ |
| | | 3. å端å¤çï¼ |
| | | âââ æå»ºå½å空é´ï¼kb-{knowledgeBaseId} |
| | | âââ è°ç¨ Embedding 模åçæé®é¢åé |
| | | âââ ä» Pinecone æ£ç´¢ç¸å
³å
容ï¼minScore=0.7, maxResults=5ï¼ |
| | | âââ æå»ºä¸ä¸æ Prompt |
| | | âââ è°ç¨ LLM çæåç |
| | | âââ æµå¼è¿åç»æ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ä¸ã注æäºé¡¹ |
| | | |
| | | 1. **Pinecone å½å空é´**ï¼ä¸è½ä½¿ç¨ `__default__`ï¼å¿
须使ç¨èªå®ä¹å½åç©ºé´ |
| | | 2. **æä»¶ç¼ç **ï¼èªå¨æ£æµ UTF-8/GBKï¼é¿å
ä¹±ç |
| | | 3. **åççç¥**ï¼ |
| | | - æä»¶ > 80MB æå
容 > 8000 åç¬¦æ¶æåç |
| | | - åçå¤§å° 500 å符ï¼éå 100 å符 |
| | | - ä¼å
å¨å¥åè¾¹çåå |
| | | 4. **Embedding éå¶**ï¼é¿éäº DashScope éå¶å次è¾å
¥æå¤§ 8192 å符 |
| | | 5. **åéå é¤**ï¼ä½¿ç¨ Pinecone åç客æ·ç«¯ï¼éè¿ metadata filter å é¤ |
| | | 6. **弿¥å¤ç**ï¼åéåä½¿ç¨ `@Async` 弿¥æ§è¡ï¼é¿å
é»å¡æ¥å£ |
| | | |
| | | --- |
| | | |
| | | ## å
«ãæä»¶æ¸
å |
| | | |
| | | ### å端æä»¶ |
| | | ``` |
| | | src/main/java/com/ruoyi/ |
| | | âââ approve/ |
| | | â âââ controller/ |
| | | â â âââ KnowledgeBaseController.java |
| | | â âââ pojo/ |
| | | â â âââ KnowledgeBase.java |
| | | â â âââ KnowledgeBaseVector.java |
| | | â âââ service/ |
| | | â â âââ KnowledgeBaseService.java |
| | | â â âââ KnowledgeBaseVectorService.java |
| | | â â âââ impl/ |
| | | â â âââ KnowledgeBaseServiceImpl.java |
| | | â â âââ KnowledgeBaseVectorServiceImpl.java |
| | | â âââ mapper/ |
| | | â â âââ KnowledgeBaseMapper.java |
| | | â â âââ KnowledgeBaseVectorMapper.java |
| | | â âââ dto/ |
| | | â âââ KnowledgeBaseVectorVO.java |
| | | âââ ai/ |
| | | âââ config/ |
| | | â âââ EmbeddingStoreConfig.java |
| | | â âââ XiaozhiAgentConfig.java |
| | | âââ controller/ |
| | | â âââ KnowledgeChatController.java |
| | | âââ assistant/ |
| | | â âââ KnowledgeChatAgent.java |
| | | âââ service/ |
| | | â âââ KnowledgeRagService.java |
| | | â âââ impl/ |
| | | â âââ KnowledgeRagServiceImpl.java |
| | | âââ dto/ |
| | | âââ KnowledgeChatRequest.java |
| | | ``` |
| | | |
| | | ### å端æä»¶ |
| | | ``` |
| | | src/views/knowledge/ |
| | | âââ index.vue # ç¥è¯åºå表 |
| | | âââ form.vue # æ°å¢/ç¼è¾è¡¨å |
| | | âââ files.vue # æä»¶ç®¡ç |
| | | âââ chat.vue # ç¥è¯åºé®ç |
| | | |
| | | src/api/knowledge.js # APIå°è£
|
| | | ``` |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ°æ®æ¨¡æ - åºç¡æ°æ®æ£æµ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - æ°æ®æ¨¡ææä½é¡µé¢ï¼æ°å¢ï¼ |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | /mock/dataCheck | æ£æµæå®æ¨¡åçåºç¡æ°æ®æ¯å¦å°±ç»ª | |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | modules | List\<String\> | æ¯ | è¦æ£æµç模åå表ï¼å¯éå¼ï¼salesï¼éå®ï¼ãpurchaseï¼éè´ï¼ãqualityï¼è´¨éï¼ãproductionï¼çäº§ï¼ | |
| | | |
| | | 请æ±ä½ç¤ºä¾ï¼ |
| | | ```json |
| | | { "modules": ["sales", "purchase", "quality", "production"] } |
| | | ``` |
| | | |
| | | **ååºï¼** |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": { |
| | | "totalItems": 6, |
| | | "passedItems": 3, |
| | | "items": [ |
| | | { |
| | | "module": "common", |
| | | "itemName": "äº§åæ°æ®", |
| | | "minRequired": 1, |
| | | "currentCount": 5, |
| | | "passed": true, |
| | | "message": "éè¿" |
| | | }, |
| | | { |
| | | "module": "sales", |
| | | "itemName": "å®¢æ·æ°æ®", |
| | | "minRequired": 1, |
| | | "currentCount": 0, |
| | | "passed": false, |
| | | "message": "缺å°å®¢æ·æ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-客æ·ç®¡çã䏿·»å è³å°1æ¡å®¢æ·" |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## æ£æµè§å |
| | | |
| | | ### é宿¨¡å (sales) |
| | | | æ£æµé¡¹ | æä½æ°é | æªéè¿æç¤º | |
| | | |--------|----------|------------| |
| | | | äº§åæ°æ® | 1 | 缺å°äº§åæ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-产å管çã䏿·»å è³å°1æ¡äº§å | |
| | | | å®¢æ·æ°æ® | 1 | 缺å°å®¢æ·æ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-客æ·ç®¡çã䏿·»å è³å°1æ¡å®¢æ· | |
| | | | æ¥ä»·å®¡æ¹æ¨¡æ¿ | 1 | ç¼ºå°æ¥ä»·å®¡æ¹æ¨¡æ¿ï¼è¯·å
å¨ãç³»ç»ç®¡ç-å®¡æ¹æ¨¡æ¿ãä¸å建æ¥ä»·å®¡æ¹æ¨¡æ¿ | |
| | | | åè´§å®¡æ¹æ¨¡æ¿ | 1 | 缺å°åè´§å®¡æ¹æ¨¡æ¿ï¼è¯·å
å¨ãç³»ç»ç®¡ç-å®¡æ¹æ¨¡æ¿ãä¸å建åè´§å®¡æ¹æ¨¡æ¿ | |
| | | |
| | | ### éè´æ¨¡å (purchase) |
| | | | æ£æµé¡¹ | æä½æ°é | æªéè¿æç¤º | |
| | | |--------|----------|------------| |
| | | | äº§åæ°æ® | 1 | 缺å°äº§åæ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-产å管çã䏿·»å è³å°1æ¡äº§å | |
| | | | ä¾åºåæ°æ® | 1 | 缺å°ä¾åºåæ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-ä¾åºå管çã䏿·»å è³å°1æ¡ä¾åºå | |
| | | | éè´å®¡æ¹æ¨¡æ¿ | 1 | 缺å°éè´å®¡æ¹æ¨¡æ¿ï¼è¯·å
å¨ãç³»ç»ç®¡ç-å®¡æ¹æ¨¡æ¿ãä¸å建éè´å®¡æ¹æ¨¡æ¿ | |
| | | |
| | | ### ç产模å (production) |
| | | | æ£æµé¡¹ | æä½æ°é | æªéè¿æç¤º | |
| | | |--------|----------|------------| |
| | | | äº§åæ°æ® | 1 | 缺å°äº§åæ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-产å管çã䏿·»å è³å°1æ¡äº§å | |
| | | | 产åè§æ ¼ | 1 | 缺å°äº§åè§æ ¼ï¼è¯·å
å¨ãåºç¡æ°æ®-产å管çãä¸ä¸ºäº§åæ·»å è§æ ¼åå· | |
| | | | å·¥åº | 1 | 缺å°å·¥åºï¼è¯·å
å¨ãå·¥èºè®¾è®¡-å·¥åºç®¡çãä¸åå»ºå·¥åº | |
| | | | BOM | 1 | 缺å°BOMï¼è¯·å
å¨ãå·¥èºè®¾è®¡-BOM管çãä¸å建BOM | |
| | | | BOM产åç»æ | 1 | 缺å°BOM产åç»æï¼è¯·å
å¨ãå·¥èºè®¾è®¡-BOM管çãä¸ä¸ºBOMæ·»å 产åç»æèç¹ | |
| | | | å·¥èºè·¯çº¿ | 1 | 缺å°å·¥èºè·¯çº¿ï¼è¯·å
å¨ãå·¥èºè®¾è®¡-å·¥èºè·¯çº¿ãä¸å建工èºè·¯çº¿ | |
| | | | å·¥èºè·¯çº¿å·¥åº | 1 | 缺å°å·¥èºè·¯çº¿å·¥åºï¼è¯·å
å¨ãå·¥èºè®¾è®¡-å·¥èºè·¯çº¿ãä¸ä¸ºå·¥èºè·¯çº¿æ·»å å·¥åº | |
| | | | 产å-å·¥èºè·¯çº¿å
³è | â¥1 | é¨å产åè§æ ¼æªå
³èå·¥èºè·¯çº¿ï¼è¯·å
å¨ãå·¥èºè®¾è®¡-å·¥èºè·¯çº¿ãä¸ä¸ºäº§åè§æ ¼å建工èºè·¯çº¿ | |
| | | | å·¥èºè·¯çº¿-å·¥åºå
³è | â¥1 | é¨åå·¥èºè·¯çº¿æªæ·»å å·¥åºï¼è¯·å
å¨ãå·¥èºè®¾è®¡-å·¥èºè·¯çº¿ãä¸ä¸ºå·¥èºè·¯çº¿æ·»å å·¥åº | |
| | | | BOM-产åç»æå
³è | â¥1 | é¨åBOMæªæ·»å 产åç»æï¼è¯·å
å¨ãå·¥èºè®¾è®¡-BOM管çãä¸ä¸ºBOMæ·»å 产åç»æèç¹ | |
| | | |
| | | ### è´¨éæ¨¡å (quality) |
| | | | æ£æµé¡¹ | æä½æ°é | æªéè¿æç¤º | |
| | | |--------|----------|------------| |
| | | | äº§åæ°æ® | 1 | 缺å°äº§åæ°æ®ï¼è¯·å
å¨ãåºç¡æ°æ®-产å管çã䏿·»å è³å°1æ¡äº§å | |
| | | | æ£æµæ å | 1 | ç¼ºå°æ£æµæ åï¼è¯·å
å¨ãè´¨é管ç-æ£æµæ åãä¸åå»ºæ£æµæ å | |
| | | | ææ ç»å® | 1 | ç¼ºå°ææ ç»å®ï¼è¯·å
å¨ãè´¨é管ç-æ£æµæ åç»å®ãä¸å°æ£æµæ åä¸äº§åç»å® | |
| | | |
| | | ## å端修æ¹ç¹ |
| | | |
| | | ### 1. æ°æ®æ¨¡æé¡µé¢å
¥å£ |
| | | |
| | | å¨å·¦ä¾§èåæ°å¢"æ°æ®æ¨¡æ"èå项ï¼è·¯ç± `/mock`ã |
| | | |
| | | ### 2. æ°æ®æ£æµåºå |
| | | |
| | | ```html |
| | | <template> |
| | | <div class="mock-container"> |
| | | <el-card header="åºç¡æ°æ®æ£æµ"> |
| | | <el-checkbox-group v-model="selectedModules"> |
| | | <el-checkbox label="sales">é宿¨¡å</el-checkbox> |
| | | <el-checkbox label="purchase">éè´æ¨¡å</el-checkbox> |
| | | <el-checkbox label="quality">è´¨éæ¨¡å</el-checkbox> |
| | | <el-checkbox label="production">ç产模å</el-checkbox> |
| | | </el-checkbox-group> |
| | | <el-button type="primary" @click="handleCheck" :loading="checking"> |
| | | å¼å§æ£æµ |
| | | </el-button> |
| | | </el-card> |
| | | |
| | | <el-card v-if="checkResult" header="æ£æµç»æ" style="margin-top: 16px"> |
| | | <el-alert |
| | | :title="`éè¿ ${checkResult.passedItems} / ${checkResult.totalItems} 项`" |
| | | :type="checkResult.passedItems === checkResult.totalItems ? 'success' : 'warning'" |
| | | :closable="false" |
| | | show-icon |
| | | /> |
| | | <el-table :data="checkResult.items" style="margin-top: 12px"> |
| | | <el-table-column prop="module" label="模å" width="100" /> |
| | | <el-table-column prop="itemName" label="æ£æµé¡¹" width="160" /> |
| | | <el-table-column prop="minRequired" label="æä½è¦æ±" width="80" /> |
| | | <el-table-column prop="currentCount" label="å½åæ°é" width="80" /> |
| | | <el-table-column label="ç¶æ" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.passed ? 'success' : 'danger'"> |
| | | {{ row.passed ? 'éè¿' : 'æªéè¿' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="message" label="æç¤º" /> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | ``` |
| | | |
| | | ### 3. data æ°æ® |
| | | |
| | | ```js |
| | | data() { |
| | | return { |
| | | selectedModules: ['sales', 'purchase', 'quality', 'production'], |
| | | checking: false, |
| | | checkResult: null, |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 4. æ¹æ³ |
| | | |
| | | ```js |
| | | import request from '@/utils/request' |
| | | |
| | | methods: { |
| | | async handleCheck() { |
| | | if (this.selectedModules.length === 0) { |
| | | this.$message.warning('请è³å°éæ©ä¸ä¸ªæ¨¡å') |
| | | return |
| | | } |
| | | this.checking = true |
| | | try { |
| | | const res = await request.post('/mock/dataCheck', { |
| | | modules: this.selectedModules |
| | | }) |
| | | this.checkResult = res.data |
| | | } finally { |
| | | this.checking = false |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - äº§åæ°æ®ï¼å
Œ
±åºç¡ï¼å¨å¤ä¸ªæ¨¡åé´å
±äº«ï¼æ£æµç»æä¸åªåºç°ä¸æ¬¡ï¼ä¸ä¼éå¤å±ç¤º |
| | | - æ£æµä»
ååªè¯»æ¥è¯¢ï¼ä¸åå
¥ä»»ä½æ°æ® |
| | | - 模ååæ°ä¸ºç©ºæ°ç»æ¶è¿åç©ºæ£æµå表 |
| | | - å»ºè®®å¨æ°æ®æ¨¡æå¼å§åå
è°ç¨æ¤æ¥å£ç¡®è®¤åºç¡æ°æ®å°±ç»ª |
| | | - ç产模åé¤äºæ£æµæ°æ®éï¼è¿ä¼æ£æµå
³èæ§ï¼äº§å-å·¥èºè·¯çº¿ãå·¥èºè·¯çº¿-å·¥åºãBOM-产åç»æï¼ï¼ç¡®ä¿å·¥èºè®¾è®¡æ°æ®å®æ´ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ°æ®æ¨¡æ - AI æ°æ®çæ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - æ°æ®æ¨¡ææä½é¡µé¢ï¼æ°å¢/ä¿®æ¹ï¼ |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | /mock/generate | AI çææ¨¡ææ°æ®ï¼å«åç½®åºç¡æ°æ®æ£æµï¼ | |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | modules | List\<String\> | æ¯ | è¦çææ°æ®ç模åï¼salesï¼éå®ï¼ãpurchaseï¼éè´ï¼ãqualityï¼è´¨éï¼ãproductionï¼ç产ï¼ãstockï¼åºåï¼ | |
| | | | industries | List\<String\> | æ¯ | è¡ä¸ï¼å¦ï¼["æºæ¢°å¶é ", "é£åå å·¥", "çµåè£
é
"] | |
| | | | countMin | int | å¦ | æ¯ä¸ªå®ä½çææ¡æ°æå°å¼ï¼é»è®¤ 3 | |
| | | | countMax | int | å¦ | æ¯ä¸ªå®ä½çææ¡æ°æå¤§å¼ï¼é»è®¤ 10 | |
| | | | dateStart | String | å¦ | æ¶é´èå´å¼å§ï¼yyyy-MM-ddï¼ | |
| | | | dateEnd | String | å¦ | æ¶é´èå´ç»æï¼yyyy-MM-ddï¼ | |
| | | | additionalInfo | String | å¦ | å
¶ä»è¡¥å
æè¿°ï¼å¦"产å以éå±é¶é¨ä»¶ä¸ºä¸»" | |
| | | |
| | | 请æ±ä½ç¤ºä¾ï¼ |
| | | ```json |
| | | { |
| | | "modules": ["sales", "purchase", "quality"], |
| | | "industries": ["æºæ¢°å¶é ", "çµåè£
é
"], |
| | | "countMin": 3, |
| | | "countMax": 8, |
| | | "dateStart": "2026-01-01", |
| | | "dateEnd": "2026-06-01", |
| | | "additionalInfo": "产å以éå±é¶é¨ä»¶åçµåå
å¨ä»¶ä¸ºä¸»" |
| | | } |
| | | ``` |
| | | |
| | | **ååºï¼æåï¼ï¼** |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": { |
| | | "status": "SUCCESS", |
| | | "totalGenerated": 24, |
| | | "moduleSummaries": [ |
| | | { "module": "sales", "entityName": "客æ·", "generatedCount": 5, "successCount": 5, "failCount": 0 }, |
| | | { "module": "sales", "entityName": "éå®å°è´¦", "generatedCount": 5, "successCount": 5, "failCount": 0 }, |
| | | { "module": "purchase", "entityName": "ä¾åºå", "generatedCount": 4, "successCount": 4, "failCount": 0 }, |
| | | { "module": "purchase", "entityName": "éè´å°è´¦", "generatedCount": 4, "successCount": 4, "failCount": 0 }, |
| | | { "module": "quality", "entityName": "æ£æµæ å", "generatedCount": 3, "successCount": 3, "failCount": 0 }, |
| | | { "module": "quality", "entityName": "ææ ç»å®", "generatedCount": 3, "successCount": 3, "failCount": 0 } |
| | | ], |
| | | "errors": [], |
| | | "checkResult": null |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **ååºï¼åºç¡æ°æ®ä¸è¶³ï¼åç½®æ£æµæªéè¿ï¼ï¼** |
| | | ```json |
| | | { |
| | | "code": 500, |
| | | "msg": "åºç¡æ°æ®ä¸è¶³ï¼è¯·å
è¡¥å
ååçæ", |
| | | "data": { |
| | | "status": "CHECK_FAILED", |
| | | "checkResult": { |
| | | "totalItems": 6, |
| | | "passedItems": 3, |
| | | "items": [ |
| | | { "module": "common", "itemName": "äº§åæ°æ®", "passed": true, "message": "éè¿" }, |
| | | { "module": "sales", "itemName": "å®¢æ·æ°æ®", "passed": false, "message": "缺å°å®¢æ·æ°æ®ï¼è¯·å
..." } |
| | | ] |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## 工使µç¨ |
| | | |
| | | 1. ç¨æ·å¨é¡µé¢éæ©æ¨¡å + å¡«åè¡ä¸/æ°é/æ¶é´çä¿¡æ¯ |
| | | 2. è°ç¨ `POST /mock/generate` |
| | | 3. å端é¦å
è°ç¨ `POST /mock/dataCheck` æ£æµåºç¡æ°æ®æ¯å¦å°±ç»ª |
| | | 4. è¥åºç¡æ°æ®ä¸è¶³ï¼è¿åæ£æµç»æï¼æç¤ºç¨æ·å
è¡¥é½ |
| | | 5. è¥åºç¡æ°æ®å°±ç»ªï¼è°ç¨ AI 大模åçæç¬¦åè¡ä¸ç¹å¾ç JSON æ°æ® |
| | | 6. æä¾èµé¡ºåºåå»ºæ°æ®ï¼å®¢æ·âéå®å°è´¦ãä¾åºåâéè´å°è´¦ãæ£æµæ åâææ ç»å®...ï¼ |
| | | 7. è¿åçææè¦ |
| | | |
| | | ## å端修æ¹ç¹ |
| | | |
| | | ### 1. æ°æ®çæåºåï¼å¨æ°æ®æ£æµåºå䏿¹ï¼ |
| | | |
| | | ```html |
| | | <el-card header="AI æ°æ®çæ" style="margin-top: 16px"> |
| | | <el-form :model="generateForm" label-width="100px"> |
| | | <el-form-item label="éæ©æ¨¡å"> |
| | | <el-checkbox-group v-model="generateForm.modules"> |
| | | <el-checkbox label="sales">éå®</el-checkbox> |
| | | <el-checkbox label="purchase">éè´</el-checkbox> |
| | | <el-checkbox label="quality">è´¨é</el-checkbox> |
| | | <el-checkbox label="production">ç产</el-checkbox> |
| | | <el-checkbox label="stock">åºå</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="è¡ä¸"> |
| | | <el-select |
| | | v-model="generateForm.industries" |
| | | multiple |
| | | filterable |
| | | allow-create |
| | | placeholder="è¾å
¥æéæ©è¡ä¸" |
| | | > |
| | | <el-option label="æºæ¢°å¶é " value="æºæ¢°å¶é " /> |
| | | <el-option label="é£åå å·¥" value="é£åå å·¥" /> |
| | | <el-option label="çµåè£
é
" value="çµåè£
é
" /> |
| | | <el-option label="汽车é¶é¨ä»¶" value="汽车é¶é¨ä»¶" /> |
| | | <el-option label="å»ç卿¢°" value="å»ç卿¢°" /> |
| | | <el-option label="åå·¥ææ" value="åå·¥ææ" /> |
| | | <el-option label="çººç»æè£
" value="çººç»æè£
" /> |
| | | <el-option label="å®¶å
·å¶é " value="å®¶å
·å¶é " /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-row :gutter="16"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çææ¡æ°"> |
| | | <el-slider |
| | | v-model="generateForm.countRange" |
| | | range |
| | | :min="1" |
| | | :max="50" |
| | | :marks="{1:'1', 10:'10', 20:'20', 50:'50'}" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | v-model="generateForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="yyyy-MM-dd" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="è¡¥å
ä¿¡æ¯"> |
| | | <el-input |
| | | v-model="generateForm.additionalInfo" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="å¦ï¼äº§å以éå±é¶é¨ä»¶ä¸ºä¸»ï¼å®¢æ·éä¸å¨åä¸å°åº" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleGenerate" :loading="generating"> |
| | | å¼å§çæ |
| | | </el-button> |
| | | <el-button @click="handleCheckFirst" :loading="checking"> |
| | | å
æ£æµåçæ |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | ``` |
| | | |
| | | ### 3. data æ°æ® |
| | | |
| | | ```js |
| | | data() { |
| | | return { |
| | | generateForm: { |
| | | modules: [], |
| | | industries: [], |
| | | countRange: [3, 10], |
| | | dateRange: [], |
| | | additionalInfo: '', |
| | | }, |
| | | generating: false, |
| | | generateResult: null, |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 4. æ¹æ³ |
| | | |
| | | ```js |
| | | methods: { |
| | | // ç´æ¥çæï¼å
é¨èªå¨æ£æµï¼ |
| | | async handleGenerate() { |
| | | if (this.generateForm.modules.length === 0) { |
| | | this.$message.warning('请è³å°éæ©ä¸ä¸ªæ¨¡å') |
| | | return |
| | | } |
| | | this.generating = true |
| | | try { |
| | | const res = await request.post('/mock/generate', { |
| | | modules: this.generateForm.modules, |
| | | industries: this.generateForm.industries, |
| | | countMin: this.generateForm.countRange[0], |
| | | countMax: this.generateForm.countRange[1], |
| | | dateStart: this.generateForm.dateRange[0] || null, |
| | | dateEnd: this.generateForm.dateRange[1] || null, |
| | | additionalInfo: this.generateForm.additionalInfo, |
| | | }) |
| | | if (res.code === 200) { |
| | | this.generateResult = res.data |
| | | this.$message.success(`æåçæ ${res.data.totalGenerated} æ¡æ°æ®`) |
| | | } else { |
| | | // åºç¡æ°æ®ä¸è¶³ |
| | | this.generateResult = res.data |
| | | this.checkResult = res.data.checkResult |
| | | this.$message.warning(res.msg) |
| | | } |
| | | } finally { |
| | | this.generating = false |
| | | } |
| | | }, |
| | | |
| | | // å
æ£æµåçæ |
| | | async handleCheckFirst() { |
| | | await this.handleCheck() |
| | | if (this.checkResult && this.checkResult.passedItems === this.checkResult.totalItems) { |
| | | await this.handleGenerate() |
| | | } else { |
| | | this.$message.warning('请å
è¡¥é½åºç¡æ°æ®ååçæ') |
| | | } |
| | | }, |
| | | } |
| | | ``` |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - `POST /mock/generate` å
é¨ä¼èªå¨è°ç¨æ°æ®æ£æµï¼ä¸éè¦æå¨å两æ¥èµ° |
| | | - AI çæçæ°æ®ä¼å°½é符åè¡ä¸ç¹å¾ï¼ä½å»ºè®®çæåæ£æ¥å
³é®æ°æ®çåçæ§ |
| | | - ç产模åååºå模åä¾èµäº§åæ°æ®ï¼Product/ProductModelï¼ï¼è¯·ç¡®ä¿åºç¡æ°æ®ä¸æäº§å |
| | | - éå®å°è´¦çååéé¢ç±äº§åæç»èªå¨è®¡ç®ï¼AI åªéçæäº§åè¡çåä»·åæ°é |
| | | - 产ççæ°æ®éè¿æå¡å±å建ï¼ä¼èªå¨å¡«å
tenantId/deptId/createUser çåæ®µ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # çäº§å·¥åºæ°å¢è®¡å人ååè®¡åæ§è¡äººå |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - å·¥èºè·¯çº¿å·¥åºç®¡ç页é¢ï¼technology_routing_operationï¼ |
| | | |
| | | ## API |
| | | |
| | | æ¶å以䏿¥å£ç请æ±/ååºæ°å¢åæ®µï¼ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | GET | /technologyRoutingOperation/page | å页æ¥è¯¢ | |
| | | | GET | /technologyRoutingOperation/list | å表æ¥è¯¢ | |
| | | | GET | /technologyRoutingOperation/{id} | 详æ
| |
| | | | POST | /technologyRoutingOperation/add | æ°å¢ | |
| | | | PUT | /technologyRoutingOperation | ä¿®æ¹ | |
| | | |
| | | **æ°å¢ååº/请æ±å段ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | planPerson | Long | å¦ | 计å人åID | |
| | | | executor | Long | å¦ | è®¡åæ§è¡äººåID | |
| | | | planPersonName | String | å¦ | 计å人åå§åï¼ååºå段ï¼å端èªå¨å
³èæ¥è¯¢ï¼ | |
| | | | executorName | String | å¦ | è®¡åæ§è¡äººåå§åï¼ååºå段ï¼å端èªå¨å
³èæ¥è¯¢ï¼ | |
| | | |
| | | ## å端修æ¹ç¹ |
| | | |
| | | ### 1. è¡¨åæ°å¢å段 |
| | | |
| | | å¨å·¥åºè¡¨å䏿°å¢ä¸¤ä¸ªäººå鿩䏿æ¡ï¼ |
| | | |
| | | ```html |
| | | <el-form-item label="计å人å" prop="planPerson"> |
| | | <el-select v-model="processForm.planPerson" |
| | | placeholder="è¯·éæ©è®¡å人å" |
| | | clearable |
| | | filterable |
| | | style="width: 100%"> |
| | | <el-option v-for="item in employeeOptions" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è®¡åæ§è¡äººå" prop="executor"> |
| | | <el-select v-model="processForm.executor" |
| | | placeholder="è¯·éæ©è®¡åæ§è¡äººå" |
| | | clearable |
| | | filterable |
| | | style="width: 100%"> |
| | | <el-option v-for="item in employeeOptions" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | ``` |
| | | |
| | | ### 2. data æ°æ® |
| | | |
| | | ```js |
| | | data() { |
| | | return { |
| | | processForm: { |
| | | planPerson: null, |
| | | executor: null, |
| | | // ...å
¶ä»å段 |
| | | }, |
| | | employeeOptions: [], // åå·¥å表ï¼é仿¥å£è·å |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 3. è¡¨æ ¼åæ°å¢ |
| | | |
| | | å表ä¸å¯ç´æ¥ä½¿ç¨å端è¿åçå§åå±ç¤ºï¼ |
| | | |
| | | ```html |
| | | <el-table-column label="计å人å" prop="planPersonName" /> |
| | | <el-table-column label="è®¡åæ§è¡äººå" prop="executorName" /> |
| | | ``` |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - `planPerson` å `executor` åå¨çæ¯åå·¥IDï¼Longç±»åï¼ï¼æäº¤è¡¨åæ¶ä¼ ID |
| | | - ååºä¸ `planPersonName` å `executorName` ç±å端éè¿ `staff_on_job` 表èªå¨å
³èæ¥è¯¢ï¼å端æ éé¢å¤è½¬æ¢ |
| | | - å工䏿åè¡¨æ°æ®éä»åå·¥æ¥å£è·åï¼åèç°æ `employeeOptions` æ°æ®æºï¼ |
| | | - æ°æ®åºè¿ç§»SQLï¼`doc/20260615_add_plan_person_and_executor_to_routing_operation.sql` |
| | | - ç产订åå·¥åºï¼`production_order_routing_operation`ï¼åæ¥æ¯æè¿ä¸¤ä¸ªå段 |
| | |
| | | // æ¼ç¤ºä¾åï¼æ§è¡ main æ¹æ³æ§å¶å°è¾å
¥æ¨¡å表åå车èªå¨çæå¯¹åºé¡¹ç®ç®å½ä¸ |
| | | public class CodeGenerator { |
| | | |
| | | public static String database_url = "jdbc:mysql://localhost:3300/product-inventory-management-new-pro"; |
| | | public static String database_url = "jdbc:mysql://localhost:3306/product-inventory-management-new-pro"; |
| | | public static String database_username = "root"; |
| | | public static String database_password= "root"; |
| | | public static String database_password= "123456"; |
| | | public static String author = "è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸"; |
| | | public static String model = "sales"; // 模å |
| | | public static String model = "account"; // 模å |
| | | public static String setParent = "com.ruoyi."+ model; // å
è·¯å¾ |
| | | public static String tablePrefix = ""; // è®¾ç½®è¿æ»¤è¡¨åç¼ |
| | | public static void main(String[] args) { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | |
| | | @Data |
| | | @Schema(name = "AccountReportDto", description = "è´¢å¡æ¥è¡¨--æ¥æåæ°") |
| | | public class AccountReportDto { |
| | | |
| | | /** |
| | | * å¼å§æ¶é´ |
| | | */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate entryDateStart; |
| | | |
| | | /** |
| | | * ç»ææ¶é´ |
| | | */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate entryDateEnd; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** |
| | | * @author :yys |
| | | * @date : 2026/1/17 13:42 |
| | | */ |
| | | @Data |
| | | public class DeviceTypeDetail { |
| | | |
| | | private String type; // 设å¤ç±»å |
| | | private BigDecimal count; // æ°é |
| | | private BigDecimal amount; // ä»·æ ¼ |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author :yys |
| | | * @date : 2026/1/17 13:19 |
| | | */ |
| | | @Data |
| | | public class DeviceTypeDistributionVO { |
| | | |
| | | private Integer totalCount = 0; // 设å¤ç±»åæ»æ° |
| | | |
| | | // 饼å¾åæçº¿å¾çåç±»ï¼è®¾å¤ç±»åï¼ |
| | | private List<String> categories = new ArrayList<>(); |
| | | // åç±»å设å¤çæ°é |
| | | private List<BigDecimal> countData = new ArrayList<>(); |
| | | // åç±»å设å¤çæ»éé¢ |
| | | private List<BigDecimal> amountData = new ArrayList<>(); |
| | | // æç»å表ï¼å
å«ç±»åãæ°éãéé¢ |
| | | private List<DeviceTypeDetail> details = new ArrayList<>(); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import com.ruoyi.account.pojo.SalesReceiptReturn; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class SalesReceiptReturnDto extends SalesReceiptReturn { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import com.ruoyi.account.pojo.SalesRefundAmountOrder; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class SalesRefundAmountOrderDto extends SalesRefundAmountOrder { |
| | | |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnManagementNo; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "éå®åå·") |
| | | private String salesContractNo; |
| | | |
| | | @Schema(description = "å建人åç§°") |
| | | private String createUserName; |
| | | |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "StatementAccountDto", description = "è´¢å¡ç®¡ç--çæå¯¹è´¦å(ä¼ å)") |
| | | public class StatementAccountDto { |
| | | |
| | | //ä¸å¡ç±»å(1åºæ¶å¯¹è´¦;2åºä»å¯¹è´¦) |
| | | @Schema(name = "accountType", description = "ä¸å¡ç±»å(1åºæ¶å¯¹è´¦;2åºä»å¯¹è´¦)") |
| | | private Integer accountType; |
| | | |
| | | //éæ©ç客æ·(åºæ¶æ¯å®¢æ·,åºä»æ¯ä¾åºåsupplierId) |
| | | @Schema(name = "customerId", description = "客æ·ID") |
| | | private Long customerId; |
| | | |
| | | //对账æä»½yyyy-MM |
| | | @Schema(name = "statementMonth", description = "对账æä»½") |
| | | private String statementMonth; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.AccountSubject; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class AccountSubjectDto extends AccountSubject { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class AccountSubjectImportDto { |
| | | |
| | | @Schema(description = "ç§ç®ç¼ç ") |
| | | @Excel(name = "ç§ç®ç¼ç ") |
| | | private String subjectCode; |
| | | |
| | | @Schema(description = "ç§ç®åç§°") |
| | | @Excel(name = "ç§ç®åç§°") |
| | | private String subjectName; |
| | | |
| | | @Schema(description = "ç§ç®ç±»å") |
| | | @Excel(name = "ç§ç®ç±»å") |
| | | private String subjectType; |
| | | |
| | | @Schema(description = "ä½é¢æ¹å") |
| | | @Excel(name = "ä½é¢æ¹å") |
| | | private String balanceDirection; |
| | | |
| | | /** |
| | | * ç¶æ 0å¯ç¨ 1ç¦ç¨ |
| | | */ |
| | | @Schema(description = "ç¶æ") |
| | | @Excel(name = "ç¶æ",readConverterExp = "0=å¯ç¨,1=ç¦ç¨") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | /** |
| | | * ç§ç®æç»è´¦æ¥è¯¢åæ°ï¼å«è¾
婿 ¸ç®æ¡ä»¶ï¼ã |
| | | */ |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class FinDetailLedgerQueryDto extends FinLedgerQueryDto { |
| | | |
| | | /** |
| | | * è¾
婿 ¸ç®ç±»åï¼customer/supplier/department/employee/projectã |
| | | */ |
| | | private String auxiliaryType; |
| | | |
| | | /** |
| | | * è¾
婿 ¸ç®å¯¹è±¡IDã |
| | | */ |
| | | private String auxiliaryId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.FinFixedAsset; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | /** |
| | | * åºå®èµäº§æ¥è¯¢ä¸ä¿å DTOã |
| | | */ |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class FinFixedAssetDto extends FinFixedAsset { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ¹éID请æ±åæ°ã |
| | | */ |
| | | @Data |
| | | public class FinIdBatchDto { |
| | | |
| | | /** |
| | | * IDéåã |
| | | */ |
| | | private List<Long> ids; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.FinIntangibleAsset; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | /** |
| | | * æ å½¢èµäº§æ¥è¯¢ä¸ä¿å DTOã |
| | | */ |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class FinIntangibleAssetDto extends FinIntangibleAsset { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * ç§ç®è´¦æ¥è¯¢åæ°ã |
| | | */ |
| | | @Data |
| | | public class FinLedgerQueryDto { |
| | | |
| | | /** |
| | | * ç§ç®ç¼ç ï¼æ¯ææ«çº§ææå®ç§ç®ï¼ã |
| | | */ |
| | | private String subjectCode; |
| | | |
| | | /** |
| | | * å¼å§æä»½ï¼æ ¼å¼ï¼YYYY-MMã |
| | | */ |
| | | private String startMonth; |
| | | |
| | | /** |
| | | * ç»ææä»½ï¼æ ¼å¼ï¼YYYY-MMã |
| | | */ |
| | | private String endMonth; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.FinVoucher; |
| | | import com.ruoyi.basic.dto.StorageBlobDTO; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * åè¯ä¿å DTOï¼ä¸»è¡¨ + åå½ï¼ã |
| | | */ |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class FinVoucherDto extends FinVoucher { |
| | | |
| | | /** |
| | | * åè¯æç»åå½ã |
| | | */ |
| | | private List<FinVoucherEntryDto> entries; |
| | | |
| | | private List<StorageBlobDTO> storageBlobDTOs; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.FinVoucherEntry; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | /** |
| | | * åè¯åå½ DTOã |
| | | */ |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class FinVoucherEntryDto extends FinVoucherEntry { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | /** |
| | | * åè¯å页æ¥è¯¢åæ°ã |
| | | */ |
| | | @Data |
| | | public class FinVoucherPageDto { |
| | | |
| | | /** |
| | | * åè¯åå·ï¼æ¨¡ç³å¹é
ï¼ã |
| | | */ |
| | | private String voucherNo; |
| | | |
| | | /** |
| | | * å¶å人ã |
| | | */ |
| | | private String creator; |
| | | |
| | | /** |
| | | * åè¯ç¶æã |
| | | */ |
| | | private String status; |
| | | |
| | | /** |
| | | * å¼å§æ¥æï¼å«ï¼ã |
| | | */ |
| | | private LocalDate startDate; |
| | | |
| | | /** |
| | | * ç»ææ¥æï¼å«ï¼ã |
| | | */ |
| | | private LocalDate endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.financial; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * åè¯ç¶æåæ´åæ°ã |
| | | */ |
| | | @Data |
| | | public class FinVoucherStatusDto { |
| | | |
| | | /** |
| | | * åè¯IDã |
| | | */ |
| | | private Long id; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.purchase; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountPaymentApplicationDto", description = "è´¢å¡ç®¡ç--仿¬¾ç³è¯·å°è´¦(ä¼ å)") |
| | | public class AccountPaymentApplicationDto { |
| | | |
| | | @Schema(description = "ä¾åºåID") |
| | | private Integer supplierId; |
| | | |
| | | @Schema(description = "ç³è¯·åå·") |
| | | private String invoiceApplicationNo; |
| | | |
| | | @Schema(description = "å®¡æ ¸ç¶æ:0å¾
å®¡æ ¸1å®¡æ ¸éè¿2å®¡æ ¸ä¸éè¿") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.purchase; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountPurchaseInvoiceDto", description = "è´¢å¡ç®¡ç--è¿é¡¹å票å°è´¦(ä¼ å)") |
| | | public class AccountPurchaseInvoiceDto { |
| | | |
| | | @Schema(description = "ä¾åºåID") |
| | | private Integer supplierId; |
| | | |
| | | @Schema(description = "å票å·ç ") |
| | | private String invoiceNumber; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | |
| | | @Schema(description = "ç¶æ") |
| | | private Integer status; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.purchase; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountPurchasePaymentDto", description = "è´¢å¡ç®¡ç--仿¬¾åå°è´¦(ä¼ å)") |
| | | public class AccountPurchasePaymentDto { |
| | | |
| | | @Schema(description = "ä¾åºåID") |
| | | private Integer supplierId; |
| | | |
| | | @Schema(description = "仿¬¾åå·") |
| | | private String paymentNumber; |
| | | |
| | | @Schema(description = "仿¬¾æ¹å¼") |
| | | private String paymentMethod; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.purchase; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "PurchaseInboundDto", description = "è´¢å¡ç®¡ç--éè´å
¥åºå°è´¦(ä¼ å)") |
| | | public class PurchaseInboundDto { |
| | | |
| | | @Schema(description = "å
¥åºåå·") |
| | | private String inboundBatches; |
| | | |
| | | private Long supplierId; |
| | | |
| | | @Schema(description = "ä¾åºå") |
| | | private String supplierName; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.purchase; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "PurchaseReturnDto", description = "è´¢å¡ç®¡ç--éè´éè´§å°è´¦(ä¼ å)") |
| | | public class PurchaseReturnDto { |
| | | |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnNo; |
| | | |
| | | private Long supplierId; |
| | | |
| | | @Schema(description = "ä¾åºå") |
| | | private String supplierName; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.sales; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountInvoiceApplicationDto", description = "è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯·å°è´¦(ä¼ å)") |
| | | public class AccountInvoiceApplicationDto { |
| | | |
| | | @Schema(description = "客æ·ID") |
| | | private Integer customerId; |
| | | |
| | | @Schema(description = "ç³è¯·åå·") |
| | | private String invoiceApplicationNo; |
| | | |
| | | @Schema(description = "å®¡æ ¸ç¶æ:0å¾
å®¡æ ¸1å®¡æ ¸éè¿2å®¡æ ¸ä¸éè¿") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.sales; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountSalesCollectionDto", description = "è´¢å¡ç®¡ç--æ¶æ¬¾åå°è´¦(ä¼ å)") |
| | | public class AccountSalesCollectionDto { |
| | | |
| | | @Schema(description = "客æ·ID") |
| | | private Integer customerId; |
| | | |
| | | @Schema(description = "æ¶æ¬¾åå·") |
| | | private String collectionNumber; |
| | | |
| | | @Schema(description = "æ¶æ¬¾æ¹å¼") |
| | | private String collectionMethod; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.sales; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountSalesInvoiceDto", description = "è´¢å¡ç®¡ç--é项å票å°è´¦(ä¼ å)") |
| | | public class AccountSalesInvoiceDto { |
| | | |
| | | @Schema(description = "客æ·ID") |
| | | private Integer customerId; |
| | | |
| | | @Schema(description = "å票å·ç ") |
| | | private String invoiceNumber; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | |
| | | @Schema(description = "ç¶æ") |
| | | private Integer status; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.sales; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesOutboundDto", description = "è´¢å¡ç®¡ç--éå®åºåºå°è´¦(ä¼ å)") |
| | | public class SalesOutboundDto { |
| | | |
| | | @Schema(description = "åºåºåå·") |
| | | private String outboundBatches; |
| | | |
| | | private Long customerId; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto.sales; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesReturnDto", description = "è´¢å¡ç®¡ç--éå®éè´§å°è´¦(ä¼ å)") |
| | | public class SalesReturnDto { |
| | | |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnNo; |
| | | |
| | | private Long customerId; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | |
| | | @Data |
| | | @Schema(name = "AccountReportVo", description = "è´¢å¡æ¥è¡¨--è¿ååæ°") |
| | | public class AccountReportVo { |
| | | |
| | | @Schema(description = "æ»è¥æ¶") |
| | | private BigDecimal totalIncome; |
| | | |
| | | @Schema(description = "æ»æ¯åº") |
| | | private BigDecimal totalExpense; |
| | | |
| | | @Schema(description = "åºæ¶è´¦æ¬¾") |
| | | private BigDecimal accountsReceivable; |
| | | |
| | | @Schema(description = "åºä»è´¦æ¬¾") |
| | | private BigDecimal accountsPayable; |
| | | |
| | | @Schema(description = "åæ¶å
¥") |
| | | private BigDecimal netRevenue; |
| | | |
| | | // --- æçº¿å¾ï¼æåº¦è¶å¿æ°æ® --- |
| | | @Schema(description = "æåº¦è¶å¿æ°æ®å表") |
| | | private List<MonthlyTrendVO> monthlyTrendList; |
| | | |
| | | // --- æ±ç¶å¾ï¼åºæ¶åºä»æåº¦æ°æ® --- |
| | | @Schema(description = "åºæ¶åºä»æåº¦æ°æ®å表") |
| | | private List<ReceivablePayableVO> receivablePayableList; |
| | | |
| | | @Data |
| | | @Schema(description = "æåº¦è¶å¿VOï¼æçº¿å¾ç¨ï¼") |
| | | public static class MonthlyTrendVO { |
| | | @Schema(description = "æä»½ï¼æ ¼å¼ï¼yyyy-MM") |
| | | private String month; |
| | | |
| | | @Schema(description = "å½æè¥æ¶") |
| | | private BigDecimal income; |
| | | |
| | | @Schema(description = "彿æ¯åº") |
| | | private BigDecimal expense; |
| | | |
| | | @Schema(description = "彿å婿¶¦") |
| | | private BigDecimal profit; |
| | | } |
| | | |
| | | @Data |
| | | @Schema(description = "åºæ¶åºä»æåº¦VOï¼æ±ç¶å¾ç¨ï¼") |
| | | public static class ReceivablePayableVO { |
| | | @Schema(description = "æä»½ï¼æ ¼å¼ï¼yyyy-MM") |
| | | private String month; |
| | | |
| | | @Schema(description = "åºæ¶è´¦æ¬¾éé¢") |
| | | private BigDecimal receivable; |
| | | |
| | | @Schema(description = "åºä»è´¦æ¬¾éé¢") |
| | | private BigDecimal payable; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.AccountStatement; |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @Schema(name = "StatementAccountVo", description = "è´¢å¡ç®¡ç--对账å详æ
(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class StatementAccountVo extends AccountStatement { |
| | | |
| | | //客æ·åç§°(åºæ¶æ¯å®¢æ·,åºä»æ¯ä¾åºå) |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | //对账æç» |
| | | @Schema(description = "对账æç»") |
| | | private List<AccountStatementDetails> accountStatementDetails; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.AccountSubject; |
| | | import lombok.Data; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | public class AccountSubjectVo extends AccountSubject { |
| | | |
| | | /** |
| | | * åç§ç®å表ï¼éå½ç»æï¼ã |
| | | */ |
| | | private List<AccountSubjectVo> children = new ArrayList<>(); |
| | | |
| | | /** |
| | | * æ¯å¦å¶åèç¹ã |
| | | */ |
| | | private Boolean leaf; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.financial; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | |
| | | /** |
| | | * ç§ç®è´¦åºç¡å彿¥è¯¢å¯¹è±¡ï¼SQLæ å°ä½¿ç¨ï¼ã |
| | | */ |
| | | @Data |
| | | public class FinLedgerEntryRecordVo { |
| | | |
| | | /** |
| | | * åè¯æ¥æã |
| | | */ |
| | | private LocalDate voucherDate; |
| | | |
| | | /** |
| | | * åè¯åå·ã |
| | | */ |
| | | private String voucherNo; |
| | | |
| | | /** |
| | | * æè¦ã |
| | | */ |
| | | private String summary; |
| | | |
| | | /** |
| | | * åæ¹éé¢ã |
| | | */ |
| | | private BigDecimal debit; |
| | | |
| | | /** |
| | | * è´·æ¹éé¢ã |
| | | */ |
| | | private BigDecimal credit; |
| | | |
| | | /** |
| | | * è¡å·ï¼æåºå段ï¼ã |
| | | */ |
| | | private Integer rowNo; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.financial; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonInclude; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | |
| | | /** |
| | | * ç§ç®è´¦è¡æ°æ®è¿å对象ã |
| | | */ |
| | | @Data |
| | | @JsonInclude(JsonInclude.Include.NON_NULL) |
| | | public class FinLedgerRowVo { |
| | | |
| | | /** |
| | | * è¡ç±»åï¼opening/entry/monthly_total/yearly_totalã |
| | | */ |
| | | private String rowType; |
| | | |
| | | /** |
| | | * æ¥æã |
| | | */ |
| | | private LocalDate date; |
| | | |
| | | /** |
| | | * åè¯åå·ã |
| | | */ |
| | | private String voucherNo; |
| | | |
| | | /** |
| | | * æè¦ã |
| | | */ |
| | | private String summary; |
| | | |
| | | /** |
| | | * åæ¹éé¢ã |
| | | */ |
| | | private BigDecimal debit; |
| | | |
| | | /** |
| | | * è´·æ¹éé¢ã |
| | | */ |
| | | private BigDecimal credit; |
| | | |
| | | /** |
| | | * ä½é¢æ¹åï¼å/è´·ã |
| | | */ |
| | | private String direction; |
| | | |
| | | /** |
| | | * ä½é¢ï¼åæ£è´·è´ï¼ã |
| | | */ |
| | | private BigDecimal balance; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.financial; |
| | | |
| | | import com.ruoyi.account.pojo.financial.FinVoucher; |
| | | import com.ruoyi.account.pojo.financial.FinVoucherEntry; |
| | | import com.ruoyi.basic.dto.StorageBlobDTO; |
| | | import com.ruoyi.basic.dto.StorageBlobVO; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * åè¯è¯¦æ
è¿å对象ã |
| | | */ |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class FinVoucherDetailVo extends FinVoucher { |
| | | |
| | | /** |
| | | * åè¯åå½å表ã |
| | | */ |
| | | private List<FinVoucherEntry> entries; |
| | | private List<StorageBlobVO> storageBlobVOList; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.purchase; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.purchase.AccountPaymentApplication; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountPaymentApplicationVo", description = "è´¢å¡ç®¡ç--仿¬¾ç³è¯·å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class AccountPaymentApplicationVo extends AccountPaymentApplication { |
| | | |
| | | @Schema(description = "ä¾åºååç§°") |
| | | @Excel(name = "ä¾åºååç§°") |
| | | private String supplierName; |
| | | |
| | | @Schema(description = "å
¥åºåå·") |
| | | @Excel(name = "å
¥åºåå·") |
| | | private String inboundBatches; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.purchase; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountPurchaseInvoiceVo", description = "è´¢å¡ç®¡ç--è¿é¡¹å票å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class AccountPurchaseInvoiceVo extends AccountPurchaseInvoice { |
| | | |
| | | @Schema(description = "ä¾åºååç§°") |
| | | @Excel(name = "ä¾åºååç§°") |
| | | private String supplierName; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.purchase; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountPurchasePaymentVo", description = "è´¢å¡ç®¡ç--仿¬¾åå°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class AccountPurchasePaymentVo extends AccountPurchasePayment { |
| | | |
| | | @Schema(description = "ä¾åºååç§°") |
| | | @Excel(name = "ä¾åºååç§°") |
| | | private String supplierName; |
| | | |
| | | @Schema(description = "仿¬¾ç³è¯·åå·") |
| | | @Excel(name = "仿¬¾ç³è¯·åå·") |
| | | private String invoiceApplicationNo; |
| | | |
| | | @Schema(description = "弿·è¡") |
| | | @Excel(name = "弿·è¡") |
| | | private String bankAccountName; |
| | | |
| | | @Schema(description = "é¶è¡è´¦å·") |
| | | @Excel(name = "é¶è¡è´¦å·") |
| | | private String bankAccountNum; |
| | | |
| | | @Schema(description = "æ¯å¦çæäºå¯¹è´¦å") |
| | | private boolean isAccountStatemen; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.purchase; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "PurchaseInboundVo", description = "è´¢å¡ç®¡ç--éè´å
¥åºå°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class PurchaseInboundVo { |
| | | |
| | | @Schema(description = "å
¥åºåid") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å
¥åºåå·") |
| | | @Excel(name = "å
¥åºåå·") |
| | | private String inboundBatches; |
| | | |
| | | @Schema(description = "ä¾åºå") |
| | | @Excel(name = "ä¾åºå") |
| | | private String supplierName; |
| | | |
| | | @Schema(description = "å
¥åºæ¥æ") |
| | | @Excel(name = "å
¥åºæ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate InboundDate; |
| | | |
| | | @Schema(description = "产ååç§°") |
| | | @Excel(name = "产ååç§°") |
| | | private String productName; |
| | | |
| | | @Schema(description = "产åè§æ ¼") |
| | | @Excel(name = "产åè§æ ¼") |
| | | private String specificationModel; |
| | | |
| | | @Schema(description = "éé¢") |
| | | @Excel(name = "éé¢") |
| | | private BigDecimal InboundAmount; |
| | | |
| | | @Schema(description = "éè´è®¢åå·") |
| | | @Excel(name = "éè´è®¢åå·") |
| | | private String purchaseContractNumber; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.purchase; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @Schema(name = "PurchaseReturnVo", description = "è´¢å¡ç®¡ç--éè´éè´§å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class PurchaseReturnVo { |
| | | |
| | | @Schema(description = "éè´§åid") |
| | | private Long id; |
| | | |
| | | @Excel(name = "éè´§åå·") |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnNo; |
| | | |
| | | @Schema(description = "ä¾åºå") |
| | | @Excel(name = "ä¾åºå") |
| | | private String supplierName; |
| | | |
| | | @Schema(description = "å
³èå
¥åºåå·") |
| | | @Excel(name = "å
³èå
¥åºåå·") |
| | | private String inboundBatches; |
| | | |
| | | @Schema(description = "åè´§ç±»å") |
| | | @Excel(name = "åè´§ç±»å") |
| | | private Integer shippingType; |
| | | |
| | | @Schema(description = "å货车çå·") |
| | | @Excel(name = "å货车çå·") |
| | | private String truckPlateNo; |
| | | |
| | | @Schema(description = "å¿«éå
¬å¸") |
| | | @Excel(name = "å¿«éå
¬å¸") |
| | | private String expressCompany; |
| | | |
| | | @Schema(description = "å¿«éåå·") |
| | | @Excel(name = "å¿«éåå·") |
| | | private String expressNo; |
| | | |
| | | @Schema(description = "éè´§æ¥æ") |
| | | @Excel(name = "éè´§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime preparedAt; |
| | | |
| | | @Schema(description = "鿬¾æ»é¢") |
| | | @Excel(name = "鿬¾æ»é¢") |
| | | private BigDecimal totalAmount; |
| | | |
| | | @Schema(description = "éè´§æ¹å¼") |
| | | @Excel(name = "éè´§æ¹å¼") |
| | | private String returnType; |
| | | |
| | | @Schema(description = "éè´è®¢åå·") |
| | | @Excel(name = "éè´è®¢åå·") |
| | | private String purchaseContractNumber; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.sales; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountInvoiceApplicationVo", description = "è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯·å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class AccountInvoiceApplicationVo extends AccountInvoiceApplication { |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "åºåºåå·") |
| | | @Excel(name = "åºåºåå·") |
| | | private String outboundBatches; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.sales; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountSalesCollectionVo", description = "è´¢å¡ç®¡ç--æ¶æ¬¾åå°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class AccountSalesCollectionVo extends AccountSalesCollection { |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "åºåºåå·") |
| | | @Excel(name = "åºåºåå·") |
| | | private String outboundBatches; |
| | | |
| | | @Schema(description = "æ¯å¦çæäºå¯¹è´¦å") |
| | | private boolean isAccountStatemen; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.sales; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesInvoice; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Schema(name = "AccountSalesInvoiceVo", description = "è´¢å¡ç®¡ç--é项å票å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class AccountSalesInvoiceVo extends AccountSalesInvoice { |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.sales; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnore; |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesOutboundVo", description = "è´¢å¡ç®¡ç--éå®åºåºå°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class SalesOutboundVo { |
| | | |
| | | @Schema(description = "åºåºåid") |
| | | private Long id; |
| | | |
| | | @Schema(description = "åºåºåå·") |
| | | @Excel(name = "åºåºåå·") |
| | | private String outboundBatches; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "åºåºæ¥æ") |
| | | @Excel(name = "åºåºæ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate shippingDate; |
| | | |
| | | @Schema(description = "产ååç§°") |
| | | @Excel(name = "产ååç§°") |
| | | private String productName; |
| | | |
| | | @Schema(description = "产åè§æ ¼") |
| | | @Excel(name = "产åè§æ ¼") |
| | | private String specificationModel; |
| | | |
| | | @Schema(description = "éé¢") |
| | | @Excel(name = "éé¢") |
| | | private BigDecimal outboundAmount; |
| | | |
| | | @Schema(description = "ç¨ç") |
| | | private BigDecimal taxRate; |
| | | |
| | | @Schema(description = "åè´§ç¼å·") |
| | | @Excel(name = "åè´§ç¼å·") |
| | | private String shippingNo; |
| | | |
| | | @Schema(description = "éå®è®¢åå·") |
| | | @Excel(name = "éå®è®¢åå·") |
| | | private String salesContractNo; |
| | | |
| | | @Schema(description = "å·²æ¶æ¬¾éé¢") |
| | | @ExcelIgnore |
| | | private BigDecimal amountReceived; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo.sales; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesReturnVo", description = "è´¢å¡ç®¡ç--éå®éè´§å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class SalesReturnVo { |
| | | |
| | | @Schema(description = "éè´§åid") |
| | | private Long id; |
| | | |
| | | @Excel(name = "éè´§åå·") |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnNo; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "å
³èåè´§åå·") |
| | | @Excel(name = "å
³èåè´§åå·") |
| | | private String shippingNo; |
| | | |
| | | @Schema(description = "åè´§ç±»å") |
| | | @Excel(name = "åè´§ç±»å") |
| | | private Integer shippingType; |
| | | |
| | | @Schema(description = "å货车çå·") |
| | | @Excel(name = "å货车çå·") |
| | | private String truckPlateNo; |
| | | |
| | | @Schema(description = "å¿«éå
¬å¸") |
| | | @Excel(name = "å¿«éå
¬å¸") |
| | | private String expressCompany; |
| | | |
| | | @Schema(description = "å¿«éåå·") |
| | | @Excel(name = "å¿«éåå·") |
| | | private String expressNo; |
| | | |
| | | @Schema(description = "éè´§æ¥æ") |
| | | @Excel(name = "éè´§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime makeTime; |
| | | |
| | | @Schema(description = "鿬¾æ»é¢") |
| | | @Excel(name = "鿬¾æ»é¢") |
| | | private BigDecimal refundAmount; |
| | | |
| | | @Schema(description = "éè´§åå ") |
| | | @Excel(name = "éè´§åå ") |
| | | private String returnReason; |
| | | |
| | | @Schema(description = "éå®è®¢åå·") |
| | | @Excel(name = "éå®è®¢åå·") |
| | | private String salesContractNo; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.StatementAccountDto; |
| | | import com.ruoyi.account.bean.vo.StatementAccountVo; |
| | | import com.ruoyi.account.service.AccountStatementService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 09:42:47 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountStatement") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ç-对账å") |
| | | public class AccountStatementController { |
| | | |
| | | private final AccountStatementService accountStatementService; |
| | | |
| | | @GetMapping("/getAccountStatementDetailsByMonth") |
| | | @Log(title = "æ ¹æ®å®¢æ·åæä»½æ¥è¯¢å¯¹è´¦åæç»", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ ¹æ®å®¢æ·åæä»½æ¥è¯¢å¯¹è´¦åæç»") |
| | | public R getAccountStatementDetailsByMonth(StatementAccountDto statementAccountDto) { |
| | | return R.ok(accountStatementService.getAccountStatementDetailsByMonth(statementAccountDto)); |
| | | } |
| | | |
| | | @PostMapping("/addAccountStatement") |
| | | @Log(title = "æ°å¢å¯¹è´¦å", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢å¯¹è´¦å") |
| | | public R addAccountStatement(@RequestBody StatementAccountVo statementAccountVo) { |
| | | return R.ok(accountStatementService.addAccountStatement(statementAccountVo)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountStatement") |
| | | @Log(title = "å é¤å¯¹è´¦å", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å é¤å¯¹è´¦å") |
| | | public R deleteAccountStatement(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountStatementService.deleteAccountStatement(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @GetMapping("/listPageAccountStatement") |
| | | @Log(title = "对账åå°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--对账åå°è´¦") |
| | | public R<IPage<StatementAccountVo>> listPageAccountStatement(Page page, StatementAccountDto statementAccountDto) { |
| | | IPage<StatementAccountVo> listPage = accountStatementService.listPageAccountStatement(page,statementAccountDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountStatement") |
| | | @Operation(summary = "导åºå¯¹è´¦åæä»¶") |
| | | @Log(title = "导åºå¯¹è´¦åæä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountStatement(HttpServletResponse response, StatementAccountDto statementAccountDto) { |
| | | accountStatementService.exportAccountStatement(response,statementAccountDto); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.ruoyi.account.controller; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.service.impl.AccountingServiceImpl; |
| | | import com.ruoyi.account.bean.dto.AccountReportDto; |
| | | import com.ruoyi.account.service.AccountingService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import lombok.AllArgsConstructor; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | |
| | | @Tag(name = "ä¼è®¡æ ¸ç®") |
| | | @RestController |
| | | @RequestMapping("/accounting") |
| | | @AllArgsConstructor |
| | | @RequiredArgsConstructor |
| | | public class AccountingController extends BaseController { |
| | | |
| | | |
| | | private AccountingServiceImpl accountingService; |
| | | private final AccountingService accountingService; |
| | | |
| | | @Operation(summary = "æ»è®¡") |
| | | @GetMapping("/total") |
| | |
| | | return accountingService.calculateDepreciation(page,year); |
| | | } |
| | | |
| | | /*****************************************è´¢å¡æ¥è¡¨******************************************************************************/ |
| | | |
| | | @GetMapping("/accountStatementDetailsByMonth") |
| | | @Log(title = "è´¢å¡æ¥è¡¨", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡æ¥è¡¨") |
| | | public R getAccountStatementDetailsByMonth(AccountReportDto accountReportDto) { |
| | | return R.ok(accountingService.getAccountStatementDetailsByMonth(accountReportDto)); |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | package com.ruoyi.account.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.ruoyi.account.dto.SalesReceiptReturnDto; |
| | | import com.ruoyi.account.bean.dto.SalesReceiptReturnDto; |
| | | import com.ruoyi.account.pojo.SalesReceiptReturn; |
| | | import com.ruoyi.account.service.SalesReceiptReturnService; |
| | | import com.ruoyi.account.service.impl.SalesReceiptReturnServiceImpl; |
| | |
| | | package com.ruoyi.account.controller; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.pojo.SalesRefundAmountOrder; |
| | | import com.ruoyi.account.service.SalesRefundAmountOrderService; |
| | | import com.ruoyi.framework.web.domain.R; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.financial.AccountSubjectDto; |
| | | import com.ruoyi.account.bean.vo.financial.AccountSubjectVo; |
| | | import com.ruoyi.account.service.purchase.AccountSubjectService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * æ»è´¦ç§ç®è¡¨ å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountSubject") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "æ»è´¦ç§ç®") |
| | | public class AccountSubjectController { |
| | | private final AccountSubjectService accountSubjectService; |
| | | |
| | | @GetMapping("/list") |
| | | @Log(title = "æ»è´¦ç§ç®æ°æ®éå", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "æ»è´¦ç§ç®æ å½¢æ¥è¯¢ï¼éå½ï¼") |
| | | public R<IPage<AccountSubjectVo>> AccountSubjectDtoList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) { |
| | | IPage<AccountSubjectVo> paramList = accountSubjectService.baseList(page, accountSubjectDto); |
| | | return R.ok(paramList); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "æ°å¢æ»è´¦ç§ç®", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "æ°å¢æ»è´¦ç§ç®") |
| | | public R AccountSubjectDtoAdd(@RequestBody AccountSubjectDto accountSubjectDto) { |
| | | return R.ok(accountSubjectService.saveAccountSubject(accountSubjectDto)); |
| | | } |
| | | |
| | | @PutMapping("/edit") |
| | | @Log(title = "ä¿®æ¹æ»è´¦ç§ç®", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "ä¿®æ¹æ»è´¦ç§ç®") |
| | | public R AccountSubjectDtoEdit(@RequestBody AccountSubjectDto accountSubjectDto) { |
| | | return R.ok(accountSubjectService.updateAccountSubject(accountSubjectDto)); |
| | | } |
| | | |
| | | @DeleteMapping("/remove/{ids}") |
| | | @Log(title = "å 餿»è´¦ç§ç®", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "å 餿»è´¦ç§ç®") |
| | | public R AccountSubjectDtooRemove(@PathVariable Long[] ids) { |
| | | return R.ok(accountSubjectService.removeAccountSubjectByIds(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/export") |
| | | @Operation(summary = "å¯¼åºæ»è´¦ç§ç®æä»¶") |
| | | @Log(title = "å¯¼åºæ»è´¦ç§ç®æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSubject(HttpServletResponse response) { |
| | | accountSubjectService.exportAccountSubject(response); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.financial.FinFixedAssetDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinIdBatchDto; |
| | | import com.ruoyi.account.pojo.financial.FinFixedAsset; |
| | | import com.ruoyi.account.service.financial.FinFixedAssetService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * åºå®èµäº§æ§å¶å¨ã |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/financial/fixedAsset") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ç-åºå®èµäº§") |
| | | public class FinFixedAssetController { |
| | | |
| | | private final FinFixedAssetService finFixedAssetService; |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "åºå®èµäº§å页æ¥è¯¢") |
| | | public R<IPage<FinFixedAsset>> page(Page<FinFixedAsset> page, FinFixedAssetDto queryDto) { |
| | | return R.ok(finFixedAssetService.pageList(page, queryDto)); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "åºå®èµäº§", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "æ°å¢åºå®èµäº§") |
| | | public R<Boolean> add(@RequestBody FinFixedAssetDto dto) { |
| | | return R.ok(finFixedAssetService.add(dto)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Log(title = "åºå®èµäº§", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "ä¿®æ¹åºå®èµäº§") |
| | | public R<Boolean> update(@RequestBody FinFixedAssetDto dto) { |
| | | return R.ok(finFixedAssetService.update(dto)); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Log(title = "åºå®èµäº§", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "å é¤åºå®èµäº§") |
| | | public R<Boolean> delete(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(finFixedAssetService.deleteByIds(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/depreciate") |
| | | @Log(title = "åºå®èµäº§ææ§è®¡æ", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "åºå®èµäº§ææè®¡æææ§") |
| | | public R depreciate(@RequestBody(required = false) FinIdBatchDto dto) { |
| | | return R.ok(finFixedAssetService.depreciate(dto == null ? null : dto.getIds())); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.financial.FinIdBatchDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinIntangibleAssetDto; |
| | | import com.ruoyi.account.pojo.financial.FinIntangibleAsset; |
| | | import com.ruoyi.account.service.financial.FinIntangibleAssetService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * æ å½¢èµäº§æ§å¶å¨ã |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/financial/intangibleAsset") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ç-æ å½¢èµäº§") |
| | | public class FinIntangibleAssetController { |
| | | |
| | | private final FinIntangibleAssetService finIntangibleAssetService; |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "æ å½¢èµäº§å页æ¥è¯¢") |
| | | public R<IPage<FinIntangibleAsset>> page(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto) { |
| | | return R.ok(finIntangibleAssetService.pageList(page, queryDto)); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "æ å½¢èµäº§", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "æ°å¢æ å½¢èµäº§") |
| | | public R<Boolean> add(@RequestBody FinIntangibleAssetDto dto) { |
| | | return R.ok(finIntangibleAssetService.add(dto)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Log(title = "æ å½¢èµäº§", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "ä¿®æ¹æ å½¢èµäº§") |
| | | public R<Boolean> update(@RequestBody FinIntangibleAssetDto dto) { |
| | | return R.ok(finIntangibleAssetService.update(dto)); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Log(title = "æ å½¢èµäº§", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "å 餿 å½¢èµäº§") |
| | | public R<Boolean> delete(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(finIntangibleAssetService.deleteByIds(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/amortize") |
| | | @Log(title = "æ å½¢èµäº§æé计æ", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "æ å½¢èµäº§ææè®¡ææé") |
| | | public R amortize(@RequestBody(required = false) FinIdBatchDto dto) { |
| | | return R.ok(finIntangibleAssetService.amortize(dto == null ? null : dto.getIds())); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.financial; |
| | | |
| | | import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto; |
| | | import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo; |
| | | import com.ruoyi.account.service.financial.FinLedgerService; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ç§ç®æ»è´¦/æç»è´¦æ§å¶å¨ã |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/financial/ledger") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ç-ç§ç®è´¦") |
| | | public class FinLedgerController { |
| | | |
| | | private final FinLedgerService finLedgerService; |
| | | |
| | | @GetMapping("/general") |
| | | @Operation(summary = "ç§ç®æ»è´¦æ¥è¯¢") |
| | | public R<List<FinLedgerRowVo>> general(FinLedgerQueryDto queryDto) { |
| | | return R.ok(finLedgerService.queryGeneralLedger(queryDto)); |
| | | } |
| | | |
| | | @GetMapping("/detail") |
| | | @Operation(summary = "ç§ç®æç»è´¦æ¥è¯¢") |
| | | public R<List<FinLedgerRowVo>> detail(FinDetailLedgerQueryDto queryDto) { |
| | | return R.ok(finLedgerService.queryDetailLedger(queryDto)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherStatusDto; |
| | | import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo; |
| | | import com.ruoyi.account.pojo.financial.FinVoucher; |
| | | import com.ruoyi.account.service.financial.FinVoucherService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | /** |
| | | * åè¯æ§å¶å¨ã |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/financial/voucher") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ç-åè¯") |
| | | public class FinVoucherController { |
| | | |
| | | private final FinVoucherService finVoucherService; |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "åè¯å页æ¥è¯¢") |
| | | public R<IPage<FinVoucher>> page(Page<FinVoucher> page, FinVoucherPageDto queryDto) { |
| | | return R.ok(finVoucherService.pageList(page, queryDto)); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "åè¯", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "æ°å¢åè¯") |
| | | public R<Boolean> add(@RequestBody FinVoucherDto dto) { |
| | | return R.ok(finVoucherService.addVoucher(dto)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Log(title = "åè¯", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "ä¿®æ¹åè¯") |
| | | public R<Boolean> update(@RequestBody FinVoucherDto dto) { |
| | | return R.ok(finVoucherService.updateVoucher(dto)); |
| | | } |
| | | |
| | | @PostMapping("/post") |
| | | @Log(title = "åè¯è¿è´¦", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "åè¯è¿è´¦") |
| | | public R<Boolean> post(@RequestBody FinVoucherStatusDto dto) { |
| | | return R.ok(finVoucherService.postVoucher(dto.getId())); |
| | | } |
| | | |
| | | @PostMapping("/cancel") |
| | | @Log(title = "åè¯ä½åº", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "åè¯ä½åº") |
| | | public R<Boolean> cancel(@RequestBody FinVoucherStatusDto dto) { |
| | | return R.ok(finVoucherService.cancelVoucher(dto.getId())); |
| | | } |
| | | |
| | | @GetMapping("/detail/{id}") |
| | | @Operation(summary = "åè¯è¯¦æ
") |
| | | public R<FinVoucherDetailVo> detail(@PathVariable("id") Long id) { |
| | | return R.ok(finVoucherService.detail(id)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo; |
| | | import com.ruoyi.account.service.financial.AccountPurchaseService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéè´é¨å å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountPurchase") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ççéè´é¨å") |
| | | public class AccounPurchaseController { |
| | | |
| | | private final AccountPurchaseService accountPurchaseService; |
| | | |
| | | @GetMapping("/listPageAccountPurchase") |
| | | @Log(title = "éè´å
¥åºå°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--éè´å
¥åºå°è´¦") |
| | | public R<IPage<PurchaseInboundVo>> listPageAccountPurchase(Page page, PurchaseInboundDto purchaseInboundDto) { |
| | | IPage<PurchaseInboundVo> listPage = accountPurchaseService.listPageAccountPurchase(page,purchaseInboundDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountPurchaseInbound") |
| | | @Operation(summary = "导åºéè´å
¥åºæä»¶") |
| | | @Log(title = "导åºéè´å
¥åºæä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountPurchaseInbound(HttpServletResponse response, PurchaseInboundDto purchaseInboundDto) { |
| | | accountPurchaseService.exportAccountPurchaseInbound(response,purchaseInboundDto); |
| | | } |
| | | |
| | | @GetMapping("/listPageAccountPurchaseReturn") |
| | | @Log(title = "éè´éè´§å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--éè´éè´§å°è´¦") |
| | | public R<IPage<PurchaseReturnVo>> listPageAccountPurchaseReturn(Page page, PurchaseReturnDto purchaseReturnDto) { |
| | | IPage<PurchaseReturnVo> listPage = accountPurchaseService.listPageAccountPurchaseReturn(page,purchaseReturnDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountPurchaseReturn") |
| | | @Operation(summary = "导åºéè´éè´§æä»¶") |
| | | @Log(title = "导åºéè´éè´§æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountPurchaseReturn(HttpServletResponse response,PurchaseReturnDto purchaseReturnDto) { |
| | | accountPurchaseService.exportAccountPurchaseReturn(response,purchaseReturnDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPaymentApplicationDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPaymentApplication; |
| | | import com.ruoyi.account.service.purchase.AccountPaymentApplicationService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾ç³è¯· å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:44:22 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountPaymentApplication") |
| | | @Tag(name = "è´¢å¡ç®¡ç--仿¬¾ç³è¯·") |
| | | @RequiredArgsConstructor |
| | | public class AccountPaymentApplicationController { |
| | | |
| | | private final AccountPaymentApplicationService accountPaymentApplicationService; |
| | | |
| | | @GetMapping("/listPageAccountPaymentApplication") |
| | | @Log(title = "仿¬¾ç³è¯·å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--仿¬¾ç³è¯·å°è´¦") |
| | | public R<IPage<AccountPaymentApplicationVo>> listPageAccountPaymentApplication(Page page, AccountPaymentApplicationDto accountPaymentApplicationDto) { |
| | | IPage<AccountPaymentApplicationVo> listPage = accountPaymentApplicationService.listPageAccountPaymentApplication(page,accountPaymentApplicationDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @GetMapping("/getInboundBatchesBySupplier") |
| | | @Log(title = "æ ¹æ®ä¾åºåæ¥è¯¢å
¥åºåå·(仿¬¾ç³è¯·)", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ ¹æ®ä¾åºåæ¥è¯¢å
¥åºåå·(仿¬¾ç³è¯·)") |
| | | public R getInboundBatchesBySupplier(Integer supplierId) { |
| | | return R.ok(accountPaymentApplicationService.getInboundBatchesBySupplier(supplierId)); |
| | | } |
| | | |
| | | @PostMapping("/addAccountPaymentApplication") |
| | | @Log(title = "æ°å¢ä»æ¬¾ç³è¯·", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢ä»æ¬¾ç³è¯·") |
| | | public R addAccountPaymentApplication(@RequestBody AccountPaymentApplication accountPaymentApplication) { |
| | | return R.ok(accountPaymentApplicationService.addAccountPaymentApplication(accountPaymentApplication)); |
| | | } |
| | | |
| | | @PutMapping("/updateAccountPaymentApplication") |
| | | @Log(title = "ä¿®æ¹ä»æ¬¾ç³è¯·", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--ä¿®æ¹ä»æ¬¾ç³è¯·") |
| | | public R updateAccountPaymentApplication(@RequestBody AccountPaymentApplication accountPaymentApplication) { |
| | | return R.ok(accountPaymentApplicationService.updateById(accountPaymentApplication)); |
| | | } |
| | | |
| | | @PutMapping("/auditAccountPaymentApplication") |
| | | @Log(title = "å®¡æ ¸ä»æ¬¾ç³è¯·", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å®¡æ ¸ä»æ¬¾ç³è¯·") |
| | | public R auditAccountPaymentApplication(@RequestBody AccountPaymentApplication accountPaymentApplication) { |
| | | return R.ok(accountPaymentApplicationService.updateById(accountPaymentApplication)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountPaymentApplication") |
| | | @Log(title = "å é¤ä»æ¬¾ç³è¯·", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å é¤ä»æ¬¾ç³è¯·") |
| | | public R deleteAccountPaymentApplication(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountPaymentApplicationService.deleteAccountPaymentApplication(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountPaymentApplication") |
| | | @Operation(summary = "导åºä»æ¬¾ç³è¯·æä»¶") |
| | | @Log(title = "导åºä»æ¬¾ç³è¯·æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountPaymentApplication(HttpServletResponse response, AccountPaymentApplicationDto accountPaymentApplicationDto) { |
| | | accountPaymentApplicationService.exportAccountPaymentApplication(response,accountPaymentApplicationDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchaseInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice; |
| | | import com.ruoyi.account.service.purchase.AccountPurchaseInvoiceService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--è¿é¡¹å票 å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:06:17 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountPurchaseInvoice") |
| | | @Tag(name = "è´¢å¡ç®¡ç--è¿é¡¹å票") |
| | | @RequiredArgsConstructor |
| | | public class AccountPurchaseInvoiceController { |
| | | |
| | | private final AccountPurchaseInvoiceService accountPurchaseInvoiceService; |
| | | |
| | | @GetMapping("/listPageAccountPurchaseInvoice") |
| | | @Log(title = "è¿é¡¹å票å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--è¿é¡¹å票å°è´¦") |
| | | public R<IPage<AccountPurchaseInvoiceVo>> listPageAccountPurchaseInvoice(Page page, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) { |
| | | IPage<AccountPurchaseInvoiceVo> listPage = accountPurchaseInvoiceService.listPageAccountPurchaseInvoice(page,accountPurchaseInvoiceDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @GetMapping("/getInboundBatchesBySupplier") |
| | | @Log(title = "æ ¹æ®ä¾åºåæ¥è¯¢å
¥åºåå·(è¿é¡¹å票)", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ ¹æ®ä¾åºåæ¥è¯¢å
¥åºåå·(è¿é¡¹å票)") |
| | | public R getInboundBatchesBySupplier(Integer supplierId) { |
| | | return R.ok(accountPurchaseInvoiceService.getInboundBatchesBySupplier(supplierId)); |
| | | } |
| | | |
| | | @PostMapping("/addAccountPurchaseInvoice") |
| | | @Log(title = "æ°å¢è¿é¡¹å票", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢è¿é¡¹å票") |
| | | public R addAccountPurchaseInvoice(@RequestBody AccountPurchaseInvoice accountPurchaseInvoice) { |
| | | return R.ok(accountPurchaseInvoiceService.save(accountPurchaseInvoice)); |
| | | } |
| | | |
| | | @PutMapping("/cancelAccountPurchaseInvoice") |
| | | @Log(title = "ä½åºè¿é¡¹å票", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--ä½åºé项å票") |
| | | public R cancelAccountPurchaseInvoice(@RequestBody AccountPurchaseInvoice accountPurchaseInvoice) { |
| | | return R.ok(accountPurchaseInvoiceService.updateById(accountPurchaseInvoice)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountPurchaseInvoice") |
| | | @Log(title = "å é¤è¿é¡¹å票", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å é¤è¿é¡¹å票") |
| | | public R deleteAccountPurchaseInvoice(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountPurchaseInvoiceService.deleteAccountPurchaseInvoice(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountPurchaseInvoice") |
| | | @Operation(summary = "导åºè¿é¡¹å票æä»¶") |
| | | @Log(title = "导åºè¿é¡¹å票æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountPurchaseInvoice(HttpServletResponse response, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) { |
| | | accountPurchaseInvoiceService.exportAccountPurchaseInvoice(response,accountPurchaseInvoiceDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.account.service.purchase.AccountPurchasePaymentService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾å å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 04:14:51 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountPurchasePayment") |
| | | @Tag(name = "è´¢å¡ç®¡ç--仿¬¾å") |
| | | @RequiredArgsConstructor |
| | | public class AccountPurchasePaymentController { |
| | | |
| | | private final AccountPurchasePaymentService accountPurchasePaymentService; |
| | | |
| | | @GetMapping("/listPageAccountPurchasePayment") |
| | | @Log(title = "仿¬¾åå°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--仿¬¾åå°è´¦") |
| | | public R<IPage<AccountPurchasePaymentVo>> listPageAccountPurchasePayment(Page page, AccountPurchasePaymentDto accountPurchasePaymentDto) { |
| | | IPage<AccountPurchasePaymentVo> listPage = accountPurchasePaymentService.listPageAccountPurchasePayment(page,accountPurchasePaymentDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/addAccountPurchasePayment") |
| | | @Log(title = "æ°å¢ä»æ¬¾å", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢ä»æ¬¾å") |
| | | public R addAccountPurchasePayment(@RequestBody AccountPurchasePayment accountPurchasePayment) { |
| | | return R.ok(accountPurchasePaymentService.addAccountPurchasePayment(accountPurchasePayment)); |
| | | } |
| | | |
| | | @PutMapping("/updateAccountPurchasePayment") |
| | | @Log(title = "ç¼è¾ä»æ¬¾å", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--ç¼è¾ä»æ¬¾å") |
| | | public R updateAccountPurchasePayment(@RequestBody AccountPurchasePayment accountPurchasePayment) { |
| | | return R.ok(accountPurchasePaymentService.updateById(accountPurchasePayment)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountPurchasePayment") |
| | | @Log(title = "å é¤ä»æ¬¾å", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å é¤ä»æ¬¾å") |
| | | public R deleteAccountPurchasePayment(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountPurchasePaymentService.deleteAccountPurchasePayment(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountPurchasePayment") |
| | | @Operation(summary = "导åºä»æ¬¾åæä»¶") |
| | | @Log(title = "导åºä»æ¬¾åæä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountPurchasePayment(HttpServletResponse response, AccountPurchasePaymentDto accountPurchasePaymentDto) { |
| | | accountPurchasePaymentService.exportAccountPurchasePayment(response,accountPurchasePaymentDto); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountInvoiceApplicationDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import com.ruoyi.account.service.sales.AccountInvoiceApplicationService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯· å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 01:38:32 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountInvoiceApplication") |
| | | @Tag(name = "è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯·") |
| | | @RequiredArgsConstructor |
| | | public class AccountInvoiceApplicationController { |
| | | |
| | | private final AccountInvoiceApplicationService accountInvoiceApplicationService; |
| | | |
| | | @GetMapping("/listPageAccountInvoiceApplication") |
| | | @Log(title = "å¼ç¥¨ç³è¯·å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯·å°è´¦") |
| | | public R<IPage<AccountInvoiceApplicationVo>> listPageAccountInvoiceApplication(Page page, AccountInvoiceApplicationDto accountInvoiceApplicationDto) { |
| | | IPage<AccountInvoiceApplicationVo> listPage = accountInvoiceApplicationService.listPageAccountInvoiceApplication(page,accountInvoiceApplicationDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @GetMapping("/getAccountInvoiceApplicationList") |
| | | @Log(title = "æ ¹æ®å®¢æ·idæ¥è¯¢å¼ç¥¨ç³è¯·å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯·å°è´¦") |
| | | public R<List<AccountInvoiceApplicationVo>> getAccountInvoiceApplicationList(@RequestParam Integer customerId){ |
| | | return R.ok(accountInvoiceApplicationService.getAccountInvoiceApplicationList(customerId)); |
| | | } |
| | | |
| | | @GetMapping("/getOutboundBatchesByCustomer") |
| | | @Log(title = "æ ¹æ®å®¢æ·æ¥è¯¢åºåºåå·(å¼ç¥¨ç³è¯·)", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ ¹æ®å®¢æ·æ¥è¯¢åºåºåå·(å¼ç¥¨ç³è¯·)") |
| | | public R getOutboundBatchesByCustomer(Integer customerId) { |
| | | return R.ok(accountInvoiceApplicationService.getOutboundBatchesByCustomer(customerId)); |
| | | } |
| | | |
| | | @PostMapping("/addAccountInvoiceApplication") |
| | | @Log(title = "æ°å¢å¼ç¥¨ç³è¯·", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢å¼ç¥¨ç³è¯·") |
| | | public R addAccountInvoiceApplication(@RequestBody AccountInvoiceApplication accountInvoiceApplication) { |
| | | return R.ok(accountInvoiceApplicationService.addAccountInvoiceApplication(accountInvoiceApplication)); |
| | | } |
| | | |
| | | @PutMapping("/updateAccountInvoiceApplication") |
| | | @Log(title = "ä¿®æ¹å¼ç¥¨ç³è¯·", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--ä¿®æ¹å¼ç¥¨ç³è¯·") |
| | | public R updateAccountInvoiceApplication(@RequestBody AccountInvoiceApplication accountInvoiceApplication) { |
| | | return R.ok(accountInvoiceApplicationService.updateById(accountInvoiceApplication)); |
| | | } |
| | | |
| | | @PutMapping("/auditAccountInvoiceApplication") |
| | | @Log(title = "å®¡æ ¸å¼ç¥¨ç³è¯·", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å®¡æ ¸å¼ç¥¨ç³è¯·") |
| | | public R auditAccountInvoiceApplication(@RequestBody AccountInvoiceApplication accountInvoiceApplication) { |
| | | return R.ok(accountInvoiceApplicationService.updateById(accountInvoiceApplication)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountInvoiceApplication") |
| | | @Log(title = "å é¤å¼ç¥¨ç³è¯·", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å é¤å¼ç¥¨ç³è¯·") |
| | | public R deleteAccountInvoiceApplication(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountInvoiceApplicationService.deleteAccountInvoiceApplication(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountInvoiceApplication") |
| | | @Operation(summary = "导åºå¼ç¥¨ç³è¯·æä»¶") |
| | | @Log(title = "导åºå¼ç¥¨ç³è¯·æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto) { |
| | | accountInvoiceApplicationService.exportAccountInvoiceApplication(response,accountInvoiceApplicationDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesCollectionDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.account.service.sales.AccountSalesCollectionService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--æ¶æ¬¾å å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:49:56 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountSalesCollection") |
| | | @Tag(name = "è´¢å¡ç®¡ç--æ¶æ¬¾å") |
| | | @RequiredArgsConstructor |
| | | public class AccountSalesCollectionController { |
| | | |
| | | private final AccountSalesCollectionService accountSalesCollectionService; |
| | | |
| | | @GetMapping("/listPageAccountSalesCollection") |
| | | @Log(title = "æ¶æ¬¾åå°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ¶æ¬¾åå°è´¦") |
| | | public R<IPage<AccountSalesCollectionVo>> listPageAccountSalesCollection(Page page, AccountSalesCollectionDto accountSalesCollectionDto) { |
| | | IPage<AccountSalesCollectionVo> listPage = accountSalesCollectionService.listPageAccountSalesCollection(page,accountSalesCollectionDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @GetMapping("/getOutboundBatchesByCustomer") |
| | | @Log(title = "æ ¹æ®å®¢æ·æ¥è¯¢åºåºåå·(æ¶æ¬¾å)", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ ¹æ®å®¢æ·æ¥è¯¢åºåºåå·(æ¶æ¬¾å)") |
| | | public R getOutboundBatchesByCustomer(Integer customerId) { |
| | | return R.ok(accountSalesCollectionService.getOutboundBatchesByCustomer(customerId)); |
| | | } |
| | | |
| | | @PostMapping("/addAccountSalesCollection") |
| | | @Log(title = "æ°å¢æ¶æ¬¾å", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢æ¶æ¬¾å") |
| | | public R addAccountSalesCollection(@RequestBody AccountSalesCollection accountSalesCollection) { |
| | | return R.ok(accountSalesCollectionService.addAccountSalesCollection(accountSalesCollection)); |
| | | } |
| | | |
| | | @PutMapping("/updateAccountSalesCollection") |
| | | @Log(title = "ç¼è¾æ¶æ¬¾å", businessType = BusinessType.UPDATE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--ç¼è¾æ¶æ¬¾å") |
| | | public R updateAccountSalesCollection(@RequestBody AccountSalesCollection accountSalesCollection) { |
| | | return R.ok(accountSalesCollectionService.updateById(accountSalesCollection)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountSalesCollection") |
| | | @Log(title = "å 餿¶æ¬¾å", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å 餿¶æ¬¾å") |
| | | public R deleteAccountSalesCollection(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountSalesCollectionService.deleteAccountSalesCollection(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountSalesCollection") |
| | | @Operation(summary = "å¯¼åºæ¶æ¬¾åæä»¶") |
| | | @Log(title = "å¯¼åºæ¶æ¬¾åæä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSalesCollection(HttpServletResponse response, AccountSalesCollectionDto accountSalesCollectionDto) { |
| | | accountSalesCollectionService.exportAccountSalesCollection(response,accountSalesCollectionDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesReturnVo; |
| | | import com.ruoyi.account.service.sales.AccountSalesService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountSales") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ççéå®é¨å") |
| | | public class AccountSalesController { |
| | | |
| | | private final AccountSalesService accountSalesService; |
| | | |
| | | @GetMapping("/listPageAccountSales") |
| | | @Log(title = "éå®åºåºå°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--éå®åºåºå°è´¦") |
| | | public R<IPage<SalesOutboundVo>> listPageAccountSales(Page page, SalesOutboundDto salesOutboundDto) { |
| | | IPage<SalesOutboundVo> listPage = accountSalesService.listPageAccountSales(page,salesOutboundDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountSalesOutbound") |
| | | @Operation(summary = "导åºéå®åºåºæä»¶") |
| | | @Log(title = "导åºéå®åºåºæä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSalesOutbound(HttpServletResponse response,SalesOutboundDto salesOutboundDto) { |
| | | accountSalesService.exportAccountSalesOutbound(response,salesOutboundDto); |
| | | } |
| | | |
| | | @GetMapping("/listPageAccountSalesReturn") |
| | | @Log(title = "éå®éè´§å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--éå®éè´§å°è´¦") |
| | | public R<IPage<SalesReturnVo>> listPageAccountSalesReturn(Page page, SalesReturnDto salesReturnDto) { |
| | | IPage<SalesReturnVo> listPage = accountSalesService.listPageAccountSalesReturn(page,salesReturnDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountSalesReturn") |
| | | @Operation(summary = "导åºéå®éè´§æä»¶") |
| | | @Log(title = "导åºéå®éè´§æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSalesReturn(HttpServletResponse response,SalesReturnDto salesReturnDto) { |
| | | accountSalesService.exportAccountSalesReturn(response,salesReturnDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesInvoice; |
| | | import com.ruoyi.account.service.sales.AccountSalesInvoiceService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--é项å票 å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:10:20 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountSalesInvoice") |
| | | @Tag(name = "è´¢å¡ç®¡ç--é项å票") |
| | | @RequiredArgsConstructor |
| | | public class AccountSalesInvoiceController { |
| | | |
| | | private final AccountSalesInvoiceService accountSalesInvoiceService; |
| | | |
| | | @GetMapping("/listPageAccountSalesInvoice") |
| | | @Log(title = "é项å票å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--é项å票å°è´¦") |
| | | public R<IPage<AccountSalesInvoiceVo>> listPageAccountSalesInvoice(Page page, AccountSalesInvoiceDto accountSalesInvoiceDto) { |
| | | IPage<AccountSalesInvoiceVo> listPage = accountSalesInvoiceService.listPageAccountSalesInvoice(page,accountSalesInvoiceDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/addAccountSalesInvoice") |
| | | @Log(title = "æ°å¢é项å票", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--æ°å¢é项å票") |
| | | public R addAccountSalesInvoice(@RequestBody AccountSalesInvoice accountSalesInvoice) { |
| | | return R.ok(accountSalesInvoiceService.save(accountSalesInvoice)); |
| | | } |
| | | |
| | | @PutMapping("/cancelAccountSalesInvoice") |
| | | @Log(title = "ä½åºé项å票", businessType = BusinessType.INSERT) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--ä½åºé项å票") |
| | | public R cancelAccountSalesInvoice(@RequestBody AccountSalesInvoice accountSalesInvoice) { |
| | | return R.ok(accountSalesInvoiceService.updateById(accountSalesInvoice)); |
| | | } |
| | | |
| | | @DeleteMapping("/deleteAccountSalesInvoice") |
| | | @Log(title = "å é¤é项å票", businessType = BusinessType.DELETE) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--å é¤é项å票") |
| | | public R deleteAccountSalesInvoice(@RequestParam("ids") Long[] ids) { |
| | | return R.ok(accountSalesInvoiceService.deleteAccountSalesInvoice(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountSalesInvoice") |
| | | @Operation(summary = "导åºé项å票æä»¶") |
| | | @Log(title = "导åºé项å票æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSalesInvoice(HttpServletResponse response, AccountSalesInvoiceDto accountSalesInvoiceDto) { |
| | | accountSalesInvoiceService.exportAccountSalesInvoice(response,accountSalesInvoiceDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--对账åæç» Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 10:12:42 |
| | | */ |
| | | @Mapper |
| | | public interface AccountStatementDetailsMapper extends BaseMapper<AccountStatementDetails> { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.StatementAccountDto; |
| | | import com.ruoyi.account.bean.vo.StatementAccountVo; |
| | | import com.ruoyi.account.pojo.AccountStatement; |
| | | import com.ruoyi.purchase.dto.VatDto; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | /** |
| | | * <p> |
| | | * Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 09:42:47 |
| | | */ |
| | | @Mapper |
| | | public interface AccountStatementMapper extends BaseMapper<AccountStatement> { |
| | | |
| | | |
| | | IPage<StatementAccountVo> listPageAccountStatement(Page page, @Param("req") StatementAccountDto statementAccountDto); |
| | | |
| | | IPage<VatDto> selectVatDtoPage(Page page, @Param("month") String month); |
| | | } |
| | |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.pojo.SalesRefundAmountOrder; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.account.pojo.financial.AccountSubject; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * æ»è´¦ç§ç®è¡¨ Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @Mapper |
| | | public interface AccountSubjectMapper extends BaseMapper<AccountSubject> { |
| | | |
| | | Long countReferencedBySubjectCodes(@Param("subjectCodes") List<String> subjectCodes); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.account.pojo.financial.FinFixedAsset; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * åºå®èµäº§ Mapperã |
| | | */ |
| | | @Mapper |
| | | public interface FinFixedAssetMapper extends BaseMapper<FinFixedAsset> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.account.pojo.financial.FinIntangibleAsset; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * æ å½¢èµäº§ Mapperã |
| | | */ |
| | | @Mapper |
| | | public interface FinIntangibleAssetMapper extends BaseMapper<FinIntangibleAsset> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo; |
| | | import com.ruoyi.account.pojo.financial.FinVoucherEntry; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * åè¯åå½ Mapperã |
| | | */ |
| | | @Mapper |
| | | public interface FinVoucherEntryMapper extends BaseMapper<FinVoucherEntry> { |
| | | |
| | | List<FinLedgerEntryRecordVo> listPostedEntries(@Param("subjectCode") String subjectCode, |
| | | @Param("startDate") LocalDate startDate, |
| | | @Param("endDate") LocalDate endDate, |
| | | @Param("auxiliaryType") String auxiliaryType, |
| | | @Param("auxiliaryId") String auxiliaryId); |
| | | |
| | | List<FinLedgerEntryRecordVo> listPostedEntriesBefore(@Param("subjectCode") String subjectCode, |
| | | @Param("beforeDate") LocalDate beforeDate, |
| | | @Param("auxiliaryType") String auxiliaryType, |
| | | @Param("auxiliaryId") String auxiliaryId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.account.pojo.financial.FinVoucher; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * åè¯ä¸»è¡¨ Mapperã |
| | | */ |
| | | @Mapper |
| | | public interface FinVoucherMapper extends BaseMapper<FinVoucher> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPaymentApplicationDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPaymentApplication; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾ç³è¯· Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:44:22 |
| | | */ |
| | | @Mapper |
| | | public interface AccountPaymentApplicationMapper extends BaseMapper<AccountPaymentApplication> { |
| | | |
| | | IPage<AccountPaymentApplicationVo> listPageAccountPaymentApplication(Page page, @Param("req") AccountPaymentApplicationDto accountPaymentApplicationDto); |
| | | |
| | | List<PurchaseInboundVo> getInboundBatchesBySupplier(@Param("supplierId") Integer supplierId); |
| | | |
| | | //å¤æè¯¥åºåºè®°å½æ¯å¦æå¼ç¥¨ç³è¯· |
| | | boolean existsByStockInRecordId(@Param("stockInRecordIds") List<Long> stockInRecordIds); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchaseInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--è¿é¡¹å票 Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:06:17 |
| | | */ |
| | | @Mapper |
| | | public interface AccountPurchaseInvoiceMapper extends BaseMapper<AccountPurchaseInvoice> { |
| | | |
| | | IPage<AccountPurchaseInvoiceVo> listPageAccountPurchaseInvoice(Page page, @Param("req") AccountPurchaseInvoiceDto accountPurchaseInvoiceDto); |
| | | |
| | | List<PurchaseInboundVo> getInboundBatchesBySupplier(@Param("supplierId") Integer supplierId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.home.dto.IncomeExpenseAnalysisDto; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾å Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 04:14:51 |
| | | */ |
| | | @Mapper |
| | | public interface AccountPurchasePaymentMapper extends BaseMapper<AccountPurchasePayment> { |
| | | |
| | | IPage<AccountPurchasePaymentVo> listPageAccountPurchasePayment(Page page, @Param("req") AccountPurchasePaymentDto accountPurchasePaymentDto); |
| | | |
| | | List<IncomeExpenseAnalysisDto> selectPayment(@Param("startStr") String startStr, @Param("endStr") String endStr, @Param("dateFormat") String dateFormat); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountInvoiceApplicationDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯· Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 01:38:32 |
| | | */ |
| | | @Mapper |
| | | public interface AccountInvoiceApplicationMapper extends BaseMapper<AccountInvoiceApplication> { |
| | | |
| | | IPage<AccountInvoiceApplicationVo> listPageAccountInvoiceApplication(Page page, @Param("req") AccountInvoiceApplicationDto accountInvoiceApplicationDto); |
| | | |
| | | List<SalesOutboundVo> getOutboundBatchesByCustomer(@Param("customerId") Integer customerId); |
| | | |
| | | //å¤æè¯¥åºåºè®°å½æ¯å¦æå¼ç¥¨ç³è¯· |
| | | boolean existsByStockOutRecordId(@Param("stockOutRecordIds") List<Long> stockOutRecordIds); |
| | | |
| | | List<AccountInvoiceApplicationVo> selectAccountInvoiceApplicationList(@Param("customerId") Integer customerId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesCollectionDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.home.dto.IncomeExpenseAnalysisDto; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--æ¶æ¬¾å Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:49:56 |
| | | */ |
| | | @Mapper |
| | | public interface AccountSalesCollectionMapper extends BaseMapper<AccountSalesCollection> { |
| | | |
| | | IPage<AccountSalesCollectionVo> listPageAccountSalesCollection(Page page, @Param("req") AccountSalesCollectionDto accountSalesCollectionDto); |
| | | |
| | | //å¤æè¯¥åºåºè®°å½æ¯å¦ææ¶æ¬¾å |
| | | boolean existsByStockOutRecordId(@Param("stockOutRecordIds") List<Long> stockOutRecordIds); |
| | | |
| | | List<SalesOutboundVo> getOutboundBatchesByCustomer(@Param("customerId") Integer customerId); |
| | | |
| | | List<IncomeExpenseAnalysisDto> selectIncomeStats(@Param("startStr") String startStr, @Param("endStr") String endStr, @Param("dateFormat") String dateFormat); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.mapper.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesInvoice; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--é项å票 Mapper æ¥å£ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:10:20 |
| | | */ |
| | | @Mapper |
| | | public interface AccountSalesInvoiceMapper extends BaseMapper<AccountSalesInvoice> { |
| | | |
| | | IPage<AccountSalesInvoiceVo> listPageAccountSalesInvoice(Page page, @Param("req") AccountSalesInvoiceDto accountSalesInvoiceDto); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 09:42:47 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_statement") |
| | | @ApiModel(value = "AccountStatement对象", description = "è´¢å¡ç®¡ç--对账å") |
| | | public class AccountStatement implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * 客æ·id(åºæ¶æ¯å®¢æ·customer,åºä»æ¯ä¾åºåsupplier) |
| | | */ |
| | | @ApiModelProperty("客æ·id") |
| | | private Integer customerId; |
| | | |
| | | /** |
| | | * 对账æä»½(yyyy-MM) |
| | | */ |
| | | @ApiModelProperty("对账æä»½(yyyy-MM)") |
| | | @Excel(name = "对账æä»½") |
| | | private String statementMonth; |
| | | |
| | | /** |
| | | * ä¸å¡ç±»å(1åºæ¶å¯¹è´¦;2åºä»å¯¹è´¦) |
| | | */ |
| | | @ApiModelProperty("ä¸å¡ç±»å(1åºæ¶å¯¹è´¦;2åºä»å¯¹è´¦)") |
| | | @Excel(name = "ä¸å¡ç±»å",readConverterExp = "1=åºæ¶å¯¹è´¦,2=åºä»å¯¹è´¦") |
| | | private Integer accountType; |
| | | |
| | | /** |
| | | * 对账åå· |
| | | */ |
| | | @ApiModelProperty("对账åå·") |
| | | @Excel(name = "对账åå·") |
| | | private String statementNumber; |
| | | |
| | | /** |
| | | * æåä½é¢ |
| | | */ |
| | | @ApiModelProperty("æåä½é¢") |
| | | @Excel(name = "æåä½é¢") |
| | | private BigDecimal openingBalance; |
| | | |
| | | /** |
| | | * æ¬æåºæ¶/åºä» |
| | | */ |
| | | @ApiModelProperty("æ¬æåºæ¶/åºä»") |
| | | @Excel(name = "æ¬æåºæ¶/åºä»") |
| | | private BigDecimal currentPlan; |
| | | |
| | | /** |
| | | * æ¬ææ¶æ¬¾/仿¬¾ |
| | | */ |
| | | @ApiModelProperty("æ¬ææ¶æ¬¾/仿¬¾") |
| | | @Excel(name = "æ¬ææ¶æ¬¾/仿¬¾") |
| | | private BigDecimal currentActually; |
| | | |
| | | /** |
| | | * ææ«ä½é¢ |
| | | */ |
| | | @ApiModelProperty("ææ«ä½é¢") |
| | | @Excel(name = "ææ«ä½é¢") |
| | | private BigDecimal closingBalance; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--对账åæç» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 10:12:42 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_statement_details") |
| | | @ApiModel(value = "AccountStatementDetails对象", description = "è´¢å¡ç®¡ç--对账åæç»") |
| | | public class AccountStatementDetails implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å
³è对账åid |
| | | */ |
| | | @ApiModelProperty("å
³è对账åid") |
| | | private Integer accountStatementId; |
| | | |
| | | /** |
| | | * æ°æ®æ¥æ |
| | | */ |
| | | @ApiModelProperty("æ°æ®æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate occurrenceDate; |
| | | |
| | | /** |
| | | * åæ®ç¼å· |
| | | */ |
| | | @ApiModelProperty("åæ®ç¼å·") |
| | | private String receiptNumber; |
| | | |
| | | /** |
| | | * æ°æ®ç±»å(1åºåº;2å
¥åº;3æ¶æ¬¾;4仿¬¾;5éè´§) |
| | | */ |
| | | @ApiModelProperty("æ°æ®ç±»å(1åºåº;2å
¥åº;3æ¶æ¬¾;4仿¬¾;5éè´§)") |
| | | private Integer type; |
| | | |
| | | /** |
| | | * éé¢ |
| | | */ |
| | | @ApiModelProperty("éé¢") |
| | | private BigDecimal amount; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.financial; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | |
| | | import java.io.Serializable; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * æ»è´¦ç§ç®è¡¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_subject") |
| | | @ApiModel(value = "AccountSubject对象", description = "æ»è´¦ç§ç®è¡¨") |
| | | public class AccountSubject implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * 主é®ID |
| | | */ |
| | | @ApiModelProperty("主é®ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | /** |
| | | * ç¶ç§ç®IDï¼ä¸ºç©ºè¡¨ç¤ºæ ¹èç¹ï¼ |
| | | */ |
| | | @ApiModelProperty("ç¶ç§ç®IDï¼ä¸ºç©ºè¡¨ç¤ºæ ¹èç¹ï¼") |
| | | private Long parentId; |
| | | |
| | | /** |
| | | * ç§ç®ç¼ç (å¯ä¸æ è¯) |
| | | */ |
| | | @ApiModelProperty("ç§ç®ç¼ç (å¯ä¸æ è¯)") |
| | | private String subjectCode; |
| | | |
| | | /** |
| | | * ç§ç®åç§° |
| | | */ |
| | | @ApiModelProperty("ç§ç®åç§°") |
| | | private String subjectName; |
| | | |
| | | /** |
| | | * ç§ç®ç±»å |
| | | */ |
| | | @ApiModelProperty("ç§ç®ç±»å") |
| | | private String subjectType; |
| | | |
| | | /** |
| | | * ä½é¢æ¹å |
| | | */ |
| | | @ApiModelProperty("ä½é¢æ¹å") |
| | | private String balanceDirection; |
| | | |
| | | /** |
| | | * ç¶æ 0å¯ç¨ 1ç¦ç¨ |
| | | */ |
| | | @ApiModelProperty("ç¶æ 0å¯ç¨ 1ç¦ç¨") |
| | | private Integer status; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.financial; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * åºå®èµäº§å®ä½ã |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("fin_fixed_asset") |
| | | @ApiModel(value = "FinFixedAsset对象", description = "åºå®èµäº§") |
| | | public class FinFixedAsset implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty("主é®ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty("èµäº§ç¼å·") |
| | | private String assetCode; |
| | | |
| | | @ApiModelProperty("èµäº§åç§°") |
| | | private String assetName; |
| | | |
| | | @ApiModelProperty("èµäº§ç±»å«") |
| | | private String category; |
| | | |
| | | @ApiModelProperty("è§æ ¼åå·") |
| | | private String specification; |
| | | |
| | | @ApiModelProperty("è´ç½®æ¥æ") |
| | | private LocalDate purchaseDate; |
| | | |
| | | @ApiModelProperty("èµäº§åå¼") |
| | | private BigDecimal originalValue; |
| | | |
| | | @ApiModelProperty("使ç¨å¹´é(å¹´)") |
| | | private Integer usefulLife; |
| | | |
| | | @ApiModelProperty("æ®å¼ç(%)") |
| | | private BigDecimal residualRate; |
| | | |
| | | @ApiModelProperty("ç´¯è®¡ææ§") |
| | | private BigDecimal accumulatedDepreciation; |
| | | |
| | | @ApiModelProperty("åå¼") |
| | | private BigDecimal netValue; |
| | | |
| | | @ApiModelProperty("åæ¾å°ç¹") |
| | | private String location; |
| | | |
| | | @ApiModelProperty("使ç¨é¨é¨") |
| | | private String department; |
| | | |
| | | @ApiModelProperty("ä¿ç®¡äºº") |
| | | private String keeper; |
| | | |
| | | @ApiModelProperty("ç¶æ") |
| | | private String status; |
| | | |
| | | @ApiModelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private String createUser; |
| | | |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private String updateUser; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.financial; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æ å½¢èµäº§å®ä½ã |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("fin_intangible_asset") |
| | | @ApiModel(value = "FinIntangibleAsset对象", description = "æ å½¢èµäº§") |
| | | public class FinIntangibleAsset implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty("主é®ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty("èµäº§ç¼å·") |
| | | private String assetCode; |
| | | |
| | | @ApiModelProperty("èµäº§åç§°") |
| | | private String assetName; |
| | | |
| | | @ApiModelProperty("èµäº§ç±»å«") |
| | | private String category; |
| | | |
| | | @ApiModelProperty("è¯ä¹¦ç¼å·") |
| | | private String certificateNo; |
| | | |
| | | @ApiModelProperty("å徿¥æ") |
| | | private LocalDate acquisitionDate; |
| | | |
| | | @ApiModelProperty("èµäº§åå¼") |
| | | private BigDecimal originalValue; |
| | | |
| | | @ApiModelProperty("æéå¹´é(å¹´)") |
| | | private Integer amortizationPeriod; |
| | | |
| | | @ApiModelProperty("æ®å¼ç(%)") |
| | | private BigDecimal residualRate; |
| | | |
| | | @ApiModelProperty("累计æé") |
| | | private BigDecimal accumulatedAmortization; |
| | | |
| | | @ApiModelProperty("åå¼") |
| | | private BigDecimal netValue; |
| | | |
| | | @ApiModelProperty("æææè³") |
| | | private LocalDate validityDate; |
| | | |
| | | @ApiModelProperty("ç¶æ") |
| | | private String status; |
| | | |
| | | @ApiModelProperty("èµäº§æè¿°") |
| | | private String description; |
| | | |
| | | @ApiModelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private String createUser; |
| | | |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private String updateUser; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.financial; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * åè¯ä¸»è¡¨å®ä½ã |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("fin_voucher") |
| | | @ApiModel(value = "FinVoucher对象", description = "åè¯ä¸»è¡¨") |
| | | public class FinVoucher implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty("主é®ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty("åè¯åå·") |
| | | private String voucherNo; |
| | | |
| | | @ApiModelProperty("åè¯æ¥æ") |
| | | private LocalDate voucherDate; |
| | | |
| | | @ApiModelProperty("æè¦") |
| | | private String summary; |
| | | |
| | | @ApiModelProperty("åæ¹å计") |
| | | private BigDecimal debit; |
| | | |
| | | @ApiModelProperty("è´·æ¹å计") |
| | | private BigDecimal credit; |
| | | |
| | | @ApiModelProperty("å¶å人") |
| | | private String creator; |
| | | |
| | | @ApiModelProperty("ç¶æ: unposted/posted/cancelled") |
| | | private String status; |
| | | |
| | | @ApiModelProperty("éä»¶æ°é") |
| | | private Integer attachmentCount; |
| | | |
| | | @ApiModelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private String createUser; |
| | | |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private String updateUser; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.financial; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * åè¯åå½å®ä½ã |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("fin_voucher_entry") |
| | | @ApiModel(value = "FinVoucherEntry对象", description = "åè¯åå½") |
| | | public class FinVoucherEntry implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty("主é®ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty("åè¯ID") |
| | | private Long voucherId; |
| | | |
| | | @ApiModelProperty("è¡å·") |
| | | private Integer rowNo; |
| | | |
| | | @ApiModelProperty("ç§ç®ç¼ç ") |
| | | private String subjectCode; |
| | | |
| | | @ApiModelProperty("ç§ç®åç§°") |
| | | private String subjectName; |
| | | |
| | | @ApiModelProperty("æè¦") |
| | | private String summary; |
| | | |
| | | @ApiModelProperty("åæ¹éé¢") |
| | | private BigDecimal debit; |
| | | |
| | | @ApiModelProperty("è´·æ¹éé¢") |
| | | private BigDecimal credit; |
| | | |
| | | @ApiModelProperty("è¾
婿 ¸ç®ç±»å") |
| | | private String auxiliaryType; |
| | | |
| | | @ApiModelProperty("è¾
婿 ¸ç®å¯¹è±¡ID") |
| | | private String auxiliaryId; |
| | | |
| | | @ApiModelProperty("è¾
婿 ¸ç®å¯¹è±¡åç§°") |
| | | private String auxiliaryName; |
| | | |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private String createUser; |
| | | |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private String updateUser; |
| | | |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private LocalDateTime updateTime; |
| | | |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾ç³è¯· |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:44:22 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_payment_application") |
| | | @ApiModel(value = "AccountPaymentApplication对象", description = "è´¢å¡ç®¡ç--仿¬¾ç³è¯·") |
| | | public class AccountPaymentApplication implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | /** |
| | | * ä¾åºåid |
| | | */ |
| | | @ApiModelProperty("ä¾åºåid") |
| | | private Integer supplierId; |
| | | |
| | | /** |
| | | * å
³èå
¥åºåid(å¤é) |
| | | */ |
| | | @ApiModelProperty("å
³èå
¥åºåid(å¤é)") |
| | | private String stockInRecordIds; |
| | | |
| | | /** |
| | | * 仿¬¾ç³è¯·åå· |
| | | */ |
| | | @ApiModelProperty("仿¬¾ç³è¯·åå·") |
| | | @Excel(name = "仿¬¾ç³è¯·åå·") |
| | | private String invoiceApplicationNo; |
| | | |
| | | /** |
| | | * 仿¬¾æ¹å¼ |
| | | */ |
| | | @ApiModelProperty("仿¬¾æ¹å¼") |
| | | @Excel(name = "仿¬¾æ¹å¼") |
| | | private String paymentMethod; |
| | | |
| | | /** |
| | | * 仿¬¾äºç± |
| | | */ |
| | | @ApiModelProperty("仿¬¾äºç±") |
| | | @Excel(name = "仿¬¾äºç±") |
| | | private String paymentContent; |
| | | |
| | | /** |
| | | * ç³è¯·æ¥æ |
| | | */ |
| | | @ApiModelProperty("ç³è¯·æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @Excel(name = "ç³è¯·æ¥æ") |
| | | private LocalDate applyDate; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | |
| | | /** |
| | | * å®¡æ ¸ç¶æ:0å¾
å®¡æ ¸1å®¡æ ¸éè¿2å®¡æ ¸ä¸éè¿ |
| | | */ |
| | | @ApiModelProperty("å®¡æ ¸ç¶æ:0å¾
å®¡æ ¸1å®¡æ ¸éè¿2å®¡æ ¸ä¸éè¿") |
| | | @Excel(name = "å®¡æ ¸ç¶æ",readConverterExp = "0=å¾
å®¡æ ¸,1=å®¡æ ¸éè¿,2=å®¡æ ¸ä¸éè¿") |
| | | private Integer status; |
| | | |
| | | /** |
| | | * 仿¬¾éé¢ |
| | | */ |
| | | @ApiModelProperty("仿¬¾éé¢") |
| | | @Excel(name = "仿¬¾éé¢") |
| | | private BigDecimal paymentAmount; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--è¿é¡¹å票 |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:06:17 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_purchase_invoice") |
| | | @ApiModel(value = "AccountPurchaseInvoice对象", description = "è´¢å¡ç®¡ç--è¿é¡¹å票") |
| | | public class AccountPurchaseInvoice implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | /** |
| | | * å票å·ç |
| | | */ |
| | | @ApiModelProperty("å票å·ç ") |
| | | @Excel(name = "å票å·ç ") |
| | | private String invoiceNumber; |
| | | |
| | | /** |
| | | * ç¨ç |
| | | */ |
| | | @ApiModelProperty("ç¨ç") |
| | | @Excel(name = "ç¨ç") |
| | | private Integer taxRate; |
| | | |
| | | /** |
| | | * å票类å |
| | | */ |
| | | @ApiModelProperty("å票类å") |
| | | @Excel(name = "å票类å") |
| | | private String invoiceType; |
| | | |
| | | /** |
| | | * å¼ç¥¨æ¥æ |
| | | */ |
| | | @ApiModelProperty("å¼ç¥¨æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @Excel(name = "å¼ç¥¨æ¥æ") |
| | | private LocalDate issueDate; |
| | | |
| | | /** |
| | | * éé¢(ä¸å«ç¨) |
| | | */ |
| | | @ApiModelProperty("éé¢(ä¸å«ç¨)") |
| | | @Excel(name = "éé¢(ä¸å«ç¨)") |
| | | private BigDecimal taxExclusivelPrice; |
| | | |
| | | /** |
| | | * ç¨é¢ |
| | | */ |
| | | @ApiModelProperty("ç¨é¢") |
| | | @Excel(name = "ç¨é¢") |
| | | private BigDecimal taxPrice; |
| | | |
| | | /** |
| | | * ä»·ç¨å计 |
| | | */ |
| | | @ApiModelProperty("ä»·ç¨å计") |
| | | @Excel(name = "ä»·ç¨å计") |
| | | private BigDecimal taxInclusivePrice; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | |
| | | /** |
| | | * å票å
容 |
| | | */ |
| | | @ApiModelProperty("å票å
容") |
| | | @Excel(name = "å票å
容") |
| | | private String invoiceContent; |
| | | |
| | | /** |
| | | * ä¾åºåid |
| | | */ |
| | | @ApiModelProperty("ä¾åºåid") |
| | | private Integer supplierId; |
| | | |
| | | /** |
| | | * å
³èä¸ä¼ çå票éä»¶id |
| | | */ |
| | | @ApiModelProperty("å
³èä¸ä¼ çå票éä»¶id") |
| | | private Integer storageAttachmentId; |
| | | |
| | | /** |
| | | * å
³èå
¥åºåid(å¤é) |
| | | */ |
| | | @ApiModelProperty("å
³èå
¥åºåid(å¤é)") |
| | | private String stockInRecordIds; |
| | | |
| | | /** |
| | | * ç¶æ 0å¯ç¨ 1ç¦ç¨ |
| | | */ |
| | | @ApiModelProperty("ç¶æ 0å¯ç¨ 1ç¦ç¨") |
| | | @Excel(name = "ç¶æ", readConverterExp = "0=æ£å¸¸,1=ä½åº") |
| | | private Integer status; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾å |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 04:14:51 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_purchase_payment") |
| | | @ApiModel(value = "AccountPurchasePayment对象", description = "è´¢å¡ç®¡ç--仿¬¾å") |
| | | public class AccountPurchasePayment implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å
³è仿¬¾ç³è¯·id |
| | | */ |
| | | @ApiModelProperty("å
³è仿¬¾ç³è¯·id") |
| | | private Integer accountPaymentApplicationId; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | /** |
| | | * ä¾åºåid |
| | | */ |
| | | @ApiModelProperty("ä¾åºåid") |
| | | private Integer supplierId; |
| | | |
| | | /** |
| | | * 仿¬¾æ¥æ |
| | | */ |
| | | @ApiModelProperty("仿¬¾æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @Excel(name = "仿¬¾æ¥æ") |
| | | private LocalDate paymentDate; |
| | | |
| | | /** |
| | | * 仿¬¾æ¹å¼ |
| | | */ |
| | | @ApiModelProperty("仿¬¾æ¹å¼") |
| | | @Excel(name = "仿¬¾æ¹å¼",dictType = "checkout_payment") |
| | | private String paymentMethod; |
| | | |
| | | /** |
| | | * 仿¬¾éé¢ |
| | | */ |
| | | @ApiModelProperty("仿¬¾éé¢") |
| | | @Excel(name = "仿¬¾éé¢") |
| | | private BigDecimal paymentAmount; |
| | | |
| | | /** |
| | | * 仿¬¾åå· |
| | | */ |
| | | @ApiModelProperty("仿¬¾åå·") |
| | | @Excel(name = "仿¬¾åå·") |
| | | private String paymentNumber; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.sales; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯· |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 01:38:32 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_invoice_application") |
| | | @ApiModel(value = "AccountInvoiceApplication对象", description = "è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯·") |
| | | public class AccountInvoiceApplication implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | /** |
| | | * 客æ·id |
| | | */ |
| | | @ApiModelProperty("客æ·id") |
| | | private Integer customerId; |
| | | |
| | | /** |
| | | * å
³èåºåºåid(å¤é) |
| | | */ |
| | | @ApiModelProperty("å
³èåºåºåid(å¤é)") |
| | | private String stockOutRecordIds; |
| | | |
| | | /** |
| | | * å¼ç¥¨ç³è¯·åå· |
| | | */ |
| | | @ApiModelProperty("å¼ç¥¨ç³è¯·åå·") |
| | | @Excel(name = "å¼ç¥¨ç³è¯·åå·") |
| | | private String invoiceApplicationNo; |
| | | |
| | | /** |
| | | * å票类å |
| | | */ |
| | | @ApiModelProperty("å票类å") |
| | | @Excel(name = "å票类å") |
| | | private String invoiceType; |
| | | |
| | | /** |
| | | * ç³è¯·æ¥æ |
| | | */ |
| | | @ApiModelProperty("ç³è¯·æ¥æ") |
| | | @Excel(name = "ç³è¯·æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate applyDate; |
| | | |
| | | /** |
| | | * å票å
容 |
| | | */ |
| | | @ApiModelProperty("å票å
容") |
| | | @Excel(name = "å票å
容") |
| | | private String invoiceContent; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | |
| | | /** |
| | | * å®¡æ ¸ç¶æ:0å¾
å®¡æ ¸1å®¡æ ¸éè¿2å®¡æ ¸ä¸éè¿ |
| | | */ |
| | | @ApiModelProperty("å®¡æ ¸ç¶æ:0å¾
å®¡æ ¸1å®¡æ ¸éè¿2å®¡æ ¸ä¸éè¿") |
| | | @Excel(name = "å®¡æ ¸ç¶æ",readConverterExp = "0=å¾
å®¡æ ¸,1=å®¡æ ¸éè¿,2=å®¡æ ¸ä¸éè¿") |
| | | private Integer status; |
| | | |
| | | @ApiModelProperty("å¼ç¥¨éé¢") |
| | | @Excel(name = "å¼ç¥¨éé¢") |
| | | private BigDecimal invoiceAmount; |
| | | |
| | | @ApiModelProperty("ç¨ç") |
| | | @Excel(name = "ç¨ç") |
| | | private BigDecimal taxRate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.sales; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--æ¶æ¬¾å |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:49:56 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_sales_collection") |
| | | @ApiModel(value = "AccountSalesCollection对象", description = "è´¢å¡ç®¡ç--æ¶æ¬¾å") |
| | | public class AccountSalesCollection implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å
³èåºåºåid(å¤é) |
| | | */ |
| | | @ApiModelProperty("å
³èåºåºåid(å¤é)") |
| | | private String stockOutRecordIds; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | /** |
| | | * 客æ·id |
| | | */ |
| | | @ApiModelProperty("客æ·id") |
| | | private Integer customerId; |
| | | |
| | | /** |
| | | * æ¶æ¬¾æ¥æ |
| | | */ |
| | | @ApiModelProperty("æ¶æ¬¾æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @Excel(name = "æ¶æ¬¾æ¥æ") |
| | | private LocalDate collectionDate; |
| | | |
| | | /** |
| | | * æ¶æ¬¾éé¢ |
| | | */ |
| | | @ApiModelProperty("æ¶æ¬¾éé¢") |
| | | @Excel(name = "æ¶æ¬¾éé¢") |
| | | private BigDecimal collectionAmount; |
| | | |
| | | /** |
| | | * æ¶æ¬¾æ¹å¼ |
| | | */ |
| | | @ApiModelProperty("æ¶æ¬¾æ¹å¼") |
| | | @Excel(name = "æ¶æ¬¾æ¹å¼",dictType = "payment_methods") |
| | | private String collectionMethod; |
| | | |
| | | /** |
| | | * æ¶æ¬¾åå· |
| | | */ |
| | | @ApiModelProperty("æ¶æ¬¾åå·") |
| | | @Excel(name = "æ¶æ¬¾åå·") |
| | | private String collectionNumber; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.pojo.sales; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--é项å票 |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:10:20 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | @TableName("account_sales_invoice") |
| | | @ApiModel(value = "AccountSalesInvoice对象", description = "è´¢å¡ç®¡ç--é项å票") |
| | | public class AccountSalesInvoice implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | /** |
| | | * å
³èå¼ç¥¨ç³è¯·id |
| | | */ |
| | | @ApiModelProperty("å
³èå¼ç¥¨ç³è¯·id") |
| | | private Integer accountInvoiceApplicationId; |
| | | |
| | | /** |
| | | * å建人 |
| | | */ |
| | | @ApiModelProperty("å建人") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Integer createUser; |
| | | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @ApiModelProperty("å建æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | | * ä¿®æ¹äºº |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹äºº") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | private Integer updateUser; |
| | | |
| | | /** |
| | | * ä¿®æ¹æ¶é´ |
| | | */ |
| | | @ApiModelProperty("ä¿®æ¹æ¶é´") |
| | | @TableField(fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime updateTime; |
| | | |
| | | /** |
| | | * é¨é¨ID |
| | | */ |
| | | @ApiModelProperty("é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | /** |
| | | * å票å·ç |
| | | */ |
| | | @ApiModelProperty("å票å·ç ") |
| | | @Excel(name = "å票å·ç ") |
| | | private String invoiceNumber; |
| | | |
| | | /** |
| | | * ç¨ç |
| | | */ |
| | | @ApiModelProperty("ç¨ç") |
| | | @Excel(name = "ç¨ç") |
| | | private BigDecimal taxRate; |
| | | |
| | | /** |
| | | * å票类å |
| | | */ |
| | | @ApiModelProperty("å票类å") |
| | | @Excel(name = "å票类å") |
| | | private String invoiceType; |
| | | |
| | | /** |
| | | * å¼ç¥¨æ¥æ |
| | | */ |
| | | @ApiModelProperty("å¼ç¥¨æ¥æ") |
| | | @Excel(name = "å¼ç¥¨æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate issueDate; |
| | | |
| | | /** |
| | | * éé¢(ä¸å«ç¨) |
| | | */ |
| | | @ApiModelProperty("éé¢(ä¸å«ç¨)") |
| | | @Excel(name = "éé¢(ä¸å«ç¨)") |
| | | private BigDecimal taxExclusivelPrice; |
| | | |
| | | /** |
| | | * ç¨é¢ |
| | | */ |
| | | @ApiModelProperty("ç¨é¢") |
| | | @Excel(name = "ç¨é¢") |
| | | private BigDecimal taxPrice; |
| | | |
| | | /** |
| | | * ä»·ç¨å计 |
| | | */ |
| | | @ApiModelProperty("ä»·ç¨å计") |
| | | @Excel(name = "ä»·ç¨å计") |
| | | private BigDecimal taxInclusivePrice; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | @ApiModelProperty("夿³¨") |
| | | @Excel(name = "夿³¨") |
| | | private String remark; |
| | | |
| | | /** |
| | | * å票å
容 |
| | | */ |
| | | @ApiModelProperty("å票å
容") |
| | | @Excel(name = "å票å
容") |
| | | private String invoiceContent; |
| | | |
| | | /** |
| | | * 客æ·id |
| | | */ |
| | | @ApiModelProperty("客æ·id") |
| | | private Integer customerId; |
| | | |
| | | /** |
| | | * å
³èä¸ä¼ çå票éä»¶id |
| | | */ |
| | | @ApiModelProperty("å
³èä¸ä¼ çå票éä»¶id") |
| | | private Integer storageAttachmentId; |
| | | |
| | | /** |
| | | * ç¶æ 0å¯ç¨ 1ç¦ç¨ |
| | | */ |
| | | @ApiModelProperty("ç¶æ 0å¯ç¨ 1ç¦ç¨") |
| | | @Excel(name = "ç¶æ", readConverterExp = "0=æ£å¸¸,1=ä½åº") |
| | | private Integer status; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service; |
| | | |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--对账åæç» æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 10:12:42 |
| | | */ |
| | | public interface AccountStatementDetailsService extends IService<AccountStatementDetails> { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.account.bean.dto.StatementAccountDto; |
| | | import com.ruoyi.account.bean.vo.StatementAccountVo; |
| | | import com.ruoyi.account.pojo.AccountStatement; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 09:42:47 |
| | | */ |
| | | public interface AccountStatementService extends IService<AccountStatement> { |
| | | |
| | | StatementAccountVo getAccountStatementDetailsByMonth(StatementAccountDto statementAccountDto); |
| | | |
| | | boolean addAccountStatement(StatementAccountVo statementAccountVo); |
| | | |
| | | boolean deleteAccountStatement(List<Long> ids); |
| | | |
| | | IPage<StatementAccountVo> listPageAccountStatement(Page page, StatementAccountDto statementAccountDto); |
| | | |
| | | void exportAccountStatement(HttpServletResponse response, StatementAccountDto statementAccountDto); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service; |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.AccountReportDto; |
| | | import com.ruoyi.account.bean.vo.AccountReportVo; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | |
| | | public interface AccountingService { |
| | | |
| | | AjaxResult total(Integer year); |
| | | |
| | | AjaxResult deviceTypeDistribution(Integer year); |
| | | |
| | | AjaxResult calculateDepreciation(Page page, Integer year); |
| | | |
| | | AccountReportVo getAccountStatementDetailsByMonth(AccountReportDto accountReportDto); |
| | | |
| | | } |
| | |
| | | package com.ruoyi.account.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.ruoyi.account.dto.SalesReceiptReturnDto; |
| | | import com.ruoyi.account.bean.dto.SalesReceiptReturnDto; |
| | | import com.ruoyi.account.pojo.SalesReceiptReturn; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | |
| | |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.pojo.SalesRefundAmountOrder; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | public interface AccountPurchaseService { |
| | | IPage<PurchaseInboundVo> listPageAccountPurchase(Page page, PurchaseInboundDto purchaseInboundDto); |
| | | |
| | | void exportAccountPurchaseInbound(HttpServletResponse response, PurchaseInboundDto purchaseInboundDto); |
| | | |
| | | IPage<PurchaseReturnVo> listPageAccountPurchaseReturn(Page page, PurchaseReturnDto purchaseReturnDto); |
| | | |
| | | void exportAccountPurchaseReturn(HttpServletResponse response, PurchaseReturnDto purchaseReturnDto); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.account.bean.dto.financial.FinFixedAssetDto; |
| | | import com.ruoyi.account.pojo.financial.FinFixedAsset; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * åºå®èµäº§æå¡ã |
| | | */ |
| | | public interface FinFixedAssetService extends IService<FinFixedAsset> { |
| | | |
| | | IPage<FinFixedAsset> pageList(Page<FinFixedAsset> page, FinFixedAssetDto queryDto); |
| | | |
| | | Boolean add(FinFixedAssetDto dto); |
| | | |
| | | Boolean update(FinFixedAssetDto dto); |
| | | |
| | | Boolean deleteByIds(List<Long> ids); |
| | | |
| | | Map<String, Object> depreciate(List<Long> ids); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.account.bean.dto.financial.FinIntangibleAssetDto; |
| | | import com.ruoyi.account.pojo.financial.FinIntangibleAsset; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * æ å½¢èµäº§æå¡ã |
| | | */ |
| | | public interface FinIntangibleAssetService extends IService<FinIntangibleAsset> { |
| | | |
| | | IPage<FinIntangibleAsset> pageList(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto); |
| | | |
| | | Boolean add(FinIntangibleAssetDto dto); |
| | | |
| | | Boolean update(FinIntangibleAssetDto dto); |
| | | |
| | | Boolean deleteByIds(List<Long> ids); |
| | | |
| | | Map<String, Object> amortize(List<Long> ids); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.financial; |
| | | |
| | | import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto; |
| | | import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ç§ç®è´¦æå¡ã |
| | | */ |
| | | public interface FinLedgerService { |
| | | |
| | | List<FinLedgerRowVo> queryGeneralLedger(FinLedgerQueryDto queryDto); |
| | | |
| | | List<FinLedgerRowVo> queryDetailLedger(FinDetailLedgerQueryDto queryDto); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto; |
| | | import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo; |
| | | import com.ruoyi.account.pojo.financial.FinVoucher; |
| | | |
| | | /** |
| | | * åè¯æå¡ã |
| | | */ |
| | | public interface FinVoucherService extends IService<FinVoucher> { |
| | | |
| | | IPage<FinVoucher> pageList(Page<FinVoucher> page, FinVoucherPageDto queryDto); |
| | | |
| | | Boolean addVoucher(FinVoucherDto dto); |
| | | |
| | | Boolean updateVoucher(FinVoucherDto dto); |
| | | |
| | | Boolean postVoucher(Long id); |
| | | |
| | | Boolean cancelVoucher(Long id); |
| | | |
| | | FinVoucherDetailVo detail(Long id); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl; |
| | | |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import com.ruoyi.account.mapper.AccountStatementDetailsMapper; |
| | | import com.ruoyi.account.service.AccountStatementDetailsService; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--对账åæç» æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 10:12:42 |
| | | */ |
| | | @Service |
| | | public class AccountStatementDetailsServiceImpl extends ServiceImpl<AccountStatementDetailsMapper, AccountStatementDetails> implements AccountStatementDetailsService { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.StatementAccountDto; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.StatementAccountVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesReturnVo; |
| | | import com.ruoyi.account.mapper.AccountStatementDetailsMapper; |
| | | import com.ruoyi.account.mapper.AccountStatementMapper; |
| | | import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper; |
| | | import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; |
| | | import com.ruoyi.account.pojo.AccountStatement; |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.account.service.AccountStatementService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.bean.BeanUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper; |
| | | import com.ruoyi.stock.mapper.StockInRecordMapper; |
| | | import com.ruoyi.stock.mapper.StockOutRecordMapper; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Random; |
| | | |
| | | /** |
| | | * <p> |
| | | * æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 09:42:47 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public class AccountStatementServiceImpl extends ServiceImpl<AccountStatementMapper, AccountStatement> implements AccountStatementService { |
| | | |
| | | private final AccountStatementMapper accountStatementMapper; |
| | | private final AccountSalesCollectionMapper accountSalesCollectionMapper; |
| | | private final StockOutRecordMapper stockOutRecordMapper; |
| | | private final StockInRecordMapper stockInRecordMapper; |
| | | private final ReturnManagementMapper returnManagementMapper; |
| | | private final AccountStatementDetailsMapper accountStatementDetailsMapper; |
| | | private final AccountPurchasePaymentMapper accountPurchasePaymentMapper; |
| | | private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper; |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd"); |
| | | |
| | | @Override |
| | | public StatementAccountVo getAccountStatementDetailsByMonth(StatementAccountDto statementAccountDto) { |
| | | //对账æä»½è½¬æ¢æå¼å§æ¥æåç»ææ¥æåºé´ |
| | | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM"); |
| | | YearMonth yearMonth = YearMonth.parse(statementAccountDto.getStatementMonth(), formatter); |
| | | statementAccountDto.setStartDate(yearMonth.atDay(1)); |
| | | statementAccountDto.setEndDate(yearMonth.atEndOfMonth()); |
| | | if (statementAccountDto.getAccountType() == 1){ |
| | | //åºæ¶å¯¹è´¦--Customer |
| | | return getAccountStatementDetailsByCustomerAndMonth(statementAccountDto); |
| | | }else { |
| | | //åºä»å¯¹è´¦--SupplierManage |
| | | return getAccountStatementDetailsBySupplierAndMonth(statementAccountDto); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAccountStatement(StatementAccountVo statementAccountVo) { |
| | | //åä¸å®¢æ·æè
åä¸ä¾åºå,ä¸ä¸ªæä»½åªè½æä¸ä¸ªå¯¹è´¦å |
| | | List<AccountStatement> accountStatements = accountStatementMapper.selectList(Wrappers.<AccountStatement>lambdaQuery() |
| | | .eq(AccountStatement::getStatementMonth, statementAccountVo.getStatementMonth()) |
| | | .eq(AccountStatement::getAccountType, statementAccountVo.getAccountType()) |
| | | .eq(AccountStatement::getCustomerId, statementAccountVo.getCustomerId())); |
| | | if (CollectionUtils.isNotEmpty(accountStatements)){ |
| | | throw new ServiceException("åä¸å®¢æ·æè
åä¸ä¾åºå,ä¸ä¸ªæä»½åªè½æä¸ä¸ªå¯¹è´¦å"); |
| | | } |
| | | AccountStatement accountStatement = new AccountStatement(); |
| | | BeanUtils.copyProperties(statementAccountVo, accountStatement); |
| | | accountStatement.setStatementNumber(genStatementAccountNo()); |
| | | boolean save = save(accountStatement); |
| | | statementAccountVo.getAccountStatementDetails().stream().forEach(accountStatementDetails -> { |
| | | accountStatementDetails.setAccountStatementId(accountStatement.getId()); |
| | | //æ·»å 对账åæç» |
| | | accountStatementDetailsMapper.insert(accountStatementDetails); |
| | | }); |
| | | return save; |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountStatement(List<Long> ids) { |
| | | //å é¤å¯¹è´¦åæç» |
| | | accountStatementDetailsMapper.delete(Wrappers.<AccountStatementDetails>lambdaQuery().in(AccountStatementDetails::getAccountStatementId, ids)); |
| | | return removeByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public IPage<StatementAccountVo> listPageAccountStatement(Page page, StatementAccountDto statementAccountDto) { |
| | | return accountStatementMapper.listPageAccountStatement(page, statementAccountDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountStatement(HttpServletResponse response, StatementAccountDto statementAccountDto) { |
| | | List<StatementAccountVo> list = accountStatementMapper.listPageAccountStatement(new Page(1,-1),statementAccountDto).getRecords(); |
| | | ExcelUtil<StatementAccountVo> util = new ExcelUtil<>(StatementAccountVo.class); |
| | | util.exportExcel(response, list , "对账å"); |
| | | } |
| | | |
| | | //æ ¹æ®å®¢æ·åæä»½è·å对账详æ
(éå®) |
| | | private StatementAccountVo getAccountStatementDetailsByCustomerAndMonth(StatementAccountDto statementAccountDto) { |
| | | StatementAccountVo statementAccountVo = new StatementAccountVo(); |
| | | statementAccountVo.setAccountType(1);//åºæ¶å¯¹è´¦ |
| | | List<AccountStatementDetails> accountStatementDetailsList = new ArrayList<>(); |
| | | /*æ¥è¯¢åºåºæç»*/ |
| | | SalesOutboundDto salesOutboundDto = new SalesOutboundDto(); |
| | | salesOutboundDto.setCustomerId(statementAccountDto.getCustomerId()); |
| | | salesOutboundDto.setStartDate(statementAccountDto.getStartDate()); |
| | | salesOutboundDto.setEndDate(statementAccountDto.getEndDate()); |
| | | List<SalesOutboundVo> salesOutboundVos = stockOutRecordMapper.listPageAccountSales(new Page(1, -1), salesOutboundDto).getRecords(); |
| | | salesOutboundVos.stream().forEach(salesOutboundVo -> { |
| | | AccountStatementDetails accountStatementDetails = new AccountStatementDetails(); |
| | | //æ°æ®æ¥æ=åºåºæ¥æ |
| | | accountStatementDetails.setOccurrenceDate(salesOutboundVo.getShippingDate()); |
| | | //åæ®ç¼å·=åºåºåå· |
| | | accountStatementDetails.setReceiptNumber(salesOutboundVo.getOutboundBatches()); |
| | | //ç±»å=åºåº |
| | | accountStatementDetails.setType(1); |
| | | //éé¢=åºåºéé¢ |
| | | accountStatementDetails.setAmount(salesOutboundVo.getOutboundAmount()); |
| | | //夿³¨ |
| | | accountStatementDetails.setRemark("产åéå®åºåºï¼äº§åï¼"+salesOutboundVo.getProductName()); |
| | | accountStatementDetailsList.add(accountStatementDetails); |
| | | }); |
| | | /*æ¥è¯¢æ¶æ¬¾æç»*/ |
| | | List<AccountSalesCollection> accountSalesCollections = accountSalesCollectionMapper.selectList(Wrappers.<AccountSalesCollection>lambdaQuery() |
| | | .eq(AccountSalesCollection::getCustomerId, statementAccountDto.getCustomerId()) |
| | | .between(AccountSalesCollection::getCollectionDate, statementAccountDto.getStartDate(), statementAccountDto.getEndDate())); |
| | | accountSalesCollections.stream().forEach(accountSalesCollection -> { |
| | | AccountStatementDetails accountStatementDetails = new AccountStatementDetails(); |
| | | //æ°æ®æ¥æ=æ¶æ¬¾æ¥æ |
| | | accountStatementDetails.setOccurrenceDate(accountSalesCollection.getCollectionDate()); |
| | | //åæ®ç¼å·=æ¶æ¬¾åå· |
| | | accountStatementDetails.setReceiptNumber(accountSalesCollection.getCollectionNumber()); |
| | | //ç±»å=æ¶æ¬¾ |
| | | accountStatementDetails.setType(3); |
| | | //éé¢=æ¶æ¬¾éé¢ |
| | | accountStatementDetails.setAmount(accountSalesCollection.getCollectionAmount()); |
| | | //夿³¨ |
| | | accountStatementDetails.setRemark("客æ·å款ï¼å¤æ³¨ï¼"+accountSalesCollection.getRemark()); |
| | | accountStatementDetailsList.add(accountStatementDetails); |
| | | }); |
| | | /*æ¥è¯¢éè´§æç»*/ |
| | | SalesReturnDto salesReturnDto = new SalesReturnDto(); |
| | | salesReturnDto.setCustomerId(statementAccountDto.getCustomerId()); |
| | | salesReturnDto.setStartDate(statementAccountDto.getStartDate()); |
| | | salesReturnDto.setEndDate(statementAccountDto.getEndDate()); |
| | | List<SalesReturnVo> salesReturnVos = returnManagementMapper.listPageAccountSalesReturn(new Page(1, -1), salesReturnDto).getRecords(); |
| | | salesReturnVos.stream().forEach(salesReturnVo -> { |
| | | AccountStatementDetails accountStatementDetails = new AccountStatementDetails(); |
| | | //æ°æ®æ¥æ=éè´§æ¥æ |
| | | accountStatementDetails.setOccurrenceDate(salesReturnVo.getMakeTime().toLocalDate()); |
| | | //åæ®ç¼å·=éè´§åå· |
| | | accountStatementDetails.setReceiptNumber(salesReturnVo.getReturnNo()); |
| | | //ç±»å=éè´§ |
| | | accountStatementDetails.setType(5); |
| | | //éé¢=鿬¾éé¢ |
| | | accountStatementDetails.setAmount(salesReturnVo.getRefundAmount()); |
| | | //夿³¨ |
| | | accountStatementDetails.setRemark("产åéè´§ï¼åå ï¼"+salesReturnVo.getReturnReason()); |
| | | accountStatementDetailsList.add(accountStatementDetails); |
| | | }); |
| | | //æåä½é¢=ä¸ä¸ªæçææ«ä½é¢ |
| | | statementAccountVo.setOpeningBalance(BigDecimal.ZERO); |
| | | List<AccountStatement> accountStatements = accountStatementMapper.selectList(Wrappers.<AccountStatement>lambdaQuery() |
| | | .eq(AccountStatement::getAccountType, 1) |
| | | .eq(AccountStatement::getCustomerId, statementAccountDto.getCustomerId()) |
| | | .eq(AccountStatement::getStatementMonth, |
| | | YearMonth.parse(statementAccountDto.getStatementMonth()).minusMonths(1).toString())); |
| | | if (CollectionUtils.isNotEmpty(accountStatements)){ |
| | | statementAccountVo.setOpeningBalance(accountStatements.get(accountStatements.size() - 1).getClosingBalance()); |
| | | } |
| | | //æ¬æåºæ¶=åºåº-éè´§éé¢ç´¯è®¡ |
| | | statementAccountVo.setCurrentPlan(salesOutboundVos.stream().map(SalesOutboundVo::getOutboundAmount).reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | .subtract(salesReturnVos.stream().map(SalesReturnVo::getRefundAmount).reduce(BigDecimal.ZERO, BigDecimal::add))); |
| | | //æ¬ææ¶æ¬¾=æ¶æ¬¾éé¢ç´¯è®¡ |
| | | statementAccountVo.setCurrentActually(accountSalesCollections.stream().map(AccountSalesCollection::getCollectionAmount).reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | //ææ«ä½é¢=æå+åºæ¶-æ¶æ¬¾ |
| | | statementAccountVo.setClosingBalance(statementAccountVo.getOpeningBalance().add(statementAccountVo.getCurrentPlan()).subtract(statementAccountVo.getCurrentActually())); |
| | | statementAccountVo.setAccountStatementDetails(accountStatementDetailsList); |
| | | return statementAccountVo; |
| | | } |
| | | |
| | | //æ ¹æ®ä¾åºååæä»½è·å对账详æ
(éè´) |
| | | private StatementAccountVo getAccountStatementDetailsBySupplierAndMonth(StatementAccountDto statementAccountDto) { |
| | | StatementAccountVo statementAccountVo = new StatementAccountVo(); |
| | | statementAccountVo.setAccountType(2);//åºä»å¯¹è´¦ |
| | | List<AccountStatementDetails> accountStatementDetailsList = new ArrayList<>(); |
| | | /*æ¥è¯¢å
¥åºæç»*/ |
| | | PurchaseInboundDto purchaseInboundDto = new PurchaseInboundDto(); |
| | | purchaseInboundDto.setSupplierId(statementAccountDto.getCustomerId()); |
| | | purchaseInboundDto.setStartDate(statementAccountDto.getStartDate()); |
| | | purchaseInboundDto.setEndDate(statementAccountDto.getEndDate()); |
| | | List<PurchaseInboundVo> purchaseInboundVos = stockInRecordMapper.listPageAccountPurchase(new Page(1, -1), purchaseInboundDto).getRecords(); |
| | | purchaseInboundVos.stream().forEach(purchaseInboundVo -> { |
| | | AccountStatementDetails accountStatementDetails = new AccountStatementDetails(); |
| | | //æ°æ®æ¥æ=å
¥åºæ¥æ |
| | | accountStatementDetails.setOccurrenceDate(purchaseInboundVo.getInboundDate()); |
| | | //åæ®ç¼å·=å
¥åºåå· |
| | | accountStatementDetails.setReceiptNumber(purchaseInboundVo.getInboundBatches()); |
| | | //ç±»å=å
¥åº |
| | | accountStatementDetails.setType(2); |
| | | //éé¢=å
¥åºéé¢ |
| | | accountStatementDetails.setAmount(purchaseInboundVo.getInboundAmount()); |
| | | //夿³¨ |
| | | accountStatementDetails.setRemark("产åéè´å
¥åºï¼äº§åï¼"+purchaseInboundVo.getProductName()); |
| | | accountStatementDetailsList.add(accountStatementDetails); |
| | | }); |
| | | /*æ¥è¯¢ä»æ¬¾æç»*/ |
| | | List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery() |
| | | .eq(AccountPurchasePayment::getSupplierId, statementAccountDto.getCustomerId()) |
| | | .between(AccountPurchasePayment::getPaymentDate, statementAccountDto.getStartDate(), statementAccountDto.getEndDate())); |
| | | accountPurchasePayments.stream().forEach(accountPurchasePayment -> { |
| | | AccountStatementDetails accountStatementDetails = new AccountStatementDetails(); |
| | | //æ°æ®æ¥æ=仿¬¾æ¥æ |
| | | accountStatementDetails.setOccurrenceDate(accountPurchasePayment.getPaymentDate()); |
| | | //åæ®ç¼å·=仿¬¾åå· |
| | | accountStatementDetails.setReceiptNumber(accountPurchasePayment.getPaymentNumber()); |
| | | //ç±»å=仿¬¾ |
| | | accountStatementDetails.setType(4); |
| | | //éé¢=仿¬¾éé¢ |
| | | accountStatementDetails.setAmount(accountPurchasePayment.getPaymentAmount()); |
| | | //夿³¨ |
| | | accountStatementDetails.setRemark("æ¯ä»è´§æ¬¾ï¼å¤æ³¨ï¼"+accountPurchasePayment.getRemark()); |
| | | accountStatementDetailsList.add(accountStatementDetails); |
| | | }); |
| | | /*æ¥è¯¢éè´§æç»*/ |
| | | PurchaseReturnDto purchaseReturnDto = new PurchaseReturnDto(); |
| | | purchaseReturnDto.setSupplierId(statementAccountDto.getCustomerId()); |
| | | purchaseReturnDto.setStartDate(statementAccountDto.getStartDate()); |
| | | purchaseReturnDto.setEndDate(statementAccountDto.getEndDate()); |
| | | List<PurchaseReturnVo> purchaseReturnVos = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1, -1), purchaseReturnDto).getRecords(); |
| | | purchaseReturnVos.stream().forEach(purchaseReturnVo -> { |
| | | AccountStatementDetails accountStatementDetails = new AccountStatementDetails(); |
| | | //æ°æ®æ¥æ=éè´§æ¥æ |
| | | accountStatementDetails.setOccurrenceDate(purchaseReturnVo.getPreparedAt().toLocalDate()); |
| | | //åæ®ç¼å·=éè´§åå· |
| | | accountStatementDetails.setReceiptNumber(purchaseReturnVo.getReturnNo()); |
| | | //ç±»å=éè´§ |
| | | accountStatementDetails.setType(5); |
| | | //éé¢=鿬¾éé¢ |
| | | accountStatementDetails.setAmount(purchaseReturnVo.getTotalAmount()); |
| | | //夿³¨ |
| | | accountStatementDetails.setRemark("产åéè´§ï¼éè´§æ¹å¼ï¼"+purchaseReturnVo.getReturnType()); |
| | | accountStatementDetailsList.add(accountStatementDetails); |
| | | }); |
| | | //æåä½é¢=ä¸ä¸ªæçææ«ä½é¢ |
| | | statementAccountVo.setOpeningBalance(BigDecimal.ZERO); |
| | | List<AccountStatement> accountStatements = accountStatementMapper.selectList(Wrappers.<AccountStatement>lambdaQuery() |
| | | .eq(AccountStatement::getAccountType, 2) |
| | | .eq(AccountStatement::getCustomerId, statementAccountDto.getCustomerId()) |
| | | .eq(AccountStatement::getStatementMonth, |
| | | YearMonth.parse(statementAccountDto.getStatementMonth()).minusMonths(1).toString())); |
| | | if (CollectionUtils.isNotEmpty(accountStatements)){ |
| | | statementAccountVo.setOpeningBalance(accountStatements.get(accountStatements.size() - 1).getClosingBalance()); |
| | | } |
| | | //æ¬æåºä»=å
¥åº-éè´§éé¢ç´¯è®¡ |
| | | statementAccountVo.setCurrentPlan(purchaseInboundVos.stream().map(PurchaseInboundVo::getInboundAmount).reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | .subtract(purchaseReturnVos.stream().map(PurchaseReturnVo::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add))); |
| | | //æ¬æä»æ¬¾=仿¬¾éé¢ç´¯è®¡ |
| | | statementAccountVo.setCurrentActually(accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentAmount).reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | //ææ«ä½é¢=æå+åºæ¶-æ¶æ¬¾ |
| | | statementAccountVo.setClosingBalance(statementAccountVo.getOpeningBalance().add(statementAccountVo.getCurrentPlan()).subtract(statementAccountVo.getCurrentActually())); |
| | | statementAccountVo.setAccountStatementDetails(accountStatementDetailsList); |
| | | return statementAccountVo; |
| | | } |
| | | |
| | | private String genStatementAccountNo() { |
| | | return "DZ" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.dto.DeviceTypeDetail; |
| | | import com.ruoyi.account.dto.DeviceTypeDistributionVO; |
| | | import com.ruoyi.account.mapper.BorrowInfoMapper; |
| | | import com.ruoyi.account.pojo.BorrowInfo; |
| | | import com.ruoyi.account.bean.dto.AccountReportDto; |
| | | import com.ruoyi.account.bean.dto.DeviceTypeDetail; |
| | | import com.ruoyi.account.bean.dto.DeviceTypeDistributionVO; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.AccountReportVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesReturnVo; |
| | | import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper; |
| | | import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.account.service.AccountingService; |
| | | import com.ruoyi.device.mapper.DeviceLedgerMapper; |
| | | import com.ruoyi.device.pojo.DeviceLedger; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.procurementrecord.mapper.CustomStorageMapper; |
| | | import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper; |
| | | import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper; |
| | | import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper; |
| | | import com.ruoyi.procurementrecord.pojo.CustomStorage; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper; |
| | | import com.ruoyi.stock.mapper.StockInRecordMapper; |
| | | import com.ruoyi.stock.mapper.StockOutRecordMapper; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.Year; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class AccountingServiceImpl { |
| | | public class AccountingServiceImpl implements AccountingService { |
| | | |
| | | private final DeviceLedgerMapper deviceLedgerMapper; |
| | | private final BorrowInfoMapper borrowInfoMapper; |
| | | private final CustomStorageMapper customStorageMapper; |
| | | private final ProcurementRecordMapper procurementRecordMapper; |
| | | private final ProcurementRecordOutMapper procurementRecordOutMapper; |
| | | private final AccountSalesCollectionMapper accountSalesCollectionMapper; |
| | | private final AccountPurchasePaymentMapper accountPurchasePaymentMapper; |
| | | private final StockOutRecordMapper stockOutRecordMapper; |
| | | private final ReturnManagementMapper returnManagementMapper; |
| | | private final StockInRecordMapper stockInRecordMapper; |
| | | private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper; |
| | | |
| | | |
| | | @Override |
| | | public AjaxResult total(Integer year) { |
| | | Map<String,Object> map = new HashMap<>(); |
| | | map.put("deprAmount",0); // ææ§éé¢ |
| | |
| | | map.put("netValue",reduce.subtract(total)); |
| | | } |
| | | // è´åº |
| | | LambdaQueryWrapper<BorrowInfo> borrowInfoLambdaQueryWrapper = new LambdaQueryWrapper<>(); |
| | | borrowInfoLambdaQueryWrapper.like(BorrowInfo::getCreateTime,year) |
| | | .eq(BorrowInfo::getStatus,1); |
| | | List<BorrowInfo> borrowInfos = borrowInfoMapper.selectList(borrowInfoLambdaQueryWrapper); |
| | | if(CollectionUtils.isNotEmpty(borrowInfos)){ |
| | | BigDecimal reduce = borrowInfos.stream() |
| | | .map(BorrowInfo::getBorrowAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | map.put("debt",reduce); |
| | | } |
| | | map.put("debt",BigDecimal.ZERO); |
| | | |
| | | // åºåèµäº§ |
| | | LambdaQueryWrapper<ProcurementRecordStorage> procurementRecordStorageLambdaQueryWrapper = new LambdaQueryWrapper<>(); |
| | | procurementRecordStorageLambdaQueryWrapper.like(ProcurementRecordStorage::getCreateTime,year); |
| | |
| | | return totalDepreciation.setScale(2, BigDecimal.ROUND_HALF_UP); |
| | | } |
| | | |
| | | @Override |
| | | public AjaxResult deviceTypeDistribution(Integer year) { |
| | | // 2. ç»è£
è¿åVO |
| | | DeviceTypeDistributionVO vo = new DeviceTypeDistributionVO(); |
| | |
| | | return AjaxResult.success(vo); |
| | | } |
| | | |
| | | @Override |
| | | public AjaxResult calculateDepreciation(Page page, Integer year) { |
| | | LambdaQueryWrapper<DeviceLedger> deviceLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>(); |
| | | deviceLedgerLambdaQueryWrapper.like(DeviceLedger::getCreateTime,year) |
| | |
| | | } |
| | | return AjaxResult.success(deviceLedgerIPage); |
| | | } |
| | | |
| | | @Override |
| | | public AccountReportVo getAccountStatementDetailsByMonth(AccountReportDto accountReportDto) { |
| | | AccountReportVo accountReportVo = new AccountReportVo(); |
| | | LocalDate start = accountReportDto.getEntryDateStart(); |
| | | LocalDate end = accountReportDto.getEntryDateEnd(); |
| | | DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM"); |
| | | |
| | | // ========== 1. é¡¶é¨å¡çæ°æ® ========== |
| | | // 1.1 æ»è¥æ¶ = æ¶æ¬¾åæ»éé¢ |
| | | List<AccountSalesCollection> accountSalesCollections = accountSalesCollectionMapper.selectList( |
| | | Wrappers.<AccountSalesCollection>lambdaQuery() |
| | | .between(AccountSalesCollection::getCollectionDate, start, end) |
| | | ); |
| | | BigDecimal totalIncome = Optional.of( |
| | | accountSalesCollections.stream() |
| | | .map(AccountSalesCollection::getCollectionAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | ).orElse(BigDecimal.ZERO); |
| | | accountReportVo.setTotalIncome(totalIncome); |
| | | // 1.2 æ»æ¯åº = 仿¬¾åæ»éé¢ |
| | | List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList( |
| | | Wrappers.<AccountPurchasePayment>lambdaQuery() |
| | | .between(AccountPurchasePayment::getPaymentDate, start, end) |
| | | ); |
| | | BigDecimal totalExpense = Optional.of( |
| | | accountPurchasePayments.stream() |
| | | .map(AccountPurchasePayment::getPaymentAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | ).orElse(BigDecimal.ZERO); |
| | | accountReportVo.setTotalExpense(totalExpense); |
| | | // 1.3 åºæ¶è´¦æ¬¾ = éå®åºåºéé¢å计 - éå®éè´§éé¢å计 |
| | | SalesOutboundDto salesOutboundDto = new SalesOutboundDto(); |
| | | salesOutboundDto.setStartDate(accountReportDto.getEntryDateStart()); |
| | | salesOutboundDto.setEndDate(accountReportDto.getEntryDateEnd()); |
| | | List<SalesOutboundVo> salesOutboundVos = stockOutRecordMapper.listPageAccountSales(new Page(1, -1), salesOutboundDto).getRecords(); |
| | | BigDecimal salesOutAmount = Optional.of( |
| | | salesOutboundVos.stream() |
| | | .map(SalesOutboundVo::getOutboundAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | ).orElse(BigDecimal.ZERO); |
| | | SalesReturnDto salesReturnDto = new SalesReturnDto(); |
| | | salesReturnDto.setStartDate(accountReportDto.getEntryDateStart()); |
| | | salesReturnDto.setEndDate(accountReportDto.getEntryDateEnd()); |
| | | List<SalesReturnVo> salesReturnVos = returnManagementMapper.listPageAccountSalesReturn(new Page(1, -1), salesReturnDto).getRecords(); |
| | | BigDecimal salesReturnAmount = Optional.of( |
| | | salesReturnVos.stream() |
| | | .map(SalesReturnVo::getRefundAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | ).orElse(BigDecimal.ZERO); |
| | | accountReportVo.setAccountsReceivable(salesOutAmount.subtract(salesReturnAmount)); |
| | | // 1.4 åºä»è´¦æ¬¾ = éè´å
¥åºéé¢å计 - éè´éè´§éé¢å计 |
| | | PurchaseInboundDto purchaseInboundDto = new PurchaseInboundDto(); |
| | | purchaseInboundDto.setStartDate(accountReportDto.getEntryDateStart()); |
| | | purchaseInboundDto.setEndDate(accountReportDto.getEntryDateEnd()); |
| | | List<PurchaseInboundVo> purchaseInboundVos = stockInRecordMapper.listPageAccountPurchase(new Page(1, -1), purchaseInboundDto).getRecords(); |
| | | BigDecimal purchaseInAmount = Optional.of( |
| | | purchaseInboundVos.stream() |
| | | .map(PurchaseInboundVo::getInboundAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | ).orElse(BigDecimal.ZERO); |
| | | PurchaseReturnDto purchaseReturnDto = new PurchaseReturnDto(); |
| | | purchaseReturnDto.setStartDate(accountReportDto.getEntryDateStart()); |
| | | purchaseReturnDto.setEndDate(accountReportDto.getEntryDateEnd()); |
| | | List<PurchaseReturnVo> purchaseReturnVos = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1, -1), purchaseReturnDto).getRecords(); |
| | | BigDecimal purchaseReturnAmount = Optional.of( |
| | | purchaseReturnVos.stream() |
| | | .map(PurchaseReturnVo::getTotalAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | ).orElse(BigDecimal.ZERO); |
| | | accountReportVo.setAccountsPayable(purchaseInAmount.subtract(purchaseReturnAmount)); |
| | | // 1.5 å婿¶¦ = æ»è¥æ¶ - æ»æ¯åº |
| | | BigDecimal netProfit = totalIncome.subtract(totalExpense); |
| | | accountReportVo.setNetRevenue(netProfit); |
| | | |
| | | // ========== 2. æçº¿å¾ï¼æåº¦è¥æ¶/æ¯åº/å婿¶¦è¶å¿ ========== |
| | | Map<String, BigDecimal> monthIncomeMap = new HashMap<>(); |
| | | Map<String, BigDecimal> monthExpenseMap = new HashMap<>(); |
| | | // æåº¦è¥æ¶ |
| | | accountSalesCollections.forEach(item -> { |
| | | String month = item.getCollectionDate().format(monthFormatter); |
| | | monthIncomeMap.put(month, monthIncomeMap.getOrDefault(month, BigDecimal.ZERO) |
| | | .add(Optional.ofNullable(item.getCollectionAmount()).orElse(BigDecimal.ZERO))); |
| | | }); |
| | | // æåº¦æ¯åº |
| | | accountPurchasePayments.forEach(item -> { |
| | | String month = item.getPaymentDate().format(monthFormatter); |
| | | monthExpenseMap.put(month, monthExpenseMap.getOrDefault(month, BigDecimal.ZERO) |
| | | .add(Optional.ofNullable(item.getPaymentAmount()).orElse(BigDecimal.ZERO))); |
| | | }); |
| | | // çæè¿ç»æä»½å表 |
| | | List<String> monthList = new ArrayList<>(); |
| | | LocalDate current = start.withDayOfMonth(1); |
| | | while (!current.isAfter(end.withDayOfMonth(1))) { |
| | | monthList.add(current.format(monthFormatter)); |
| | | current = current.plusMonths(1); |
| | | } |
| | | // ç»è£
è¶å¿æ°æ® |
| | | List<AccountReportVo.MonthlyTrendVO> trendList = new ArrayList<>(); |
| | | for (String month : monthList) { |
| | | BigDecimal income = monthIncomeMap.getOrDefault(month, BigDecimal.ZERO); |
| | | BigDecimal expense = monthExpenseMap.getOrDefault(month, BigDecimal.ZERO); |
| | | AccountReportVo.MonthlyTrendVO trend = new AccountReportVo.MonthlyTrendVO(); |
| | | trend.setMonth(month); |
| | | trend.setIncome(income); |
| | | trend.setExpense(expense); |
| | | trend.setProfit(income.subtract(expense)); |
| | | trendList.add(trend); |
| | | } |
| | | accountReportVo.setMonthlyTrendList(trendList); |
| | | |
| | | // ========== 3. æ±ç¶å¾ï¼æåº¦åºæ¶/åºä»æ°æ® ========== |
| | | Map<String, BigDecimal> monthReceivableMap = new HashMap<>(); |
| | | Map<String, BigDecimal> monthPayableMap = new HashMap<>(); |
| | | // æåº¦åºæ¶ï¼éå®åºåº-éè´§ï¼ |
| | | salesOutboundVos.forEach(item -> { |
| | | String month = item.getShippingDate().format(monthFormatter); |
| | | monthReceivableMap.put(month, monthReceivableMap.getOrDefault(month, BigDecimal.ZERO) |
| | | .add(Optional.ofNullable(item.getOutboundAmount()).orElse(BigDecimal.ZERO))); |
| | | }); |
| | | salesReturnVos.forEach(item -> { |
| | | String month = item.getMakeTime().format(monthFormatter); |
| | | monthReceivableMap.put(month, monthReceivableMap.getOrDefault(month, BigDecimal.ZERO) |
| | | .subtract(Optional.ofNullable(item.getRefundAmount()).orElse(BigDecimal.ZERO))); |
| | | }); |
| | | |
| | | // æåº¦åºä»ï¼éè´å
¥åº-éè´§ï¼ |
| | | purchaseInboundVos.forEach(item -> { |
| | | String month = item.getInboundDate().format(monthFormatter); |
| | | monthPayableMap.put(month, monthPayableMap.getOrDefault(month, BigDecimal.ZERO) |
| | | .add(Optional.ofNullable(item.getInboundAmount()).orElse(BigDecimal.ZERO))); |
| | | }); |
| | | purchaseReturnVos.forEach(item -> { |
| | | String month = item.getPreparedAt().format(monthFormatter); |
| | | monthPayableMap.put(month, monthPayableMap.getOrDefault(month, BigDecimal.ZERO) |
| | | .subtract(Optional.ofNullable(item.getTotalAmount()).orElse(BigDecimal.ZERO))); |
| | | }); |
| | | // ç»è£
åºæ¶åºä»æ°æ® |
| | | List<AccountReportVo.ReceivablePayableVO> rpList = new ArrayList<>(); |
| | | for (String month : monthList) { |
| | | AccountReportVo.ReceivablePayableVO rp = new AccountReportVo.ReceivablePayableVO(); |
| | | rp.setMonth(month); |
| | | rp.setReceivable(monthReceivableMap.getOrDefault(month, BigDecimal.ZERO)); |
| | | rp.setPayable(monthPayableMap.getOrDefault(month, BigDecimal.ZERO)); |
| | | rpList.add(rp); |
| | | } |
| | | accountReportVo.setReceivablePayableList(rpList); |
| | | return accountReportVo; |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.account.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.ruoyi.account.dto.SalesReceiptReturnDto; |
| | | import com.ruoyi.account.bean.dto.SalesReceiptReturnDto; |
| | | import com.ruoyi.account.pojo.SalesReceiptReturn; |
| | | import com.ruoyi.account.mapper.SalesReceiptReturnMapper; |
| | | import com.ruoyi.account.service.SalesReceiptReturnService; |
| | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto; |
| | | import com.ruoyi.account.mapper.SalesRefundAmountOrderMapper; |
| | | import com.ruoyi.account.pojo.SalesRefundAmountOrder; |
| | | import com.ruoyi.account.service.SalesRefundAmountOrderService; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.financial.AccountSubjectDto; |
| | | import com.ruoyi.account.bean.dto.financial.AccountSubjectImportDto; |
| | | import com.ruoyi.account.bean.vo.financial.AccountSubjectVo; |
| | | import com.ruoyi.account.mapper.financial.AccountSubjectMapper; |
| | | import com.ruoyi.account.pojo.financial.AccountSubject; |
| | | import com.ruoyi.account.service.purchase.AccountSubjectService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * <p> |
| | | * æ»è´¦ç§ç®è¡¨ æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountSubjectServiceImpl extends ServiceImpl<AccountSubjectMapper, AccountSubject> implements AccountSubjectService { |
| | | |
| | | private final AccountSubjectMapper accountSubjectMapper; |
| | | |
| | | @Override |
| | | public IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) { |
| | | Page<AccountSubjectDto> requestPage = page == null ? new Page<>(1, 10) : page; |
| | | List<AccountSubject> allSubjects = list(loadBaseQueryWrapper(accountSubjectDto)); |
| | | List<AccountSubject> filteredSubjects = applyTreeFilter(allSubjects, accountSubjectDto); |
| | | List<AccountSubjectVo> fullTree = buildTree(filteredSubjects); |
| | | |
| | | long current = requestPage.getCurrent() <= 0 ? 1 : requestPage.getCurrent(); |
| | | long size = requestPage.getSize() <= 0 ? 10 : requestPage.getSize(); |
| | | int fromIndex = (int) Math.min((current - 1) * size, fullTree.size()); |
| | | int toIndex = (int) Math.min(fromIndex + size, fullTree.size()); |
| | | List<AccountSubjectVo> pagedRoots = fromIndex >= toIndex |
| | | ? Collections.emptyList() |
| | | : fullTree.subList(fromIndex, toIndex); |
| | | |
| | | Page<AccountSubjectVo> resultPage = new Page<>(current, size, fullTree.size()); |
| | | resultPage.setRecords(pagedRoots); |
| | | return resultPage; |
| | | } |
| | | |
| | | @Override |
| | | public Boolean saveAccountSubject(AccountSubjectDto accountSubjectDto) { |
| | | validateSubjectRequiredFields(accountSubjectDto); |
| | | validateSubjectCodeUnique(accountSubjectDto, false); |
| | | validateParent(accountSubjectDto.getParentId(), null); |
| | | if (accountSubjectDto.getStatus() == null) { |
| | | accountSubjectDto.setStatus(0); |
| | | } |
| | | return save(accountSubjectDto); |
| | | } |
| | | |
| | | @Override |
| | | public Boolean updateAccountSubject(AccountSubjectDto accountSubjectDto) { |
| | | if (accountSubjectDto == null || accountSubjectDto.getId() == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼ç§ç®IDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (getById(accountSubjectDto.getId()) == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼æªæ¾å°å¯¹åºç§ç®"); |
| | | } |
| | | validateParent(accountSubjectDto.getParentId(), accountSubjectDto.getId()); |
| | | validateSubjectRequiredFields(accountSubjectDto); |
| | | validateSubjectCodeUnique(accountSubjectDto, true); |
| | | return updateById(accountSubjectDto); |
| | | } |
| | | |
| | | @Override |
| | | public Boolean removeAccountSubjectByIds(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | return true; |
| | | } |
| | | List<AccountSubject> allSubjects = list(); |
| | | if (allSubjects == null || allSubjects.isEmpty()) { |
| | | return true; |
| | | } |
| | | Map<Long, List<Long>> childrenIdMap = buildChildrenIdMap(allSubjects); |
| | | Set<Long> removeIds = new LinkedHashSet<>(); |
| | | for (Long id : ids) { |
| | | collectDescendantIds(id, childrenIdMap, removeIds); |
| | | } |
| | | if (removeIds.isEmpty()) { |
| | | return true; |
| | | } |
| | | List<String> subjectCodes = allSubjects.stream() |
| | | .filter(subject -> removeIds.contains(subject.getId())) |
| | | .map(AccountSubject::getSubjectCode) |
| | | .filter(StringUtils::isNotEmpty) |
| | | .collect(Collectors.toList()); |
| | | if (!subjectCodes.isEmpty()) { |
| | | Long referencedCount = accountSubjectMapper.countReferencedBySubjectCodes(subjectCodes); |
| | | if (referencedCount != null && referencedCount > 0) { |
| | | throw new ServiceException("å é¤å¤±è´¥ï¼ç§ç®å·²è¢«åè¯åå½å¼ç¨"); |
| | | } |
| | | } |
| | | return removeByIds(removeIds); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSubject(HttpServletResponse response) { |
| | | List<AccountSubject> list = accountSubjectMapper.selectList(null); |
| | | List<AccountSubjectImportDto> importDtos = list.stream().map(accountSubject -> { |
| | | AccountSubjectImportDto accountSubjectImportDto = new AccountSubjectImportDto(); |
| | | BeanUtils.copyProperties(accountSubject, accountSubjectImportDto); |
| | | return accountSubjectImportDto; |
| | | }).collect(Collectors.toList()); |
| | | ExcelUtil<AccountSubjectImportDto> util = new ExcelUtil<>(AccountSubjectImportDto.class); |
| | | util.exportExcel(response, importDtos , "æ»è´¦ç§ç®"); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªç§ç®å¿
å¡«åæ®µï¼é¿å
èæ°æ®åå
¥ã |
| | | */ |
| | | private void validateSubjectRequiredFields(AccountSubjectDto accountSubjectDto) { |
| | | if (accountSubjectDto == null) { |
| | | throw new ServiceException("æ»è´¦ç§ç®æ°æ®ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(accountSubjectDto.getSubjectCode())) { |
| | | throw new ServiceException("ç§ç®ç¼ç ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(accountSubjectDto.getSubjectName())) { |
| | | throw new ServiceException("ç§ç®åç§°ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(accountSubjectDto.getSubjectType())) { |
| | | throw new ServiceException("ç§ç®ç±»åä¸è½ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªç§ç®ç¼ç å¯ä¸ï¼æ°å¢åä¿®æ¹é½è¦æ§è¡ã |
| | | */ |
| | | private void validateSubjectCodeUnique(AccountSubjectDto accountSubjectDto, boolean isUpdate) { |
| | | LambdaQueryWrapper<AccountSubject> codeQueryWrapper = new LambdaQueryWrapper<>(); |
| | | codeQueryWrapper.eq(AccountSubject::getSubjectCode, accountSubjectDto.getSubjectCode()); |
| | | if (isUpdate) { |
| | | codeQueryWrapper.ne(AccountSubject::getId, accountSubjectDto.getId()); |
| | | } |
| | | AccountSubject exists = getOne(codeQueryWrapper, false); |
| | | if (Objects.nonNull(exists)) { |
| | | throw new ServiceException("ç§ç®ç¼ç å·²åå¨ï¼è¯·å¿éå¤æäº¤"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä»
æéç¨è¿æ»¤æ¡ä»¶æ¥è¯¢åºç¡æ°æ®ï¼æ å½¢è¿æ»¤åç»ååï¼ã |
| | | */ |
| | | private LambdaQueryWrapper<AccountSubject> loadBaseQueryWrapper(AccountSubjectDto accountSubjectDto) { |
| | | LambdaQueryWrapper<AccountSubject> queryWrapper = new LambdaQueryWrapper<>(); |
| | | if (accountSubjectDto != null && accountSubjectDto.getStatus() != null) { |
| | | queryWrapper.eq(AccountSubject::getStatus, accountSubjectDto.getStatus()); |
| | | } |
| | | queryWrapper.orderByDesc(AccountSubject::getSubjectCode).orderByDesc(AccountSubject::getId); |
| | | return queryWrapper; |
| | | } |
| | | |
| | | /** |
| | | * æ å½¢è¿æ»¤ï¼å½ä¸èç¹åä¿çå
¶ç¶é¾ä¸åæ ï¼ä¿è¯éå½ç»æå®æ´ã |
| | | */ |
| | | private List<AccountSubject> applyTreeFilter(List<AccountSubject> allSubjects, AccountSubjectDto queryDto) { |
| | | if (allSubjects == null || allSubjects.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | boolean hasFilter = queryDto != null && ( |
| | | StringUtils.isNotEmpty(queryDto.getSubjectCode()) |
| | | || StringUtils.isNotEmpty(queryDto.getSubjectName()) |
| | | || StringUtils.isNotEmpty(queryDto.getSubjectType()) |
| | | ); |
| | | if (!hasFilter) { |
| | | return allSubjects; |
| | | } |
| | | |
| | | Map<Long, AccountSubject> subjectMap = allSubjects.stream() |
| | | .filter(item -> item.getId() != null) |
| | | .collect(Collectors.toMap(AccountSubject::getId, item -> item, (a, b) -> a, LinkedHashMap::new)); |
| | | Map<Long, List<AccountSubject>> childrenMap = buildChildrenMap(allSubjects); |
| | | |
| | | Set<Long> matchedIds = new LinkedHashSet<>(); |
| | | for (AccountSubject subject : allSubjects) { |
| | | if (subject.getId() == null) { |
| | | continue; |
| | | } |
| | | if (matchesFilter(subject, queryDto)) { |
| | | matchedIds.add(subject.getId()); |
| | | } |
| | | } |
| | | if (matchedIds.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | Set<Long> resultIds = new LinkedHashSet<>(matchedIds); |
| | | for (Long matchedId : matchedIds) { |
| | | addAncestors(matchedId, subjectMap, resultIds); |
| | | addDescendants(matchedId, childrenMap, resultIds); |
| | | } |
| | | |
| | | return allSubjects.stream() |
| | | .filter(item -> item.getId() != null && resultIds.contains(item.getId())) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | private boolean matchesFilter(AccountSubject subject, AccountSubjectDto queryDto) { |
| | | if (queryDto == null) { |
| | | return true; |
| | | } |
| | | if (StringUtils.isNotEmpty(queryDto.getSubjectCode()) |
| | | && (subject.getSubjectCode() == null || !subject.getSubjectCode().contains(queryDto.getSubjectCode()))) { |
| | | return false; |
| | | } |
| | | if (StringUtils.isNotEmpty(queryDto.getSubjectName()) |
| | | && (subject.getSubjectName() == null || !subject.getSubjectName().contains(queryDto.getSubjectName()))) { |
| | | return false; |
| | | } |
| | | if (StringUtils.isNotEmpty(queryDto.getSubjectType()) |
| | | && !queryDto.getSubjectType().equals(subject.getSubjectType())) { |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private void addAncestors(Long subjectId, Map<Long, AccountSubject> subjectMap, Set<Long> resultIds) { |
| | | AccountSubject current = subjectMap.get(subjectId); |
| | | if (current == null) { |
| | | return; |
| | | } |
| | | Long parentId = current.getParentId(); |
| | | while (parentId != null && parentId > 0) { |
| | | AccountSubject parent = subjectMap.get(parentId); |
| | | if (parent == null) { |
| | | break; |
| | | } |
| | | if (!resultIds.add(parent.getId())) { |
| | | break; |
| | | } |
| | | parentId = parent.getParentId(); |
| | | } |
| | | } |
| | | |
| | | private void addDescendants(Long subjectId, Map<Long, List<AccountSubject>> childrenMap, Set<Long> resultIds) { |
| | | List<AccountSubject> children = childrenMap.getOrDefault(subjectId, Collections.emptyList()); |
| | | for (AccountSubject child : children) { |
| | | if (child.getId() == null) { |
| | | continue; |
| | | } |
| | | if (resultIds.add(child.getId())) { |
| | | addDescendants(child.getId(), childrenMap, resultIds); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Map<Long, List<AccountSubject>> buildChildrenMap(List<AccountSubject> subjects) { |
| | | Map<Long, List<AccountSubject>> childrenMap = new HashMap<>(); |
| | | for (AccountSubject subject : subjects) { |
| | | if (subject.getId() == null) { |
| | | continue; |
| | | } |
| | | Long parentId = subject.getParentId(); |
| | | if (parentId == null || parentId <= 0) { |
| | | continue; |
| | | } |
| | | childrenMap.computeIfAbsent(parentId, key -> new ArrayList<>()).add(subject); |
| | | } |
| | | return childrenMap; |
| | | } |
| | | |
| | | /** |
| | | * åºäº parentId éå½æå»ºç§ç®æ ã |
| | | */ |
| | | private List<AccountSubjectVo> buildTree(List<AccountSubject> subjects) { |
| | | if (subjects == null || subjects.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<AccountSubject> sortedSubjects = new ArrayList<>(subjects); |
| | | sortedSubjects.sort(Comparator |
| | | .comparing(AccountSubject::getSubjectCode, Comparator.nullsFirst(String::compareTo)).reversed() |
| | | .thenComparing(AccountSubject::getId, Comparator.nullsFirst(Long::compareTo)).reversed()); |
| | | |
| | | Map<Long, AccountSubjectVo> subjectVoMap = new LinkedHashMap<>(); |
| | | for (AccountSubject subject : sortedSubjects) { |
| | | if (subject.getId() == null) { |
| | | continue; |
| | | } |
| | | AccountSubjectVo vo = new AccountSubjectVo(); |
| | | BeanUtils.copyProperties(subject, vo); |
| | | subjectVoMap.put(subject.getId(), vo); |
| | | } |
| | | |
| | | List<AccountSubjectVo> roots = new ArrayList<>(); |
| | | for (AccountSubject subject : sortedSubjects) { |
| | | if (subject.getId() == null) { |
| | | continue; |
| | | } |
| | | AccountSubjectVo current = subjectVoMap.get(subject.getId()); |
| | | Long parentId = subject.getParentId(); |
| | | if (parentId != null && parentId > 0 && subjectVoMap.containsKey(parentId)) { |
| | | subjectVoMap.get(parentId).getChildren().add(current); |
| | | } else { |
| | | roots.add(current); |
| | | } |
| | | } |
| | | |
| | | markLeafRecursively(roots); |
| | | return roots; |
| | | } |
| | | |
| | | private void markLeafRecursively(List<AccountSubjectVo> nodes) { |
| | | for (AccountSubjectVo node : nodes) { |
| | | List<AccountSubjectVo> children = node.getChildren(); |
| | | node.setLeaf(children == null || children.isEmpty()); |
| | | if (children != null && !children.isEmpty()) { |
| | | markLeafRecursively(children); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªç¶åå
³ç³»ï¼ç¶èç¹å¿
é¡»åå¨ï¼ä¸ä¸è½å½¢æå¾ªç¯å¼ç¨ã |
| | | */ |
| | | private void validateParent(Long parentId, Long currentId) { |
| | | if (parentId == null || parentId <= 0) { |
| | | return; |
| | | } |
| | | if (currentId != null && parentId.equals(currentId)) { |
| | | throw new ServiceException("ç¶ç§ç®ä¸è½éæ©èªèº«"); |
| | | } |
| | | AccountSubject parent = getById(parentId); |
| | | if (parent == null) { |
| | | throw new ServiceException("ç¶ç§ç®ä¸åå¨ï¼è¯·éæ°éæ©"); |
| | | } |
| | | // 鲿¢å½¢æç¯ï¼æ´æ°æ¶ï¼ç¶èç¹ä¸è½æ¯å½åèç¹çä»»æååèç¹ã |
| | | if (currentId != null) { |
| | | Set<Long> visited = new HashSet<>(); |
| | | Long traceParentId = parentId; |
| | | while (traceParentId != null && traceParentId > 0) { |
| | | if (!visited.add(traceParentId)) { |
| | | throw new ServiceException("ç§ç®å±çº§åå¨å¾ªç¯å¼ç¨ï¼è¯·æ£æ¥ç¶ç§ç®è®¾ç½®"); |
| | | } |
| | | if (traceParentId.equals(currentId)) { |
| | | throw new ServiceException("ç¶ç§ç®ä¸è½æ¯å½åç§ç®æå
¶åç§ç®"); |
| | | } |
| | | AccountSubject traceNode = getById(traceParentId); |
| | | if (traceNode == null) { |
| | | break; |
| | | } |
| | | traceParentId = traceNode.getParentId(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Map<Long, List<Long>> buildChildrenIdMap(List<AccountSubject> subjects) { |
| | | Map<Long, List<Long>> map = new HashMap<>(); |
| | | for (AccountSubject subject : subjects) { |
| | | if (subject.getId() == null || subject.getParentId() == null || subject.getParentId() <= 0) { |
| | | continue; |
| | | } |
| | | map.computeIfAbsent(subject.getParentId(), key -> new ArrayList<>()).add(subject.getId()); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | /** |
| | | * æ¶éå¾
å é¤èç¹åå
¶ææååèç¹ã |
| | | */ |
| | | private void collectDescendantIds(Long id, Map<Long, List<Long>> childrenIdMap, Set<Long> result) { |
| | | if (id == null || !result.add(id)) { |
| | | return; |
| | | } |
| | | List<Long> children = childrenIdMap.getOrDefault(id, Collections.emptyList()); |
| | | for (Long childId : children) { |
| | | collectDescendantIds(childId, childrenIdMap, result); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.financial.FinFixedAssetDto; |
| | | import com.ruoyi.account.mapper.financial.FinFixedAssetMapper; |
| | | import com.ruoyi.account.pojo.financial.FinFixedAsset; |
| | | import com.ruoyi.account.service.financial.FinFixedAssetService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * åºå®èµäº§æå¡å®ç°ã |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class FinFixedAssetServiceImpl extends ServiceImpl<FinFixedAssetMapper, FinFixedAsset> implements FinFixedAssetService { |
| | | |
| | | private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); |
| | | private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); |
| | | |
| | | @Override |
| | | public IPage<FinFixedAsset> pageList(Page<FinFixedAsset> page, FinFixedAssetDto queryDto) { |
| | | LambdaQueryWrapper<FinFixedAsset> wrapper = new LambdaQueryWrapper<>(); |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) { |
| | | wrapper.like(FinFixedAsset::getAssetCode, queryDto.getAssetCode()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) { |
| | | wrapper.like(FinFixedAsset::getAssetName, queryDto.getAssetName()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) { |
| | | wrapper.eq(FinFixedAsset::getCategory, queryDto.getCategory()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) { |
| | | wrapper.eq(FinFixedAsset::getStatus, queryDto.getStatus()); |
| | | } |
| | | wrapper.orderByDesc(FinFixedAsset::getId); |
| | | return page(page, wrapper); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean add(FinFixedAssetDto dto) { |
| | | validateForSave(dto, false); |
| | | if (StringUtils.isEmpty(dto.getAssetCode())) { |
| | | dto.setAssetCode(generateAssetCode()); |
| | | } |
| | | BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate()); |
| | | dto.setResidualRate(residualRate); |
| | | BigDecimal accumulatedDepreciation = defaultMoney(dto.getAccumulatedDepreciation()); |
| | | dto.setAccumulatedDepreciation(accumulatedDepreciation); |
| | | dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedDepreciation)); |
| | | if (StringUtils.isEmpty(dto.getStatus())) { |
| | | dto.setStatus("in_use"); |
| | | } |
| | | return save(dto); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean update(FinFixedAssetDto dto) { |
| | | if (dto == null || dto.getId() == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼èµäº§IDä¸è½ä¸ºç©º"); |
| | | } |
| | | FinFixedAsset existed = getById(dto.getId()); |
| | | if (existed == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼åºå®èµäº§ä¸åå¨"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getAssetCode())) { |
| | | dto.setAssetCode(existed.getAssetCode()); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getStatus())) { |
| | | dto.setStatus(existed.getStatus()); |
| | | } |
| | | validateForSave(dto, true); |
| | | BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate()); |
| | | dto.setResidualRate(residualRate); |
| | | if (dto.getAccumulatedDepreciation() == null) { |
| | | dto.setAccumulatedDepreciation(defaultMoney(existed.getAccumulatedDepreciation())); |
| | | } |
| | | dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedDepreciation())); |
| | | return updateById(dto); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean deleteByIds(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | throw new ServiceException("å é¤å¤±è´¥ï¼è¯·éæ©è¦å é¤çæ°æ®"); |
| | | } |
| | | return removeByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, Object> depreciate(List<Long> ids) { |
| | | LambdaQueryWrapper<FinFixedAsset> wrapper = new LambdaQueryWrapper<>(); |
| | | if (ids != null && !ids.isEmpty()) { |
| | | wrapper.in(FinFixedAsset::getId, ids); |
| | | } else { |
| | | wrapper.eq(FinFixedAsset::getStatus, "in_use"); |
| | | } |
| | | List<FinFixedAsset> assets = list(wrapper); |
| | | BigDecimal totalMonthlyDepreciation = ZERO; |
| | | int processedCount = 0; |
| | | for (FinFixedAsset asset : assets) { |
| | | if (!"in_use".equals(asset.getStatus())) { |
| | | continue; |
| | | } |
| | | BigDecimal monthlyDepreciation = calculateMonthlyDepreciation( |
| | | asset.getOriginalValue(), |
| | | asset.getResidualRate(), |
| | | asset.getUsefulLife() |
| | | ); |
| | | BigDecimal accumulatedDepreciation = defaultMoney(asset.getAccumulatedDepreciation()).add(monthlyDepreciation); |
| | | if (accumulatedDepreciation.compareTo(defaultMoney(asset.getOriginalValue())) > 0) { |
| | | accumulatedDepreciation = defaultMoney(asset.getOriginalValue()); |
| | | } |
| | | asset.setAccumulatedDepreciation(roundMoney(accumulatedDepreciation)); |
| | | asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedDepreciation())); |
| | | updateById(asset); |
| | | processedCount++; |
| | | totalMonthlyDepreciation = totalMonthlyDepreciation.add(monthlyDepreciation); |
| | | } |
| | | Map<String, Object> result = new HashMap<>(4); |
| | | result.put("processedCount", processedCount); |
| | | result.put("totalMonthlyDepreciation", roundMoney(totalMonthlyDepreciation)); |
| | | result.put("executionTime", LocalDateTime.now()); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * æææ¡£è§åæ ¡éªåºå®èµäº§æ°æ®ã |
| | | */ |
| | | private void validateForSave(FinFixedAssetDto dto, boolean isUpdate) { |
| | | if (dto == null) { |
| | | throw new ServiceException("åºå®èµäº§æ°æ®ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (isUpdate && dto.getId() == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼èµäº§IDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getAssetName())) { |
| | | throw new ServiceException("èµäº§åç§°ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getCategory())) { |
| | | throw new ServiceException("èµäº§ç±»å«ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (dto.getPurchaseDate() == null) { |
| | | throw new ServiceException("è´ç½®æ¥æä¸è½ä¸ºç©º"); |
| | | } |
| | | if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new ServiceException("èµäº§åå¼ä¸è½ä¸ºç©ºä¸ä¸è½å°äº0"); |
| | | } |
| | | if (dto.getUsefulLife() == null || dto.getUsefulLife() <= 0) { |
| | | throw new ServiceException("使ç¨å¹´éå¿
须大äº0"); |
| | | } |
| | | if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new ServiceException("æ®å¼çä¸è½å°äº0"); |
| | | } |
| | | if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(ONE_HUNDRED) > 0) { |
| | | throw new ServiceException("æ®å¼çä¸è½å¤§äº100%"); |
| | | } |
| | | if (StringUtils.isNotEmpty(dto.getAssetCode())) { |
| | | LambdaQueryWrapper<FinFixedAsset> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(FinFixedAsset::getAssetCode, dto.getAssetCode()); |
| | | if (isUpdate) { |
| | | wrapper.ne(FinFixedAsset::getId, dto.getId()); |
| | | } |
| | | if (count(wrapper) > 0) { |
| | | throw new ServiceException("èµäº§ç¼å·å·²åå¨ï¼è¯·å¿éå¤æäº¤"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åºå®èµäº§ææ§å
¬å¼ï¼ |
| | | * monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12) |
| | | */ |
| | | private BigDecimal calculateMonthlyDepreciation(BigDecimal originalValue, BigDecimal residualRate, Integer usefulLife) { |
| | | BigDecimal normalizedOriginalValue = defaultMoney(originalValue); |
| | | BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate); |
| | | BigDecimal depreciableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP)); |
| | | BigDecimal months = BigDecimal.valueOf((long) usefulLife * 12L); |
| | | if (months.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("使ç¨å¹´éæ æï¼æ æ³è®¡æææ§"); |
| | | } |
| | | return roundMoney(normalizedOriginalValue.multiply(depreciableRatio).divide(months, 8, RoundingMode.HALF_UP)); |
| | | } |
| | | |
| | | /** |
| | | * åå¼ = åå¼ - ç´¯è®¡ææ§ã |
| | | */ |
| | | private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedDepreciation) { |
| | | BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedDepreciation)); |
| | | if (value.compareTo(BigDecimal.ZERO) < 0) { |
| | | value = BigDecimal.ZERO; |
| | | } |
| | | return roundMoney(value); |
| | | } |
| | | |
| | | private BigDecimal normalizeResidualRate(BigDecimal residualRate) { |
| | | return residualRate == null ? BigDecimal.ZERO : residualRate; |
| | | } |
| | | |
| | | private BigDecimal defaultMoney(BigDecimal value) { |
| | | return value == null ? ZERO : roundMoney(value); |
| | | } |
| | | |
| | | private BigDecimal roundMoney(BigDecimal value) { |
| | | if (value == null) { |
| | | return ZERO; |
| | | } |
| | | return value.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private String generateAssetCode() { |
| | | return "GD" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.financial.FinIntangibleAssetDto; |
| | | import com.ruoyi.account.mapper.financial.FinIntangibleAssetMapper; |
| | | import com.ruoyi.account.pojo.financial.FinIntangibleAsset; |
| | | import com.ruoyi.account.service.financial.FinIntangibleAssetService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * æ å½¢èµäº§æå¡å®ç°ã |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class FinIntangibleAssetServiceImpl extends ServiceImpl<FinIntangibleAssetMapper, FinIntangibleAsset> implements FinIntangibleAssetService { |
| | | |
| | | private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); |
| | | private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); |
| | | |
| | | @Override |
| | | public IPage<FinIntangibleAsset> pageList(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto) { |
| | | LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>(); |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) { |
| | | wrapper.like(FinIntangibleAsset::getAssetCode, queryDto.getAssetCode()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) { |
| | | wrapper.like(FinIntangibleAsset::getAssetName, queryDto.getAssetName()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) { |
| | | wrapper.eq(FinIntangibleAsset::getCategory, queryDto.getCategory()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) { |
| | | wrapper.eq(FinIntangibleAsset::getStatus, queryDto.getStatus()); |
| | | } |
| | | wrapper.orderByDesc(FinIntangibleAsset::getId); |
| | | return page(page, wrapper); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean add(FinIntangibleAssetDto dto) { |
| | | validateForSave(dto, false); |
| | | if (StringUtils.isEmpty(dto.getAssetCode())) { |
| | | dto.setAssetCode(generateAssetCode()); |
| | | } |
| | | BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate()); |
| | | dto.setResidualRate(residualRate); |
| | | BigDecimal accumulatedAmortization = defaultMoney(dto.getAccumulatedAmortization()); |
| | | dto.setAccumulatedAmortization(accumulatedAmortization); |
| | | dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedAmortization)); |
| | | if (StringUtils.isEmpty(dto.getStatus())) { |
| | | dto.setStatus("in_use"); |
| | | } |
| | | return save(dto); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean update(FinIntangibleAssetDto dto) { |
| | | if (dto == null || dto.getId() == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼èµäº§IDä¸è½ä¸ºç©º"); |
| | | } |
| | | FinIntangibleAsset existed = getById(dto.getId()); |
| | | if (existed == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼æ å½¢èµäº§ä¸åå¨"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getAssetCode())) { |
| | | dto.setAssetCode(existed.getAssetCode()); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getStatus())) { |
| | | dto.setStatus(existed.getStatus()); |
| | | } |
| | | validateForSave(dto, true); |
| | | BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate()); |
| | | dto.setResidualRate(residualRate); |
| | | if (dto.getAccumulatedAmortization() == null) { |
| | | dto.setAccumulatedAmortization(defaultMoney(existed.getAccumulatedAmortization())); |
| | | } |
| | | dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedAmortization())); |
| | | if (dto.getNetValue().compareTo(BigDecimal.ZERO) <= 0) { |
| | | dto.setStatus("amortized"); |
| | | } else if ("amortized".equals(dto.getStatus())) { |
| | | dto.setStatus("in_use"); |
| | | } |
| | | if (dto.getValidityDate() != null |
| | | && dto.getValidityDate().isBefore(LocalDate.now()) |
| | | && !"amortized".equals(dto.getStatus())) { |
| | | dto.setStatus("expired"); |
| | | } |
| | | return updateById(dto); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean deleteByIds(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | throw new ServiceException("å é¤å¤±è´¥ï¼è¯·éæ©è¦å é¤çæ°æ®"); |
| | | } |
| | | return removeByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, Object> amortize(List<Long> ids) { |
| | | LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>(); |
| | | if (ids != null && !ids.isEmpty()) { |
| | | wrapper.in(FinIntangibleAsset::getId, ids); |
| | | } else { |
| | | wrapper.eq(FinIntangibleAsset::getStatus, "in_use"); |
| | | } |
| | | List<FinIntangibleAsset> assets = list(wrapper); |
| | | BigDecimal totalMonthlyAmortization = ZERO; |
| | | int processedCount = 0; |
| | | for (FinIntangibleAsset asset : assets) { |
| | | if (!"in_use".equals(asset.getStatus())) { |
| | | continue; |
| | | } |
| | | BigDecimal monthlyAmortization = calculateMonthlyAmortization( |
| | | asset.getOriginalValue(), |
| | | asset.getResidualRate(), |
| | | asset.getAmortizationPeriod() |
| | | ); |
| | | BigDecimal accumulatedAmortization = defaultMoney(asset.getAccumulatedAmortization()).add(monthlyAmortization); |
| | | if (accumulatedAmortization.compareTo(defaultMoney(asset.getOriginalValue())) > 0) { |
| | | accumulatedAmortization = defaultMoney(asset.getOriginalValue()); |
| | | } |
| | | asset.setAccumulatedAmortization(roundMoney(accumulatedAmortization)); |
| | | asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedAmortization())); |
| | | |
| | | // è§åï¼å½åå¼ <= 0 æ¶ï¼åå¼å½é¶å¹¶æ 记为已æéå®ã |
| | | if (asset.getNetValue().compareTo(BigDecimal.ZERO) <= 0) { |
| | | asset.setNetValue(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)); |
| | | asset.setStatus("amortized"); |
| | | } else if (asset.getValidityDate() != null && asset.getValidityDate().isBefore(LocalDate.now())) { |
| | | asset.setStatus("expired"); |
| | | } |
| | | updateById(asset); |
| | | processedCount++; |
| | | totalMonthlyAmortization = totalMonthlyAmortization.add(monthlyAmortization); |
| | | } |
| | | Map<String, Object> result = new HashMap<>(4); |
| | | result.put("processedCount", processedCount); |
| | | result.put("totalMonthlyAmortization", roundMoney(totalMonthlyAmortization)); |
| | | result.put("executionTime", LocalDateTime.now()); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * æææ¡£è§åæ ¡éªæ å½¢èµäº§æ°æ®ã |
| | | */ |
| | | private void validateForSave(FinIntangibleAssetDto dto, boolean isUpdate) { |
| | | if (dto == null) { |
| | | throw new ServiceException("æ å½¢èµäº§æ°æ®ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (isUpdate && dto.getId() == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼èµäº§IDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getAssetName())) { |
| | | throw new ServiceException("èµäº§åç§°ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getCategory())) { |
| | | throw new ServiceException("èµäº§ç±»å«ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (dto.getAcquisitionDate() == null) { |
| | | throw new ServiceException("å徿¥æä¸è½ä¸ºç©º"); |
| | | } |
| | | if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new ServiceException("èµäº§åå¼ä¸è½ä¸ºç©ºä¸ä¸è½å°äº0"); |
| | | } |
| | | if (dto.getAmortizationPeriod() == null || dto.getAmortizationPeriod() <= 0) { |
| | | throw new ServiceException("æéå¹´éå¿
须大äº0"); |
| | | } |
| | | if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new ServiceException("æ®å¼çä¸è½å°äº0"); |
| | | } |
| | | if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(ONE_HUNDRED) > 0) { |
| | | throw new ServiceException("æ®å¼çä¸è½å¤§äº100%"); |
| | | } |
| | | if (StringUtils.isNotEmpty(dto.getAssetCode())) { |
| | | LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(FinIntangibleAsset::getAssetCode, dto.getAssetCode()); |
| | | if (isUpdate) { |
| | | wrapper.ne(FinIntangibleAsset::getId, dto.getId()); |
| | | } |
| | | if (count(wrapper) > 0) { |
| | | throw new ServiceException("èµäº§ç¼å·å·²åå¨ï¼è¯·å¿éå¤æäº¤"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ å½¢èµäº§æéå
¬å¼ï¼ |
| | | * monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12) |
| | | */ |
| | | private BigDecimal calculateMonthlyAmortization(BigDecimal originalValue, BigDecimal residualRate, Integer amortizationPeriod) { |
| | | BigDecimal normalizedOriginalValue = defaultMoney(originalValue); |
| | | BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate); |
| | | BigDecimal amortizableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP)); |
| | | BigDecimal months = BigDecimal.valueOf((long) amortizationPeriod * 12L); |
| | | if (months.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("æéå¹´éæ æï¼æ æ³è®¡ææé"); |
| | | } |
| | | return roundMoney(normalizedOriginalValue.multiply(amortizableRatio).divide(months, 8, RoundingMode.HALF_UP)); |
| | | } |
| | | |
| | | /** |
| | | * åå¼ = åå¼ - 累计æéã |
| | | */ |
| | | private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedAmortization) { |
| | | BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedAmortization)); |
| | | if (value.compareTo(BigDecimal.ZERO) < 0) { |
| | | value = BigDecimal.ZERO; |
| | | } |
| | | return roundMoney(value); |
| | | } |
| | | |
| | | private BigDecimal normalizeResidualRate(BigDecimal residualRate) { |
| | | return residualRate == null ? BigDecimal.ZERO : residualRate; |
| | | } |
| | | |
| | | private BigDecimal defaultMoney(BigDecimal value) { |
| | | return value == null ? ZERO : roundMoney(value); |
| | | } |
| | | |
| | | private BigDecimal roundMoney(BigDecimal value) { |
| | | if (value == null) { |
| | | return ZERO; |
| | | } |
| | | return value.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private String generateAssetCode() { |
| | | return "WX" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.financial; |
| | | |
| | | import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto; |
| | | import com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo; |
| | | import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo; |
| | | import com.ruoyi.account.mapper.financial.FinVoucherEntryMapper; |
| | | import com.ruoyi.account.service.financial.FinLedgerService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * ç§ç®æ»è´¦/æç»è´¦æå¡å®ç°ã |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class FinLedgerServiceImpl implements FinLedgerService { |
| | | |
| | | private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); |
| | | private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | |
| | | private final FinVoucherEntryMapper finVoucherEntryMapper; |
| | | |
| | | @Override |
| | | public List<FinLedgerRowVo> queryGeneralLedger(FinLedgerQueryDto queryDto) { |
| | | if (queryDto == null || StringUtils.isEmpty(queryDto.getSubjectCode())) { |
| | | return Collections.emptyList(); |
| | | } |
| | | YearMonth startMonth = parseMonth(queryDto.getStartMonth(), "å¼å§æä»½"); |
| | | YearMonth endMonth = parseMonth(queryDto.getEndMonth(), "ç»ææä»½"); |
| | | if (startMonth.isAfter(endMonth)) { |
| | | throw new ServiceException("å¼å§æä»½ä¸è½å¤§äºç»ææä»½"); |
| | | } |
| | | return Collections.singletonList(buildGeneralLedgerTotalRow(queryDto.getSubjectCode(), startMonth, endMonth)); |
| | | } |
| | | |
| | | @Override |
| | | public List<FinLedgerRowVo> queryDetailLedger(FinDetailLedgerQueryDto queryDto) { |
| | | if (queryDto == null || StringUtils.isEmpty(queryDto.getSubjectCode())) { |
| | | return Collections.emptyList(); |
| | | } |
| | | YearMonth startMonth = parseMonth(queryDto.getStartMonth(), "å¼å§æä»½"); |
| | | YearMonth endMonth = parseMonth(queryDto.getEndMonth(), "ç»ææä»½"); |
| | | if (startMonth.isAfter(endMonth)) { |
| | | throw new ServiceException("å¼å§æä»½ä¸è½å¤§äºç»ææä»½"); |
| | | } |
| | | return buildLedgerRows(queryDto.getSubjectCode(), startMonth, endMonth, queryDto.getAuxiliaryType(), queryDto.getAuxiliaryId()); |
| | | } |
| | | |
| | | /** |
| | | * æå»ºè´¦ç°¿è¡æ°æ®ï¼è¾åºæåãåå½ãæ¬æåè®¡ãæ¬å¹´ç´¯è®¡ã |
| | | */ |
| | | private List<FinLedgerRowVo> buildLedgerRows(String subjectCode, |
| | | YearMonth startMonth, |
| | | YearMonth endMonth, |
| | | String auxiliaryType, |
| | | String auxiliaryId) { |
| | | LocalDate startDate = startMonth.atDay(1); |
| | | LocalDate endDate = endMonth.atEndOfMonth(); |
| | | |
| | | List<FinLedgerEntryRecordVo> openingEntries = finVoucherEntryMapper.listPostedEntriesBefore( |
| | | subjectCode, startDate, auxiliaryType, auxiliaryId |
| | | ); |
| | | BigDecimal openingBalance = calculateBalance(openingEntries); |
| | | |
| | | List<FinLedgerEntryRecordVo> currentPeriodEntries = finVoucherEntryMapper.listPostedEntries( |
| | | subjectCode, startDate, endDate, auxiliaryType, auxiliaryId |
| | | ); |
| | | Map<YearMonth, List<FinLedgerEntryRecordVo>> monthEntriesMap = groupEntriesByMonth(currentPeriodEntries); |
| | | |
| | | List<FinLedgerRowVo> rows = new ArrayList<>(); |
| | | BigDecimal runningBalance = openingBalance; |
| | | BigDecimal yearDebit = ZERO; |
| | | BigDecimal yearCredit = ZERO; |
| | | |
| | | for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) { |
| | | rows.add(buildOpeningRow(month.atDay(1), runningBalance)); |
| | | |
| | | List<FinLedgerEntryRecordVo> monthEntries = monthEntriesMap.getOrDefault(month, Collections.emptyList()); |
| | | BigDecimal monthDebit = ZERO; |
| | | BigDecimal monthCredit = ZERO; |
| | | for (FinLedgerEntryRecordVo entry : monthEntries) { |
| | | BigDecimal debit = money(entry.getDebit()); |
| | | BigDecimal credit = money(entry.getCredit()); |
| | | runningBalance = runningBalance.add(debit).subtract(credit); |
| | | monthDebit = monthDebit.add(debit); |
| | | monthCredit = monthCredit.add(credit); |
| | | |
| | | FinLedgerRowVo row = new FinLedgerRowVo(); |
| | | row.setRowType("entry"); |
| | | row.setDate(entry.getVoucherDate()); |
| | | row.setVoucherNo(entry.getVoucherNo()); |
| | | row.setSummary(StringUtils.isNotEmpty(entry.getSummary()) ? entry.getSummary() : ""); |
| | | row.setDebit(debit); |
| | | row.setCredit(credit); |
| | | row.setBalance(money(runningBalance)); |
| | | row.setDirection(resolveDirection(runningBalance)); |
| | | rows.add(row); |
| | | } |
| | | |
| | | rows.add(buildMonthlyTotalRow(month.atEndOfMonth(), monthDebit, monthCredit, runningBalance)); |
| | | yearDebit = yearDebit.add(monthDebit); |
| | | yearCredit = yearCredit.add(monthCredit); |
| | | } |
| | | |
| | | rows.add(buildYearlyTotalRow(endMonth.atEndOfMonth(), yearDebit, yearCredit, runningBalance)); |
| | | return rows; |
| | | } |
| | | |
| | | private FinLedgerRowVo buildGeneralLedgerTotalRow(String subjectCode, YearMonth startMonth, YearMonth endMonth) { |
| | | LocalDate startDate = startMonth.atDay(1); |
| | | LocalDate endDate = endMonth.atEndOfMonth(); |
| | | |
| | | List<FinLedgerEntryRecordVo> openingEntries = finVoucherEntryMapper.listPostedEntriesBefore( |
| | | subjectCode, startDate, null, null |
| | | ); |
| | | BigDecimal openingBalance = calculateBalance(openingEntries); |
| | | |
| | | List<FinLedgerEntryRecordVo> currentPeriodEntries = finVoucherEntryMapper.listPostedEntries( |
| | | subjectCode, startDate, endDate, null, null |
| | | ); |
| | | |
| | | BigDecimal totalDebit = ZERO; |
| | | BigDecimal totalCredit = ZERO; |
| | | for (FinLedgerEntryRecordVo entry : currentPeriodEntries) { |
| | | totalDebit = totalDebit.add(money(entry.getDebit())); |
| | | totalCredit = totalCredit.add(money(entry.getCredit())); |
| | | } |
| | | |
| | | BigDecimal endingBalance = openingBalance.add(totalDebit).subtract(totalCredit); |
| | | FinLedgerRowVo totalRow = new FinLedgerRowVo(); |
| | | totalRow.setRowType("yearly_total"); |
| | | totalRow.setDate(endDate); |
| | | totalRow.setDebit(money(totalDebit)); |
| | | totalRow.setCredit(money(totalCredit)); |
| | | totalRow.setBalance(money(endingBalance)); |
| | | totalRow.setDirection(resolveDirection(endingBalance)); |
| | | return totalRow; |
| | | } |
| | | |
| | | private Map<YearMonth, List<FinLedgerEntryRecordVo>> groupEntriesByMonth(List<FinLedgerEntryRecordVo> entries) { |
| | | Map<YearMonth, List<FinLedgerEntryRecordVo>> map = new LinkedHashMap<>(); |
| | | for (FinLedgerEntryRecordVo entry : entries) { |
| | | if (entry.getVoucherDate() == null) { |
| | | continue; |
| | | } |
| | | YearMonth month = YearMonth.from(entry.getVoucherDate()); |
| | | map.computeIfAbsent(month, key -> new ArrayList<>()).add(entry); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | private FinLedgerRowVo buildOpeningRow(LocalDate date, BigDecimal openingBalance) { |
| | | FinLedgerRowVo row = new FinLedgerRowVo(); |
| | | row.setRowType("opening"); |
| | | row.setDate(date); |
| | | row.setVoucherNo("-"); |
| | | row.setSummary("æåä½é¢"); |
| | | row.setDebit(ZERO); |
| | | row.setCredit(ZERO); |
| | | row.setBalance(money(openingBalance)); |
| | | row.setDirection(resolveDirection(openingBalance)); |
| | | return row; |
| | | } |
| | | |
| | | private FinLedgerRowVo buildMonthlyTotalRow(LocalDate date, |
| | | BigDecimal monthDebit, |
| | | BigDecimal monthCredit, |
| | | BigDecimal monthBalance) { |
| | | FinLedgerRowVo row = new FinLedgerRowVo(); |
| | | row.setRowType("monthly_total"); |
| | | row.setDate(date); |
| | | row.setVoucherNo("-"); |
| | | row.setSummary("æ¬æå计"); |
| | | row.setDebit(money(monthDebit)); |
| | | row.setCredit(money(monthCredit)); |
| | | row.setBalance(money(monthBalance)); |
| | | row.setDirection(resolveDirection(monthBalance)); |
| | | return row; |
| | | } |
| | | |
| | | private FinLedgerRowVo buildYearlyTotalRow(LocalDate date, |
| | | BigDecimal yearDebit, |
| | | BigDecimal yearCredit, |
| | | BigDecimal yearBalance) { |
| | | FinLedgerRowVo row = new FinLedgerRowVo(); |
| | | row.setRowType("yearly_total"); |
| | | row.setDate(date); |
| | | row.setVoucherNo("-"); |
| | | row.setSummary("å计"); |
| | | row.setDebit(money(yearDebit)); |
| | | row.setCredit(money(yearCredit)); |
| | | row.setBalance(money(yearBalance)); |
| | | row.setDirection(resolveDirection(yearBalance)); |
| | | return row; |
| | | } |
| | | |
| | | private BigDecimal calculateBalance(List<FinLedgerEntryRecordVo> entries) { |
| | | BigDecimal balance = ZERO; |
| | | for (FinLedgerEntryRecordVo entry : entries) { |
| | | balance = balance.add(money(entry.getDebit())).subtract(money(entry.getCredit())); |
| | | } |
| | | return money(balance); |
| | | } |
| | | |
| | | private String resolveDirection(BigDecimal balance) { |
| | | return money(balance).compareTo(BigDecimal.ZERO) >= 0 ? "å" : "è´·"; |
| | | } |
| | | |
| | | private YearMonth parseMonth(String value, String fieldLabel) { |
| | | if (StringUtils.isEmpty(value)) { |
| | | throw new ServiceException(fieldLabel + "ä¸è½ä¸ºç©ºï¼æ ¼å¼åºä¸ºYYYY-MM"); |
| | | } |
| | | try { |
| | | return YearMonth.parse(value, MONTH_FORMATTER); |
| | | } catch (DateTimeParseException ex) { |
| | | throw new ServiceException(fieldLabel + "æ ¼å¼éè¯¯ï¼æ ¼å¼åºä¸ºYYYY-MM"); |
| | | } |
| | | } |
| | | |
| | | private BigDecimal money(BigDecimal value) { |
| | | if (value == null) { |
| | | return ZERO; |
| | | } |
| | | return value.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.financial; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherEntryDto; |
| | | import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto; |
| | | import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo; |
| | | import com.ruoyi.account.mapper.financial.AccountSubjectMapper; |
| | | import com.ruoyi.account.mapper.financial.FinVoucherEntryMapper; |
| | | import com.ruoyi.account.mapper.financial.FinVoucherMapper; |
| | | import com.ruoyi.account.pojo.financial.AccountSubject; |
| | | import com.ruoyi.account.pojo.financial.FinVoucher; |
| | | import com.ruoyi.account.pojo.financial.FinVoucherEntry; |
| | | import com.ruoyi.account.service.financial.FinVoucherService; |
| | | import com.ruoyi.basic.enums.ApplicationTypeEnum; |
| | | import com.ruoyi.basic.enums.RecordTypeEnum; |
| | | import com.ruoyi.basic.utils.FileUtil; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * åè¯æå¡å®ç°ã |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class FinVoucherServiceImpl extends ServiceImpl<FinVoucherMapper, FinVoucher> implements FinVoucherService { |
| | | |
| | | private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | |
| | | private final FinVoucherEntryMapper finVoucherEntryMapper; |
| | | private final AccountSubjectMapper accountSubjectMapper; |
| | | private final FileUtil fileUtil; |
| | | |
| | | @Override |
| | | public IPage<FinVoucher> pageList(Page<FinVoucher> page, FinVoucherPageDto queryDto) { |
| | | LambdaQueryWrapper<FinVoucher> wrapper = new LambdaQueryWrapper<>(); |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getVoucherNo())) { |
| | | wrapper.like(FinVoucher::getVoucherNo, queryDto.getVoucherNo()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCreator())) { |
| | | wrapper.eq(FinVoucher::getCreator, queryDto.getCreator()); |
| | | } |
| | | if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) { |
| | | wrapper.eq(FinVoucher::getStatus, queryDto.getStatus()); |
| | | } |
| | | if (queryDto != null && queryDto.getStartDate() != null) { |
| | | wrapper.ge(FinVoucher::getVoucherDate, queryDto.getStartDate()); |
| | | } |
| | | if (queryDto != null && queryDto.getEndDate() != null) { |
| | | wrapper.le(FinVoucher::getVoucherDate, queryDto.getEndDate()); |
| | | } |
| | | wrapper.orderByDesc(FinVoucher::getVoucherDate).orderByDesc(FinVoucher::getId); |
| | | return page(page, wrapper); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean addVoucher(FinVoucherDto dto) { |
| | | validateVoucherBasicInfo(dto, false); |
| | | List<FinVoucherEntry> validEntries = buildAndValidateEntries(dto); |
| | | |
| | | FinVoucher voucher = new FinVoucher(); |
| | | BeanUtils.copyProperties(dto, voucher); |
| | | voucher.setStatus("unposted"); |
| | | voucher.setAttachmentCount(voucher.getAttachmentCount() == null ? 0 : voucher.getAttachmentCount()); |
| | | BigDecimal totalDebit = calculateTotalDebit(validEntries); |
| | | BigDecimal totalCredit = calculateTotalCredit(validEntries); |
| | | voucher.setDebit(totalDebit); |
| | | voucher.setCredit(totalCredit); |
| | | if (StringUtils.isEmpty(voucher.getSummary())) { |
| | | voucher.setSummary(findDefaultSummary(validEntries)); |
| | | } |
| | | save(voucher); |
| | | saveEntries(voucher.getId(), validEntries); |
| | | // 5. ä¿åéå®å°è´¦éä»¶ |
| | | fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_VOUCHER, voucher.getId(), dto.getStorageBlobDTOs()); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean updateVoucher(FinVoucherDto dto) { |
| | | validateVoucherBasicInfo(dto, true); |
| | | FinVoucher existed = getById(dto.getId()); |
| | | if (existed == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼åè¯ä¸åå¨"); |
| | | } |
| | | if (!"unposted".equals(existed.getStatus())) { |
| | | throw new ServiceException("ä»
æªè¿è´¦åè¯å
许修æ¹"); |
| | | } |
| | | List<FinVoucherEntry> validEntries = buildAndValidateEntries(dto); |
| | | |
| | | FinVoucher voucher = new FinVoucher(); |
| | | BeanUtils.copyProperties(dto, voucher); |
| | | voucher.setStatus(existed.getStatus()); |
| | | voucher.setAttachmentCount(voucher.getAttachmentCount() == null ? 0 : voucher.getAttachmentCount()); |
| | | BigDecimal totalDebit = calculateTotalDebit(validEntries); |
| | | BigDecimal totalCredit = calculateTotalCredit(validEntries); |
| | | voucher.setDebit(totalDebit); |
| | | voucher.setCredit(totalCredit); |
| | | if (StringUtils.isEmpty(voucher.getSummary())) { |
| | | voucher.setSummary(findDefaultSummary(validEntries)); |
| | | } |
| | | updateById(voucher); |
| | | |
| | | LambdaQueryWrapper<FinVoucherEntry> deleteWrapper = new LambdaQueryWrapper<>(); |
| | | deleteWrapper.eq(FinVoucherEntry::getVoucherId, voucher.getId()); |
| | | finVoucherEntryMapper.delete(deleteWrapper); |
| | | saveEntries(voucher.getId(), validEntries); |
| | | fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_VOUCHER, voucher.getId(), dto.getStorageBlobDTOs()); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean postVoucher(Long id) { |
| | | FinVoucher voucher = getById(id); |
| | | if (voucher == null) { |
| | | throw new ServiceException("è¿è´¦å¤±è´¥ï¼åè¯ä¸åå¨"); |
| | | } |
| | | if (!"unposted".equals(voucher.getStatus())) { |
| | | throw new ServiceException("ä»
æªè¿è´¦åè¯å
许è¿è´¦"); |
| | | } |
| | | voucher.setStatus("posted"); |
| | | return updateById(voucher); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean cancelVoucher(Long id) { |
| | | FinVoucher voucher = getById(id); |
| | | if (voucher == null) { |
| | | throw new ServiceException("ä½åºå¤±è´¥ï¼åè¯ä¸åå¨"); |
| | | } |
| | | if (!"unposted".equals(voucher.getStatus())) { |
| | | throw new ServiceException("ä»
æªè¿è´¦åè¯å
许ä½åº"); |
| | | } |
| | | voucher.setStatus("cancelled"); |
| | | return updateById(voucher); |
| | | } |
| | | |
| | | @Override |
| | | public FinVoucherDetailVo detail(Long id) { |
| | | FinVoucher voucher = getById(id); |
| | | if (voucher == null) { |
| | | throw new ServiceException("æ¥è¯¢å¤±è´¥ï¼åè¯ä¸åå¨"); |
| | | } |
| | | LambdaQueryWrapper<FinVoucherEntry> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(FinVoucherEntry::getVoucherId, id) |
| | | .orderByAsc(FinVoucherEntry::getRowNo) |
| | | .orderByAsc(FinVoucherEntry::getId); |
| | | List<FinVoucherEntry> entries = finVoucherEntryMapper.selectList(wrapper); |
| | | |
| | | FinVoucherDetailVo vo = new FinVoucherDetailVo(); |
| | | BeanUtils.copyProperties(voucher, vo); |
| | | vo.setEntries(entries); |
| | | vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_VOUCHER, id)); |
| | | return vo; |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªåè¯ä¸»è¡¨å段ãç¶æåæ®µä¸å¯ä¸æ§ã |
| | | */ |
| | | private void validateVoucherBasicInfo(FinVoucherDto dto, boolean isUpdate) { |
| | | if (dto == null) { |
| | | throw new ServiceException("åè¯æ°æ®ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (isUpdate && dto.getId() == null) { |
| | | throw new ServiceException("ä¿®æ¹å¤±è´¥ï¼åè¯IDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getVoucherNo())) { |
| | | throw new ServiceException("åè¯åå·ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (dto.getVoucherDate() == null) { |
| | | throw new ServiceException("åè¯æ¥æä¸è½ä¸ºç©º"); |
| | | } |
| | | LambdaQueryWrapper<FinVoucher> uniqueWrapper = new LambdaQueryWrapper<>(); |
| | | uniqueWrapper.eq(FinVoucher::getVoucherNo, dto.getVoucherNo()); |
| | | if (isUpdate) { |
| | | uniqueWrapper.ne(FinVoucher::getId, dto.getId()); |
| | | } |
| | | if (count(uniqueWrapper) > 0) { |
| | | throw new ServiceException("åè¯åå·å·²åå¨ï¼è¯·å¿éå¤æäº¤"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è¿æ»¤ææåå½å¹¶æ§è¡åè´·å¹³è¡¡æ ¡éªã |
| | | */ |
| | | private List<FinVoucherEntry> buildAndValidateEntries(FinVoucherDto dto) { |
| | | List<FinVoucherEntryDto> rawEntries = dto.getEntries(); |
| | | if (rawEntries == null || rawEntries.isEmpty()) { |
| | | throw new ServiceException("åå½ä¸è½ä¸ºç©ºï¼è³å°éè¦ä¸æ¡ææåå½"); |
| | | } |
| | | List<FinVoucherEntry> validEntries = new ArrayList<>(); |
| | | int rowNo = 1; |
| | | for (FinVoucherEntryDto entryDto : rawEntries) { |
| | | if (entryDto == null || StringUtils.isEmpty(entryDto.getSubjectCode())) { |
| | | continue; |
| | | } |
| | | BigDecimal debit = defaultMoney(entryDto.getDebit()); |
| | | BigDecimal credit = defaultMoney(entryDto.getCredit()); |
| | | if (debit.compareTo(BigDecimal.ZERO) <= 0 && credit.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | if (debit.compareTo(BigDecimal.ZERO) > 0 && credit.compareTo(BigDecimal.ZERO) > 0) { |
| | | throw new ServiceException("åå½åæ¹åè´·æ¹ä¸è½åæ¶å¤§äº0"); |
| | | } |
| | | FinVoucherEntry entry = new FinVoucherEntry(); |
| | | BeanUtils.copyProperties(entryDto, entry); |
| | | entry.setDebit(debit); |
| | | entry.setCredit(credit); |
| | | entry.setRowNo(rowNo++); |
| | | validEntries.add(entry); |
| | | } |
| | | if (validEntries.isEmpty()) { |
| | | throw new ServiceException("åå½è³å°éè¦ä¸æ¡ææè¡ï¼ç§ç®ä¸ç©ºï¼ä¸åæ¹æè´·æ¹å¤§äº0ï¼"); |
| | | } |
| | | |
| | | // åå½ç§ç®å¿
é¡»åå¨ï¼é¿å
èç§ç®ç¼ç å
¥è´¦ã |
| | | Set<String> subjectCodes = validEntries.stream() |
| | | .map(FinVoucherEntry::getSubjectCode) |
| | | .filter(StringUtils::isNotEmpty) |
| | | .collect(Collectors.toSet()); |
| | | if (subjectCodes.isEmpty()) { |
| | | throw new ServiceException("åå½ç§ç®ä¸è½ä¸ºç©º"); |
| | | } |
| | | LambdaQueryWrapper<AccountSubject> subjectWrapper = new LambdaQueryWrapper<>(); |
| | | subjectWrapper.in(AccountSubject::getSubjectCode, subjectCodes); |
| | | List<AccountSubject> subjects = accountSubjectMapper.selectList(subjectWrapper); |
| | | Map<String, AccountSubject> subjectMap = subjects.stream() |
| | | .collect(Collectors.toMap(AccountSubject::getSubjectCode, it -> it, (a, b) -> a)); |
| | | for (FinVoucherEntry entry : validEntries) { |
| | | AccountSubject accountSubject = subjectMap.get(entry.getSubjectCode()); |
| | | if (accountSubject == null) { |
| | | throw new ServiceException("ç§ç®ç¼ç ä¸åå¨ï¼" + entry.getSubjectCode()); |
| | | } |
| | | if (StringUtils.isEmpty(entry.getSubjectName())) { |
| | | entry.setSubjectName(accountSubject.getSubjectName()); |
| | | } |
| | | } |
| | | |
| | | BigDecimal totalDebit = calculateTotalDebit(validEntries); |
| | | BigDecimal totalCredit = calculateTotalCredit(validEntries); |
| | | if (totalDebit.compareTo(BigDecimal.ZERO) <= 0 || totalCredit.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("åè´·éé¢å¿
须大äº0"); |
| | | } |
| | | if (totalDebit.compareTo(totalCredit) != 0) { |
| | | throw new ServiceException("åè´·ä¸å¹³è¡¡ï¼ç¦æ¢ä¿å"); |
| | | } |
| | | return validEntries; |
| | | } |
| | | |
| | | private void saveEntries(Long voucherId, List<FinVoucherEntry> entries) { |
| | | if (voucherId == null) { |
| | | throw new ServiceException("åè¯IDä¸è½ä¸ºç©º"); |
| | | } |
| | | for (FinVoucherEntry entry : entries) { |
| | | entry.setVoucherId(voucherId); |
| | | finVoucherEntryMapper.insert(entry); |
| | | } |
| | | } |
| | | |
| | | private String findDefaultSummary(List<FinVoucherEntry> entries) { |
| | | for (FinVoucherEntry entry : entries) { |
| | | if (StringUtils.isNotEmpty(entry.getSummary())) { |
| | | return entry.getSummary(); |
| | | } |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | private BigDecimal calculateTotalDebit(List<FinVoucherEntry> entries) { |
| | | BigDecimal total = BigDecimal.ZERO; |
| | | for (FinVoucherEntry entry : entries) { |
| | | total = total.add(defaultMoney(entry.getDebit())); |
| | | } |
| | | return total.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private BigDecimal calculateTotalCredit(List<FinVoucherEntry> entries) { |
| | | BigDecimal total = BigDecimal.ZERO; |
| | | for (FinVoucherEntry entry : entries) { |
| | | total = total.add(defaultMoney(entry.getCredit())); |
| | | } |
| | | return total.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private BigDecimal defaultMoney(BigDecimal value) { |
| | | if (value == null) { |
| | | return ZERO; |
| | | } |
| | | return value.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPaymentApplicationDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.mapper.purchase.AccountPaymentApplicationMapper; |
| | | import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper; |
| | | import com.ruoyi.account.pojo.purchase.AccountPaymentApplication; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.account.service.purchase.AccountPaymentApplicationService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Random; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾ç³è¯· æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:44:22 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountPaymentApplicationServiceImpl extends ServiceImpl<AccountPaymentApplicationMapper, AccountPaymentApplication> implements AccountPaymentApplicationService { |
| | | |
| | | private final AccountPaymentApplicationMapper accountPaymentApplicationMapper; |
| | | private final AccountPurchasePaymentMapper accountPurchasePaymentMapper; |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss"); |
| | | |
| | | @Override |
| | | public IPage<AccountPaymentApplicationVo> listPageAccountPaymentApplication(Page page, AccountPaymentApplicationDto accountPaymentApplicationDto) { |
| | | return accountPaymentApplicationMapper.listPageAccountPaymentApplication(page, accountPaymentApplicationDto); |
| | | } |
| | | |
| | | @Override |
| | | public List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId) { |
| | | return accountPaymentApplicationMapper.getInboundBatchesBySupplier(supplierId); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAccountPaymentApplication(AccountPaymentApplication accountPaymentApplication) { |
| | | if (StringUtils.isEmpty(accountPaymentApplication.getInvoiceApplicationNo())) { |
| | | accountPaymentApplication.setInvoiceApplicationNo(genPaymentApplicationNo()); |
| | | } |
| | | String stockInRecordIds= accountPaymentApplication.getStockInRecordIds(); |
| | | if (stockInRecordIds != null && !stockInRecordIds.isEmpty()) { |
| | | List<Long> ids = Arrays.stream(stockInRecordIds.split(",")) |
| | | .map(Long::valueOf) |
| | | .toList(); |
| | | if (accountPaymentApplicationMapper.existsByStockInRecordId(ids)){ |
| | | throw new ServiceException("åå¨éå¤çå
¥åºå"); |
| | | } |
| | | } |
| | | return save(accountPaymentApplication); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountPaymentApplication(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | throw new ServiceException("å é¤å¤±è´¥ï¼è¯·éæ©è¦å é¤çæ°æ®"); |
| | | } |
| | | //夿æ¯å¦å·²ç»æå¯¹åºç仿¬¾å,妿æåæ æ³å é¤ |
| | | List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery().in(AccountPurchasePayment::getAccountPaymentApplicationId, ids)); |
| | | if (CollectionUtils.isNotEmpty(accountPurchasePayments)){ |
| | | throw new ServiceException("å é¤å¤±è´¥ï¼å·²ç»æå
³èç仿¬¾å"); |
| | | } |
| | | //å é¤å¼ç¥¨ç³è¯· |
| | | return removeBatchByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountPaymentApplication(HttpServletResponse response, AccountPaymentApplicationDto accountPaymentApplicationDto) { |
| | | List<AccountPaymentApplicationVo> list = accountPaymentApplicationMapper.listPageAccountPaymentApplication(new Page(1,-1),accountPaymentApplicationDto).getRecords(); |
| | | ExcelUtil<AccountPaymentApplicationVo> util = new ExcelUtil<>(AccountPaymentApplicationVo.class); |
| | | util.exportExcel(response, list , "仿¬¾ç³è¯·"); |
| | | } |
| | | |
| | | private String genPaymentApplicationNo() { |
| | | return "FK" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchaseInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.mapper.purchase.AccountPurchaseInvoiceMapper; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice; |
| | | import com.ruoyi.account.service.purchase.AccountPurchaseInvoiceService; |
| | | import com.ruoyi.basic.mapper.StorageAttachmentMapper; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--è¿é¡¹å票 æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:06:17 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountPurchaseInvoiceServiceImpl extends ServiceImpl<AccountPurchaseInvoiceMapper, AccountPurchaseInvoice> implements AccountPurchaseInvoiceService { |
| | | |
| | | private final AccountPurchaseInvoiceMapper accountPurchaseInvoiceMapper; |
| | | private final StorageAttachmentMapper storageAttachmentMapper; |
| | | |
| | | @Override |
| | | public IPage<AccountPurchaseInvoiceVo> listPageAccountPurchaseInvoice(Page page, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) { |
| | | return accountPurchaseInvoiceMapper.listPageAccountPurchaseInvoice(page, accountPurchaseInvoiceDto); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountPurchaseInvoice(List<Long> ids) { |
| | | List<AccountPurchaseInvoice> accountPurchaseInvoices = accountPurchaseInvoiceMapper.selectByIds(ids); |
| | | //å é¤éä»¶ |
| | | storageAttachmentMapper.deleteBatchIds(accountPurchaseInvoices.stream().map(AccountPurchaseInvoice::getStorageAttachmentId).toList()); |
| | | return removeBatchByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountPurchaseInvoice(HttpServletResponse response, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) { |
| | | List<AccountPurchaseInvoiceVo> list = accountPurchaseInvoiceMapper.listPageAccountPurchaseInvoice(new Page(1,-1),accountPurchaseInvoiceDto).getRecords(); |
| | | ExcelUtil<AccountPurchaseInvoiceVo> util = new ExcelUtil<>(AccountPurchaseInvoiceVo.class); |
| | | util.exportExcel(response, list , "è¿é¡¹å票"); |
| | | } |
| | | |
| | | @Override |
| | | public List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId) { |
| | | return accountPurchaseInvoiceMapper.getInboundBatchesBySupplier(supplierId); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.github.xiaoymin.knife4j.core.util.CollectionUtils; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo; |
| | | import com.ruoyi.account.mapper.AccountStatementDetailsMapper; |
| | | import com.ruoyi.account.mapper.purchase.AccountPaymentApplicationMapper; |
| | | import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper; |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import com.ruoyi.account.pojo.purchase.AccountPaymentApplication; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.account.service.purchase.AccountPurchasePaymentService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | import java.util.Random; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾å æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 04:14:51 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountPurchasePaymentServiceImpl extends ServiceImpl<AccountPurchasePaymentMapper, AccountPurchasePayment> implements AccountPurchasePaymentService { |
| | | |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss"); |
| | | private final AccountPurchasePaymentMapper accountPurchasePaymentMapper; |
| | | private final AccountStatementDetailsMapper accountStatementDetailsMapper; |
| | | private final AccountPaymentApplicationMapper accountPaymentApplicationMapper; |
| | | |
| | | @Override |
| | | public IPage<AccountPurchasePaymentVo> listPageAccountPurchasePayment(Page page, AccountPurchasePaymentDto accountPurchasePaymentDto) { |
| | | return accountPurchasePaymentMapper.listPageAccountPurchasePayment(page, accountPurchasePaymentDto); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAccountPurchasePayment(AccountPurchasePayment accountPurchasePayment) { |
| | | if (StringUtils.isEmpty(accountPurchasePayment.getPaymentNumber())) { |
| | | accountPurchasePayment.setPaymentNumber(genAccountPurchasePaymentNo()); |
| | | } |
| | | //æ ¡éªç´¯è®¡ä»æ¬¾éé¢ä¸è½è¶
è¿ç³è¯·éé¢ |
| | | AccountPaymentApplication accountPaymentApplication = accountPaymentApplicationMapper.selectById(accountPurchasePayment.getAccountPaymentApplicationId()); |
| | | List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery().eq(AccountPurchasePayment::getAccountPaymentApplicationId, accountPurchasePayment.getAccountPaymentApplicationId())); |
| | | BigDecimal totalPaymentAmount = accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentAmount).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (accountPaymentApplication.getPaymentAmount().compareTo(totalPaymentAmount) < 0) { |
| | | throw new ServiceException("ç´¯è®¡ä»æ¬¾éé¢ä¸è½è¶
è¿ç³è¯·éé¢"); |
| | | } |
| | | return save(accountPurchasePayment); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountPurchasePayment(HttpServletResponse response, AccountPurchasePaymentDto accountPurchasePaymentDto) { |
| | | List<AccountPurchasePaymentVo> list = accountPurchasePaymentMapper.listPageAccountPurchasePayment(new Page(1,-1),accountPurchasePaymentDto).getRecords(); |
| | | ExcelUtil<AccountPurchasePaymentVo> util = new ExcelUtil<>(AccountPurchasePaymentVo.class); |
| | | util.exportExcel(response, list , "仿¬¾å"); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountPurchasePayment(List<Long> ids) { |
| | | //å¦æè¯¥ä»æ¬¾åå·²ç»çæå¯¹è´¦ååæ æ³å é¤ |
| | | List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectByIds(ids); |
| | | List<String> strings = accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentNumber).toList(); |
| | | List<AccountStatementDetails> accountStatementDetails = accountStatementDetailsMapper.selectList(Wrappers.<AccountStatementDetails>lambdaQuery() |
| | | .in(AccountStatementDetails::getReceiptNumber, strings)); |
| | | if (CollectionUtils.isNotEmpty(accountStatementDetails)){ |
| | | throw new ServiceException("è¯¥ä»æ¬¾åå·²ç»çæå¯¹è´¦åï¼æ æ³å é¤"); |
| | | } |
| | | return removeByIds(ids); |
| | | } |
| | | |
| | | private String genAccountPurchasePaymentNo() { |
| | | return "SK" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto; |
| | | import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo; |
| | | import com.ruoyi.account.service.financial.AccountPurchaseService; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper; |
| | | import com.ruoyi.stock.mapper.StockInRecordMapper; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountPurchaseServiceImpl implements AccountPurchaseService { |
| | | |
| | | private final StockInRecordMapper stockInRecordMapper; |
| | | |
| | | private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper; |
| | | |
| | | |
| | | @Override |
| | | public IPage<PurchaseInboundVo> listPageAccountPurchase(Page page, PurchaseInboundDto purchaseInboundDto) { |
| | | return stockInRecordMapper.listPageAccountPurchase(page,purchaseInboundDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountPurchaseInbound(HttpServletResponse response, PurchaseInboundDto purchaseInboundDto) { |
| | | List<PurchaseInboundVo> list = stockInRecordMapper.listPageAccountPurchase(new Page(1,-1),purchaseInboundDto).getRecords(); |
| | | ExcelUtil<PurchaseInboundVo> util = new ExcelUtil<>(PurchaseInboundVo.class); |
| | | util.exportExcel(response, list , "éè´å
¥åº"); |
| | | } |
| | | |
| | | @Override |
| | | public IPage<PurchaseReturnVo> listPageAccountPurchaseReturn(Page page, PurchaseReturnDto purchaseReturnDto) { |
| | | return purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(page,purchaseReturnDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountPurchaseReturn(HttpServletResponse response, PurchaseReturnDto purchaseReturnDto) { |
| | | List<PurchaseReturnVo> list = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1,-1),purchaseReturnDto).getRecords(); |
| | | ExcelUtil<PurchaseReturnVo> util = new ExcelUtil<>(PurchaseReturnVo.class); |
| | | util.exportExcel(response, list , "éè´éè´§"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.sales.AccountInvoiceApplicationDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.mapper.sales.AccountInvoiceApplicationMapper; |
| | | import com.ruoyi.account.mapper.sales.AccountSalesInvoiceMapper; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesInvoice; |
| | | import com.ruoyi.account.service.sales.AccountInvoiceApplicationService; |
| | | import com.ruoyi.basic.mapper.StorageAttachmentMapper; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Random; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯· æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 01:38:32 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountInvoiceApplicationServiceImpl extends ServiceImpl<AccountInvoiceApplicationMapper, AccountInvoiceApplication> implements AccountInvoiceApplicationService { |
| | | |
| | | private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper; |
| | | private final AccountSalesInvoiceMapper accountSalesInvoiceMapper; |
| | | private final StorageAttachmentMapper storageAttachmentMapper; |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss"); |
| | | |
| | | @Override |
| | | public IPage<AccountInvoiceApplicationVo> listPageAccountInvoiceApplication(Page page, AccountInvoiceApplicationDto accountInvoiceApplicationDto) { |
| | | return accountInvoiceApplicationMapper.listPageAccountInvoiceApplication(page, accountInvoiceApplicationDto); |
| | | } |
| | | |
| | | @Override |
| | | public List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId) { |
| | | return accountInvoiceApplicationMapper.getOutboundBatchesByCustomer(customerId); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAccountInvoiceApplication(AccountInvoiceApplication accountInvoiceApplication) { |
| | | if (StringUtils.isEmpty(accountInvoiceApplication.getInvoiceApplicationNo())) { |
| | | accountInvoiceApplication.setInvoiceApplicationNo(genInvoiceApplicationNo()); |
| | | } |
| | | String stockOutRecordIds = accountInvoiceApplication.getStockOutRecordIds(); |
| | | if (stockOutRecordIds != null && !stockOutRecordIds.isEmpty()) { |
| | | List<Long> ids = Arrays.stream(stockOutRecordIds.split(",")) |
| | | .map(Long::valueOf) |
| | | .toList(); |
| | | if (accountInvoiceApplicationMapper.existsByStockOutRecordId(ids)){ |
| | | throw new ServiceException("åå¨éå¤çåºåºå"); |
| | | } |
| | | } |
| | | return save(accountInvoiceApplication); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto) { |
| | | List<AccountInvoiceApplicationVo> list = accountInvoiceApplicationMapper.listPageAccountInvoiceApplication(new Page(1,-1),accountInvoiceApplicationDto).getRecords(); |
| | | ExcelUtil<AccountInvoiceApplicationVo> util = new ExcelUtil<>(AccountInvoiceApplicationVo.class); |
| | | util.exportExcel(response, list , "å¼ç¥¨ç³è¯·"); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountInvoiceApplication(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | throw new ServiceException("å é¤å¤±è´¥ï¼è¯·éæ©è¦å é¤çæ°æ®"); |
| | | } |
| | | //å é¤ç¸å
³éä»¶ |
| | | List<AccountSalesInvoice> accountSalesInvoices = accountSalesInvoiceMapper.selectList(Wrappers.<AccountSalesInvoice>lambdaQuery().in(AccountSalesInvoice::getAccountInvoiceApplicationId,ids)); |
| | | storageAttachmentMapper.deleteBatchIds(accountSalesInvoices.stream().map(AccountSalesInvoice::getStorageAttachmentId).toList()); |
| | | //å é¤ç¸å
³çé项å票 |
| | | accountSalesInvoiceMapper.delete(Wrappers.<AccountSalesInvoice>lambdaQuery().in(AccountSalesInvoice::getAccountInvoiceApplicationId,ids)); |
| | | //å é¤å¼ç¥¨ç³è¯· |
| | | return removeBatchByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public List<AccountInvoiceApplicationVo> getAccountInvoiceApplicationList(Integer customerId) { |
| | | return accountInvoiceApplicationMapper.selectAccountInvoiceApplicationList(customerId); |
| | | } |
| | | |
| | | private String genInvoiceApplicationNo() { |
| | | return "KP" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesCollectionDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.mapper.AccountStatementDetailsMapper; |
| | | import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; |
| | | import com.ruoyi.account.pojo.AccountStatementDetails; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.account.service.sales.AccountSalesCollectionService; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Random; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--æ¶æ¬¾å æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:49:56 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountSalesCollectionServiceImpl extends ServiceImpl<AccountSalesCollectionMapper, AccountSalesCollection> implements AccountSalesCollectionService { |
| | | |
| | | private final AccountSalesCollectionMapper accountSalesCollectionMapper; |
| | | private final AccountStatementDetailsMapper accountStatementDetailsMapper; |
| | | private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss"); |
| | | |
| | | |
| | | @Override |
| | | public IPage<AccountSalesCollectionVo> listPageAccountSalesCollection(Page page, AccountSalesCollectionDto accountSalesCollectionDto) { |
| | | return accountSalesCollectionMapper.listPageAccountSalesCollection(page, accountSalesCollectionDto); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAccountSalesCollection(AccountSalesCollection accountSalesCollection) { |
| | | if (StringUtils.isEmpty(accountSalesCollection.getCollectionNumber())) { |
| | | accountSalesCollection.setCollectionNumber(genAccountSalesCollectionNo()); |
| | | } |
| | | // String stockOutRecordIds = accountSalesCollection.getStockOutRecordIds(); |
| | | // if (stockOutRecordIds != null && !stockOutRecordIds.isEmpty()) { |
| | | // List<Long> ids = Arrays.stream(stockOutRecordIds.split(",")) |
| | | // .map(Long::valueOf) |
| | | // .toList(); |
| | | // if (accountSalesCollectionMapper.existsByStockOutRecordId(ids)){ |
| | | // throw new ServiceException("åå¨éå¤çåºåºå"); |
| | | // } |
| | | // } |
| | | return save(accountSalesCollection); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSalesCollection(HttpServletResponse response, AccountSalesCollectionDto accountSalesCollectionDto) { |
| | | List<AccountSalesCollectionVo> list = accountSalesCollectionMapper.listPageAccountSalesCollection(new Page(1,-1),accountSalesCollectionDto).getRecords(); |
| | | ExcelUtil<AccountSalesCollectionVo> util = new ExcelUtil<>(AccountSalesCollectionVo.class); |
| | | util.exportExcel(response, list , "æ¶æ¬¾å"); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountSalesCollection(List<Long> ids) { |
| | | //å¦æè¯¥æ¶æ¬¾åå·²ç»çæå¯¹è´¦ååæ æ³å é¤ |
| | | List<AccountSalesCollection> accountSalesCollections = accountSalesCollectionMapper.selectByIds(ids); |
| | | List<String> strings = accountSalesCollections.stream().map(AccountSalesCollection::getCollectionNumber).toList(); |
| | | List<AccountStatementDetails> accountStatementDetails = accountStatementDetailsMapper.selectList(Wrappers.<AccountStatementDetails>lambdaQuery() |
| | | .in(AccountStatementDetails::getReceiptNumber, strings)); |
| | | if (CollectionUtils.isNotEmpty(accountStatementDetails)){ |
| | | throw new ServiceException("è¯¥æ¶æ¬¾åå·²ç»çæå¯¹è´¦åï¼æ æ³å é¤"); |
| | | } |
| | | return removeByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId) { |
| | | return accountSalesCollectionMapper.getOutboundBatchesByCustomer(customerId); |
| | | } |
| | | |
| | | private String genAccountSalesCollectionNo() { |
| | | return "SK" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo; |
| | | import com.ruoyi.account.mapper.sales.AccountSalesInvoiceMapper; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesInvoice; |
| | | import com.ruoyi.account.service.sales.AccountSalesInvoiceService; |
| | | import com.ruoyi.basic.mapper.StorageAttachmentMapper; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--é项å票 æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:10:20 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountSalesInvoiceServiceImpl extends ServiceImpl<AccountSalesInvoiceMapper, AccountSalesInvoice> implements AccountSalesInvoiceService { |
| | | |
| | | private final AccountSalesInvoiceMapper accountSalesInvoiceMapper; |
| | | private final StorageAttachmentMapper storageAttachmentMapper; |
| | | |
| | | @Override |
| | | public IPage<AccountSalesInvoiceVo> listPageAccountSalesInvoice(Page page, AccountSalesInvoiceDto accountSalesInvoiceDto) { |
| | | return accountSalesInvoiceMapper.listPageAccountSalesInvoice(page, accountSalesInvoiceDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSalesInvoice(HttpServletResponse response, AccountSalesInvoiceDto accountSalesInvoiceDto) { |
| | | List<AccountSalesInvoiceVo> list = accountSalesInvoiceMapper.listPageAccountSalesInvoice(new Page(1,-1),accountSalesInvoiceDto).getRecords(); |
| | | ExcelUtil<AccountSalesInvoiceVo> util = new ExcelUtil<>(AccountSalesInvoiceVo.class); |
| | | util.exportExcel(response, list , "é项å票"); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteAccountSalesInvoice(List<Long> ids) { |
| | | List<AccountSalesInvoice> accountSalesInvoices = accountSalesInvoiceMapper.selectByIds(ids); |
| | | //å é¤éä»¶ |
| | | storageAttachmentMapper.deleteBatchIds(accountSalesInvoices.stream().map(AccountSalesInvoice::getStorageAttachmentId).toList()); |
| | | return removeBatchByIds(ids); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesReturnVo; |
| | | import com.ruoyi.account.service.sales.AccountSalesService; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper; |
| | | import com.ruoyi.stock.mapper.StockOutRecordMapper; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountSalesServiceImpl implements AccountSalesService { |
| | | |
| | | private final StockOutRecordMapper stockOutRecordMapper; |
| | | |
| | | private final ReturnManagementMapper returnManagementMapper; |
| | | |
| | | @Override |
| | | public IPage<SalesOutboundVo> listPageAccountSales(Page page, SalesOutboundDto salesOutboundDto) { |
| | | return stockOutRecordMapper.listPageAccountSales(page,salesOutboundDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto) { |
| | | List<SalesOutboundVo> list = stockOutRecordMapper.listPageAccountSales(new Page(1,-1),salesOutboundDto).getRecords(); |
| | | ExcelUtil<SalesOutboundVo> util = new ExcelUtil<>(SalesOutboundVo.class); |
| | | util.exportExcel(response, list , "éå®åºåº"); |
| | | } |
| | | |
| | | @Override |
| | | public IPage<SalesReturnVo> listPageAccountSalesReturn(Page page, SalesReturnDto salesReturnDto) { |
| | | return returnManagementMapper.listPageAccountSalesReturn(page,salesReturnDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto) { |
| | | List<SalesReturnVo> list = returnManagementMapper.listPageAccountSalesReturn(new Page(1,-1),salesReturnDto).getRecords(); |
| | | ExcelUtil<SalesReturnVo> util = new ExcelUtil<>(SalesReturnVo.class); |
| | | util.exportExcel(response, list , "éå®éè´§"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPaymentApplicationDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPaymentApplication; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾ç³è¯· æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:44:22 |
| | | */ |
| | | public interface AccountPaymentApplicationService extends IService<AccountPaymentApplication> { |
| | | |
| | | IPage<AccountPaymentApplicationVo> listPageAccountPaymentApplication(Page page, AccountPaymentApplicationDto accountPaymentApplicationDto); |
| | | |
| | | List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId); |
| | | |
| | | boolean addAccountPaymentApplication(AccountPaymentApplication accountPaymentApplication); |
| | | |
| | | boolean deleteAccountPaymentApplication(List<Long> ids); |
| | | |
| | | void exportAccountPaymentApplication(HttpServletResponse response, AccountPaymentApplicationDto accountPaymentApplicationDto); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchaseInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo; |
| | | import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--è¿é¡¹å票 æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 03:06:17 |
| | | */ |
| | | public interface AccountPurchaseInvoiceService extends IService<AccountPurchaseInvoice> { |
| | | |
| | | IPage<AccountPurchaseInvoiceVo> listPageAccountPurchaseInvoice(Page page, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto); |
| | | |
| | | boolean deleteAccountPurchaseInvoice(List<Long> ids); |
| | | |
| | | void exportAccountPurchaseInvoice(HttpServletResponse response, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto); |
| | | |
| | | List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto; |
| | | import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--仿¬¾å æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-19 04:14:51 |
| | | */ |
| | | public interface AccountPurchasePaymentService extends IService<AccountPurchasePayment> { |
| | | |
| | | IPage<AccountPurchasePaymentVo> listPageAccountPurchasePayment(Page page, AccountPurchasePaymentDto accountPurchasePaymentDto); |
| | | |
| | | boolean addAccountPurchasePayment(AccountPurchasePayment accountPurchasePayment); |
| | | |
| | | void exportAccountPurchasePayment(HttpServletResponse response, AccountPurchasePaymentDto accountPurchasePaymentDto); |
| | | |
| | | boolean deleteAccountPurchasePayment(List<Long> ids); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.purchase; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.financial.AccountSubjectDto; |
| | | import com.ruoyi.account.bean.vo.financial.AccountSubjectVo; |
| | | import com.ruoyi.account.pojo.financial.AccountSubject; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * æ»è´¦ç§ç®è¡¨ æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | public interface AccountSubjectService extends IService<AccountSubject> { |
| | | |
| | | IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto); |
| | | |
| | | Boolean saveAccountSubject(AccountSubjectDto accountSubjectDto); |
| | | |
| | | Boolean updateAccountSubject(AccountSubjectDto accountSubjectDto); |
| | | |
| | | Boolean removeAccountSubjectByIds(List<Long> ids); |
| | | |
| | | void exportAccountSubject(HttpServletResponse response); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.account.bean.dto.sales.AccountInvoiceApplicationDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--å¼ç¥¨ç³è¯· æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 01:38:32 |
| | | */ |
| | | public interface AccountInvoiceApplicationService extends IService<AccountInvoiceApplication> { |
| | | |
| | | IPage<AccountInvoiceApplicationVo> listPageAccountInvoiceApplication(Page page, AccountInvoiceApplicationDto accountInvoiceApplicationDto); |
| | | |
| | | List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId); |
| | | |
| | | boolean addAccountInvoiceApplication(AccountInvoiceApplication accountInvoiceApplication); |
| | | |
| | | void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto); |
| | | |
| | | boolean deleteAccountInvoiceApplication(List<Long> ids); |
| | | |
| | | List<AccountInvoiceApplicationVo> getAccountInvoiceApplicationList(Integer customerId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesCollectionDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--æ¶æ¬¾å æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:49:56 |
| | | */ |
| | | public interface AccountSalesCollectionService extends IService<AccountSalesCollection> { |
| | | |
| | | IPage<AccountSalesCollectionVo> listPageAccountSalesCollection(Page page, AccountSalesCollectionDto accountSalesCollectionDto); |
| | | |
| | | boolean addAccountSalesCollection(AccountSalesCollection accountSalesCollection); |
| | | |
| | | void exportAccountSalesCollection(HttpServletResponse response, AccountSalesCollectionDto accountSalesCollectionDto); |
| | | |
| | | boolean deleteAccountSalesCollection(List<Long> ids); |
| | | |
| | | List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.AccountSalesInvoiceDto; |
| | | import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesInvoice; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ç--é项å票 æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-18 03:10:20 |
| | | */ |
| | | public interface AccountSalesInvoiceService extends IService<AccountSalesInvoice> { |
| | | |
| | | IPage<AccountSalesInvoiceVo> listPageAccountSalesInvoice(Page page, AccountSalesInvoiceDto accountSalesInvoiceDto); |
| | | |
| | | void exportAccountSalesInvoice(HttpServletResponse response, AccountSalesInvoiceDto accountSalesInvoiceDto); |
| | | |
| | | boolean deleteAccountSalesInvoice(List<Long> ids); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.sales; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.sales.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.sales.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.sales.SalesReturnVo; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | public interface AccountSalesService { |
| | | |
| | | IPage<SalesOutboundVo> listPageAccountSales(Page page, SalesOutboundDto salesOutboundDto); |
| | | |
| | | void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto); |
| | | |
| | | IPage<SalesReturnVo> listPageAccountSalesReturn(Page page, SalesReturnDto salesReturnDto); |
| | | |
| | | void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto); |
| | | } |
| | |
| | | /** |
| | | * å建æ¶é´ |
| | | */ |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private LocalDateTime createTime; |
| | | |
| | | /** |
| | |
| | | import com.ruoyi.aftersalesservice.service.AfterSalesServiceService; |
| | | import com.ruoyi.common.utils.OrderUtils; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.project.system.domain.SysDept; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | |
| | | if(sysUser == null) throw new RuntimeException("å®¡æ ¸äººä¸åå¨"); |
| | | afterSalesServiceNewDto.setCheckNickName(sysUser.getNickName()); |
| | | if (StringUtils.isEmpty(afterSalesServiceNewDto.getAfterSalesServiceNo())) { |
| | | String string = OrderUtils.countAfterServiceTodayByCreateTime(afterSalesServiceMapper, "SH_"); |
| | | String string = OrderUtils.countAfterServiceTodayByCreateTime(afterSalesServiceMapper, "SH_", afterSalesServiceNewDto.getCreateTime() != null ? afterSalesServiceNewDto.getCreateTime() : LocalDateTime.now()); |
| | | afterSalesServiceNewDto.setAfterSalesServiceNo(string); |
| | | } |
| | | return this.save(afterSalesServiceNewDto); |
| | |
| | | import dev.langchain4j.service.MemoryId; |
| | | import dev.langchain4j.service.SystemMessage; |
| | | import dev.langchain4j.service.UserMessage; |
| | | import dev.langchain4j.service.V; |
| | | import dev.langchain4j.service.spring.AiService; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | |
| | | public interface ApproveTodoAgent { |
| | | |
| | | @SystemMessage(fromResource = "approve-todo-agent-prompt.txt") |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage); |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate); |
| | | } |
| | |
| | | @Component |
| | | public class ApproveTodoIntentExecutor { |
| | | |
| | | private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{8,}\\b"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?(\\d{1,2})æ¡"); |
| | | private static final Pattern APPROVE_ID_BY_LABEL_PATTERN = Pattern.compile("(æµç¨ç¼å·|æµç¨å·|æµç¨ID|审æ¹ç¼å·|ç¼å·)\\s*[:ï¼]?\\s*([A-Za-z0-9_-]{2,64})"); |
| | | private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{6,}[A-Za-z0-9_-]*\\b"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?\\s*(\\d{1,2})\\s*æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?)"); |
| | | private static final Pattern RECENT_RANGE_PATTERN = Pattern.compile("è¿\\d+(天|å¨|个æ|æ|å¹´)"); |
| | |
| | | } |
| | | |
| | | String text = message.trim(); |
| | | String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); |
| | | if (StringUtils.hasText(quickPromptResponse)) { |
| | | return quickPromptResponse; |
| | | } |
| | | |
| | | String approveId = extractApproveId(text); |
| | | boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId); |
| | | String startDate = extractStartDate(text); |
| | | String endDate = extractEndDate(text); |
| | | String timeRange = extractTimeRange(text); |
| | | |
| | | if (isStatsIntent(text)) { |
| | | return approveTodoTools.getTodoStats( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | extractTimeRange(text) |
| | | startDate, |
| | | endDate, |
| | | timeRange |
| | | ); |
| | | } |
| | | if (containsAny(text, "æµè½¬", "è¿åº¦", "èç¹", "æ¥å¿", "å¡å¨", "å¡å°", "å½å审æ¹äºº", "å¤çè®°å½")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoProgress(memoryId, approveId) |
| | | : missingApproveId("todo_progress", "æ¥è¯¢å®¡æ¹è¿åº¦éè¦æä¾æµç¨ç¼å·ã"); |
| | | } |
| | | if (containsAny(text, "详æ
", "æç»") && !containsAny(text, "å表")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoDetail(memoryId, approveId) |
| | | : missingApproveId("todo_detail", "æ¥è¯¢å®¡æ¹è¯¦æ
éè¦æä¾æµç¨ç¼å·ã"); |
| | | } |
| | | if (containsAny(text, "åæ¶å®¡æ ¸", "æ¤éå®¡æ ¸", "åéå®¡æ ¸", "æ¤é审æ¹", "æ¤å审æ¹") |
| | | || (containsAny(text, "æ¤é", "æ¤å") && containsAny(text, "å®¡æ¹æä½", "å®¡æ ¸æä½"))) { |
| | | return StringUtils.hasText(approveId) |
| | | ? approveTodoTools.cancelReviewTodo(memoryId, approveId, firstNonBlank(extractTail(text, "åå "), extractTail(text, "夿³¨"))) |
| | | return hasApproveId |
| | | ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text)) |
| | | : missingApproveId("cancel_review_action", "åæ¶å®¡æ ¸éè¦æä¾æµç¨ç¼å·ã"); |
| | | } |
| | | if (containsAny(text, "å é¤", "ç§»é¤")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.deleteTodo(memoryId, approveId) |
| | | : missingApproveId("delete_action", "å é¤å®¡æ¹åéè¦æä¾æµç¨ç¼å·ã"); |
| | | } |
| | | if (containsAny(text, "驳å", "æç»")) { |
| | | return StringUtils.hasText(approveId) |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", firstNonBlank(extractTail(text, "åå "), extractTail(text, "夿³¨"))) |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text)) |
| | | : missingApproveId("review_action", "驳å审æ¹éè¦æä¾æµç¨ç¼å·ã"); |
| | | } |
| | | if (containsAny(text, "å®¡æ ¸éè¿", "审æ¹éè¿", "éè¿å®¡æ¹", "åæå®¡æ¹", "审æ¹åæ")) { |
| | | return StringUtils.hasText(approveId) |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractTail(text, "夿³¨")) |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)) |
| | | : missingApproveId("review_action", "审æ¹éè¿éè¦æä¾æµç¨ç¼å·ã"); |
| | | } |
| | | if (StringUtils.hasText(approveId) |
| | | if (hasApproveId |
| | | && containsAny(text, "éè¿", "åæ") |
| | | && !containsAny(text, "æªéè¿", "éè¿ç", "审æ¹éè¿ç", "å®¡æ ¸éè¿ç")) { |
| | | return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractTail(text, "夿³¨")); |
| | | return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)); |
| | | } |
| | | if (containsAny(text, "ä¿®æ¹", "æ´æ°", "åæ´")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.updateTodo( |
| | | memoryId, |
| | | approveId, |
| | |
| | | extractApproveType(text), |
| | | extractKeyword(text), |
| | | extractLimit(text), |
| | | extractScope(text)); |
| | | extractScope(text), |
| | | startDate, |
| | | endDate, |
| | | timeRange); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String tryExecuteQuickPrompt(String memoryId, String text) { |
| | | String normalized = normalizeForMatch(text); |
| | | String approveId = extractApproveId(text); |
| | | boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId); |
| | | |
| | | if ("æå½åæåªäºå®¡æ¹å¾
åéè¦å¤ç".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null); |
| | | } |
| | | if ("帮æååºä»å¤©æ°å¢ç审æ¹å¾
å".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "all", null, null, 10, "related", null, null, "ä»å¤©"); |
| | | } |
| | | if ("å½åå¾
æå®¡æ¹çåæ®ææ¶é´ååºååºæ¥".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null); |
| | | } |
| | | if ("æåèµ·ç审æ¹éåªäºè¿å¨å¤çä¸".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "processing", null, null, 10, "applicant", null, null, null); |
| | | } |
| | | if ("è¿7天æç审æ¹å¾
åç»è®¡æ
嵿乿 ·".equals(normalized)) { |
| | | return approveTodoTools.getTodoStats(memoryId, null, null, "è¿7天"); |
| | | } |
| | | if ("æ¬ææç审æ¹ä¸éè¿é©³åå¤çä¸åæå¤å°".equals(normalized)) { |
| | | return approveTodoTools.getTodoStats(memoryId, null, null, "æ¬æ"); |
| | | } |
| | | if ("è¿30天åç±»åå®¡æ¹æ°éå叿¯ä»ä¹".equals(normalized)) { |
| | | return approveTodoTools.getTodoStats(memoryId, null, null, "è¿30天"); |
| | | } |
| | | |
| | | if (normalized.startsWith("æ¥è¯¢æµç¨ç¼å·") && normalized.contains("审æ¹è¯¦æ
")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoDetail(memoryId, approveId) |
| | | : missingApproveId("todo_detail", "æ¥è¯¢å®¡æ¹è¯¦æ
éè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("æµç¨ç¼å·") |
| | | && normalized.contains("å¡å¨åªä¸ªå®¡æ¹èç¹") |
| | | && normalized.contains("å½å审æ¹äººæ¯è°")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoProgress(memoryId, approveId) |
| | | : missingApproveId("todo_progress", "æ¥è¯¢å®¡æ¹è¿åº¦éè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("å¸®ææ¥çæµç¨ç¼å·") && normalized.contains("å®¡æ¹æµè½¬è®°å½")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoProgress(memoryId, approveId) |
| | | : missingApproveId("todo_progress", "æ¥è¯¢å®¡æ¹æµè½¬è®°å½éè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("帮æå®¡æ¹éè¿æµç¨ç¼å·")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)) |
| | | : missingApproveId("review_action", "审æ¹éè¿éè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("帮æé©³åæµç¨ç¼å·")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text)) |
| | | : missingApproveId("review_action", "驳å审æ¹éè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("æ¤éæåå对æµç¨ç¼å·") && normalized.contains("å®¡æ¹æä½")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text)) |
| | | : missingApproveId("cancel_review_action", "æ¤éå®¡æ¹æä½éè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("帮æä¿®æ¹æµç¨ç¼å·") && normalized.contains("夿³¨ä¸º")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.updateTodo(memoryId, approveId, null, null, null, null, null, null, extractRemark(text)) |
| | | : missingApproveId("update_action", "ä¿®æ¹å®¡æ¹åéè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | if (normalized.startsWith("å 餿åèµ·çæµç¨ç¼å·")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.deleteTodo(memoryId, approveId) |
| | | : missingApproveId("delete_action", "å é¤å®¡æ¹åéè¦æä¾ç宿µç¨ç¼å·ã"); |
| | | } |
| | | return null; |
| | | } |
| | |
| | | } |
| | | |
| | | private String extractApproveId(String text) { |
| | | Matcher keywordMatcher = APPROVE_ID_BY_LABEL_PATTERN.matcher(text); |
| | | if (keywordMatcher.find()) { |
| | | return keywordMatcher.group(2); |
| | | } |
| | | Matcher matcher = APPROVE_ID_PATTERN.matcher(text); |
| | | return matcher.find() ? matcher.group() : null; |
| | | } |
| | |
| | | .replace("åæ®", "") |
| | | .replace("å¾
å", "") |
| | | .replace("å表", "") |
| | | .replace("æµç¨ç¼å·", "") |
| | | .replace("æµç¨å·", "") |
| | | .replace("å10æ¡", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .trim(); |
| | |
| | | } |
| | | |
| | | private String extractValue(String text, String fieldName) { |
| | | Pattern pattern = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|æ¯)[:ï¼]?[\\s]*([^,ï¼ãï¼;\\s]+)"); |
| | | Pattern pattern = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|为|æ¯)[:ï¼]?[\\s]*([^,ï¼ãï¼;\\s]+)"); |
| | | Matcher matcher = pattern.matcher(text); |
| | | return matcher.find() ? matcher.group(2) : null; |
| | | } |
| | |
| | | if (!text.contains(fieldName)) { |
| | | return null; |
| | | } |
| | | Matcher matcher = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|æ¯)[:ï¼]?[\\s]*(\\d{1,2})").matcher(text); |
| | | Matcher matcher = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|为|æ¯)[:ï¼]?[\\s]*(\\d{1,2})").matcher(text); |
| | | return matcher.find() ? Integer.parseInt(matcher.group(2)) : null; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | private String extractTail(String text, String key) { |
| | | Pattern quotedPattern = Pattern.compile(key + "(æ¯|为)?[:ï¼]?[\\s]*[â\"]([^â\"]+)[â\"]"); |
| | | Matcher quotedMatcher = quotedPattern.matcher(text); |
| | | if (quotedMatcher.find()) { |
| | | return cleanContent(quotedMatcher.group(2)); |
| | | } |
| | | Pattern pattern = Pattern.compile(key + "(æ¯|为)?[:ï¼]?[\\s]*(.+)"); |
| | | Matcher matcher = pattern.matcher(text); |
| | | return matcher.find() ? matcher.group(2).trim() : null; |
| | | return matcher.find() ? cleanContent(matcher.group(2)) : null; |
| | | } |
| | | |
| | | private String extractScope(String text) { |
| | | if (containsAny(text, "æåèµ·", "ææäº¤", "æç³è¯·", "ç³è¯·äººæ¯æ")) { |
| | | return "applicant"; |
| | | } |
| | | if (containsAny(text, "å¾
æå®¡æ¹", "å¾
æå®¡æ ¸", "æå¤ç", "æå®¡æ¹", "å½åå¾
æ", "éè¦æå¤ç")) { |
| | | if (containsAny(text, "å¾
æå®¡æ¹", "å¾
æå®¡æ ¸", "æå¤ç", "æå®¡æ¹", "å½åå¾
æ", "éè¦æå¤ç", "éè¦å¤ç")) { |
| | | return "approver"; |
| | | } |
| | | return "related"; |
| | | } |
| | | |
| | | private String extractRemark(String text) { |
| | | return firstNonBlank(firstNonBlank(extractTail(text, "夿³¨"), extractTail(text, "åå ")), extractQuotedContent(text)); |
| | | } |
| | | |
| | | private String extractQuotedContent(String text) { |
| | | Matcher matcher = Pattern.compile("[â\"]([^â\"]+)[â\"]").matcher(text); |
| | | return matcher.find() ? cleanContent(matcher.group(1)) : null; |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace("ï¼", "") |
| | | .replace(",", "") |
| | | .replace("ã", "") |
| | | .replace(".", "") |
| | | .replace("ï¼", "") |
| | | .replace("!", "") |
| | | .replace("ï¼", "") |
| | | .replace("?", "") |
| | | .replace("ï¼", "") |
| | | .replace(":", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .replace("â", "") |
| | | .replace("â", "") |
| | | .replace("\"", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private boolean isPlaceholderApproveId(String approveId) { |
| | | if (!StringUtils.hasText(approveId)) { |
| | | return true; |
| | | } |
| | | String value = approveId.trim(); |
| | | return "xxx".equalsIgnoreCase(value) |
| | | || value.matches("[xX]{2,}") |
| | | || "æµç¨ç¼å·".equals(value) |
| | | || "ç¼å·".equals(value) |
| | | || value.contains("示ä¾") |
| | | || value.contains("请è¾å
¥"); |
| | | } |
| | | |
| | | private String cleanContent(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | return text.trim() |
| | | .replace("â", "") |
| | | .replace("â", "") |
| | | .replace("\"", "") |
| | | .replace("ã", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private String firstNonBlank(String first, String second) { |
| | | return StringUtils.hasText(first) ? first : second; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import dev.langchain4j.service.MemoryId; |
| | | import dev.langchain4j.service.SystemMessage; |
| | | import dev.langchain4j.service.UserMessage; |
| | | import dev.langchain4j.service.V; |
| | | import dev.langchain4j.service.spring.AiService; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; |
| | | |
| | | @AiService( |
| | | wiringMode = EXPLICIT, |
| | | streamingChatModel = "qwenStreamingChatModel", |
| | | chatMemoryProvider = "chatMemoryProviderFinancial", |
| | | tools = "financialAgentTools" |
| | | ) |
| | | public interface FinancialAgent { |
| | | |
| | | @SystemMessage(fromResource = "financial-agent-prompt.txt") |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import com.ruoyi.ai.tools.FinancialAgentTools; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | | @Component |
| | | public class FinancialIntentExecutor { |
| | | |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(?:å|æè¿|å±ç¤º|è¿å)?\\s*(\\d{1,2})\\s*(?:æ¡|个|å)"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final Pattern RELATIVE_DAY_PATTERN = Pattern.compile("(?:è¿|æè¿)\\s*(\\d{1,3})\\s*天"); |
| | | private static final Pattern FUTURE_MONTH_PATTERN = Pattern.compile("(?:æªæ¥|åç»|æ¥ä¸æ¥)\\s*(\\d{1,2})\\s*(?:个)?æ"); |
| | | |
| | | private final FinancialAgentTools financialAgentTools; |
| | | |
| | | public FinancialIntentExecutor(FinancialAgentTools financialAgentTools) { |
| | | this.financialAgentTools = financialAgentTools; |
| | | } |
| | | |
| | | public String tryExecute(String memoryId, String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return null; |
| | | } |
| | | String text = message.trim(); |
| | | |
| | | String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); |
| | | if (StringUtils.hasText(quickPromptResponse)) { |
| | | return quickPromptResponse; |
| | | } |
| | | |
| | | DateRange dateRange = extractDateRange(text); |
| | | Integer limit = extractLimit(text); |
| | | String keyword = extractKeyword(text); |
| | | String startDate = dateRange.startDate(); |
| | | String endDate = dateRange.endDate(); |
| | | String timeRange = dateRange.label(); |
| | | |
| | | if (containsAny(text, "ææ¬æ ¸ç®", "äº§åææ¬", "å·¥åºææ¬", "äººå·¥ææ¬", "ææ§", "æææè", "ææ¬æé«")) { |
| | | return financialAgentTools.calculateIntelligentCost(memoryId, startDate, endDate, timeRange, keyword, limit); |
| | | } |
| | | if (containsAny(text, "婿¶¦åæ", "订å婿¶¦", "äºæè®¢å", "ä½å©æ¶¦", "æèµé±å®¢æ·", "åªä¸ªå®¢æ·æèµé±", |
| | | "å®¢æ·æèµé±", "婿¶¦æé«å®¢æ·", "婿¶¦è´¡ç®æé«", "婿¶¦ä¸é")) { |
| | | return financialAgentTools.analyzeOrderProfit(memoryId, startDate, endDate, timeRange, keyword, limit); |
| | | } |
| | | if (containsAny(text, "åºåèµé", "åºå积å", "åæ»åºå", "èµéå ç¨", "å¨è½¬ç", "åºåå¨è½¬")) { |
| | | return financialAgentTools.analyzeInventoryCapital(memoryId, startDate, endDate, timeRange, keyword, limit); |
| | | } |
| | | if (containsAny(text, "ç°éæµ", "忬¾é£é©", "仿¬¾åå", "èµé缺å£", "åºæ¶", "åºä»", "忬¾é¢æµ")) { |
| | | return financialAgentTools.forecastCashFlow(memoryId, startDate, endDate, timeRange, extractForecastMonths(text)); |
| | | } |
| | | if (containsAny(text, "å¼å¸¸é¢è¦", "ç»è¥å¼å¸¸", "é£é©é¢è¦", "ææ¬å¼å¸¸", "婿¶¦å¼å¸¸", "忬¾å¼å¸¸", "订åé£é©")) { |
| | | return financialAgentTools.detectBusinessAnomalies(memoryId, startDate, endDate, timeRange, limit); |
| | | } |
| | | if (containsAny(text, "驾驶è±", "ç»è¥çæ¿", "ç»è¥æ»è§", "ç»è¥ä»ªè¡¨ç", "ç»è¥å¤§ç")) { |
| | | return financialAgentTools.getBusinessCockpit(memoryId, startDate, endDate, timeRange); |
| | | } |
| | | if (containsAny(text, "æ¥æ¥", "卿¥", "ç»è¥æ¥å", "åææ¥å")) { |
| | | return financialAgentTools.generateOperationReport(memoryId, startDate, endDate, timeRange, |
| | | containsAny(text, "卿¥") ? "weekly" : "daily"); |
| | | } |
| | | if (containsAny(text, "ä¸è´¢èå", "ä¸è´¢èå¨", "å£å¾", "ææ è§£é", "为ä»ä¹")) { |
| | | return financialAgentTools.retrieveFinancialKnowledge(memoryId, text); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String tryExecuteQuickPrompt(String memoryId, String text) { |
| | | String normalized = normalizeForMatch(text); |
| | | if ("çææ¬å¨ç»è¥å¨æ¥å©æ¶¦ä¸ç°éæµ".equals(normalized) || "çææ¬å¨ç»è¥å¨æ¥".equals(normalized) || "çæå¨æ¥".equals(normalized)) { |
| | | DateRange range = weekRange(); |
| | | return financialAgentTools.generateOperationReport(memoryId, range.startDate(), range.endDate(), range.label(), "weekly"); |
| | | } |
| | | if ("åææ¬æå©æ¶¦ä¸éåå ".equals(normalized)) { |
| | | DateRange range = monthRange(); |
| | | return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null); |
| | | } |
| | | if ("è¿30天åªä¸ªå®¢æ·å©æ¶¦è´¡ç®æé«".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null); |
| | | } |
| | | if ("æ¥çæ¬æç»è¥é©¾é©¶è±".equals(normalized) || "æ¥çç»è¥é©¾é©¶è±".equals(normalized)) { |
| | | DateRange range = monthRange(); |
| | | return financialAgentTools.getBusinessCockpit(memoryId, range.startDate(), range.endDate(), range.label()); |
| | | } |
| | | if ("æ¥è¯¢è¿30å¤©äºæè®¢å".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null); |
| | | } |
| | | if ("åæè¿30天åºåèµéå ç¨".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return financialAgentTools.analyzeInventoryCapital(memoryId, range.startDate(), range.endDate(), range.label(), null, null); |
| | | } |
| | | if ("颿µæªæ¥3个æç°éæµ".equals(normalized)) { |
| | | return financialAgentTools.forecastCashFlow(memoryId, null, null, null, 3); |
| | | } |
| | | if ("åªä¸ªå·¥åºææ¬æé«".equals(normalized)) { |
| | | return financialAgentTools.calculateIntelligentCost(memoryId, null, null, null, null, null); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private Integer extractLimit(String text) { |
| | | Matcher matcher = LIMIT_PATTERN.matcher(text); |
| | | return matcher.find() ? Integer.parseInt(matcher.group(1)) : null; |
| | | } |
| | | |
| | | private Integer extractForecastMonths(String text) { |
| | | Matcher matcher = FUTURE_MONTH_PATTERN.matcher(text); |
| | | return matcher.find() ? Integer.parseInt(matcher.group(1)) : null; |
| | | } |
| | | |
| | | private DateRange extractDateRange(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | if (matcher.find()) { |
| | | String first = matcher.group(1); |
| | | String second = matcher.find() ? matcher.group(1) : first; |
| | | return buildDateRange(first, second, first + "è³" + second); |
| | | } |
| | | if (text.contains("æ¬æ")) { |
| | | return monthRange(); |
| | | } |
| | | if (text.contains("䏿")) { |
| | | return lastMonthRange(); |
| | | } |
| | | if (text.contains("ä»å¹´") || text.contains("æ¬å¹´")) { |
| | | return yearRange(); |
| | | } |
| | | if (text.contains("æ¬å¨")) { |
| | | return weekRange(); |
| | | } |
| | | Matcher relativeDayMatcher = RELATIVE_DAY_PATTERN.matcher(text); |
| | | if (relativeDayMatcher.find()) { |
| | | int days = Integer.parseInt(relativeDayMatcher.group(1)); |
| | | return recentDaysRange(days); |
| | | } |
| | | return new DateRange(null, null, null); |
| | | } |
| | | |
| | | private DateRange buildDateRange(String start, String end, String label) { |
| | | LocalDate startDate = parseDate(start); |
| | | LocalDate endDate = parseDate(end); |
| | | if (startDate == null || endDate == null) { |
| | | return new DateRange(null, null, null); |
| | | } |
| | | if (startDate.isAfter(endDate)) { |
| | | LocalDate temp = startDate; |
| | | startDate = endDate; |
| | | endDate = temp; |
| | | } |
| | | return new DateRange(formatDate(startDate), formatDate(endDate), label); |
| | | } |
| | | |
| | | private DateRange recentDaysRange(int days) { |
| | | LocalDate end = LocalDate.now(); |
| | | int safeDays = Math.max(days, 1); |
| | | LocalDate start = end.minusDays(safeDays - 1L); |
| | | return new DateRange(formatDate(start), formatDate(end), "è¿" + safeDays + "天"); |
| | | } |
| | | |
| | | private DateRange monthRange() { |
| | | LocalDate today = LocalDate.now(); |
| | | return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today), "æ¬æ"); |
| | | } |
| | | |
| | | private DateRange weekRange() { |
| | | LocalDate today = LocalDate.now(); |
| | | LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(formatDate(start), formatDate(today), "æ¬å¨"); |
| | | } |
| | | |
| | | private DateRange lastMonthRange() { |
| | | YearMonth lastMonth = YearMonth.now().minusMonths(1); |
| | | return new DateRange(formatDate(lastMonth.atDay(1)), formatDate(lastMonth.atEndOfMonth()), "䏿"); |
| | | } |
| | | |
| | | private DateRange yearRange() { |
| | | LocalDate today = LocalDate.now(); |
| | | return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today), "ä»å¹´"); |
| | | } |
| | | |
| | | private LocalDate parseDate(String text) { |
| | | try { |
| | | return LocalDate.parse(text, DATE_FMT); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? null : date.format(DATE_FMT); |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace("ï¼", "") |
| | | .replace("ï¼", "") |
| | | .replace("(", "") |
| | | .replace(")", "") |
| | | .replace("ï¼", "") |
| | | .replace(",", "") |
| | | .replace("ã", "") |
| | | .replace(".", "") |
| | | .replace("ï¼", "") |
| | | .replace("!", "") |
| | | .replace("ï¼", "") |
| | | .replace("?", "") |
| | | .replace("ï¼", "") |
| | | .replace(":", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private String extractKeyword(String text) { |
| | | String cleaned = text |
| | | .replaceAll("\\d{4}-\\d{2}-\\d{2}", "") |
| | | .replaceAll("(?:è¿|æè¿)\\s*\\d{1,3}\\s*天", "") |
| | | .replaceAll("(?:å|æè¿|å±ç¤º|è¿å)?\\s*\\d{1,2}\\s*(?:æ¡|个|å)", "") |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("çä¸", "") |
| | | .replace("çç", "") |
| | | .replace("帮æ", "") |
| | | .replace("请", "") |
| | | .replace("ä¸ä¸ª", "") |
| | | .replace("为ä»ä¹", "") |
| | | .replace("åªä¸ªå®¢æ·æèµé±", "") |
| | | .replace("æè¿åªä¸ªå®¢æ·æèµé±", "") |
| | | .replace("æ¬æåªä¸ªå®¢æ·æèµé±", "") |
| | | .replace("è¿30天åªä¸ªå®¢æ·æèµé±", "") |
| | | .replace("æèµé±å®¢æ·", "") |
| | | .replace("å®¢æ·æèµé±", "") |
| | | .replace("åªä¸ªå®¢æ·å©æ¶¦æé«", "") |
| | | .replace("婿¶¦æé«å®¢æ·", "") |
| | | .replace("åªä¸ªå®¢æ·å©æ¶¦è´¡ç®æé«", "") |
| | | .replace("婿¶¦è´¡ç®æé«å®¢æ·", "") |
| | | .replace("æ¬æ", "") |
| | | .replace("æ¬å¨", "") |
| | | .replace("æ¬å¹´", "") |
| | | .replace("ä»å¹´", "") |
| | | .replace("䏿", "") |
| | | .replace("è¿30天", "") |
| | | .replace("è¿7天", "") |
| | | .replace("è¿90天", "") |
| | | .replace("å10æ¡", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .replace("å20æ¡", "") |
| | | .replace("æè¿20æ¡", "") |
| | | .replace("订å婿¶¦åæ", "") |
| | | .replace("婿¶¦åæ", "") |
| | | .replace("åºåèµéåæ", "") |
| | | .replace("ç°éæµé¢æµ", "") |
| | | .replace("ç»è¥é©¾é©¶è±", "") |
| | | .replace("æ¥æ¥", "") |
| | | .replace("卿¥", "") |
| | | .replace("å¼å¸¸é¢è¦", "") |
| | | .replace("æ¡", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private record DateRange(String startDate, String endDate, String label) { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import dev.langchain4j.service.MemoryId; |
| | | import dev.langchain4j.service.SystemMessage; |
| | | import dev.langchain4j.service.UserMessage; |
| | | import dev.langchain4j.service.spring.AiService; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; |
| | | |
| | | /** |
| | | * ç¥è¯åºé®çAgent |
| | | * åºäºRAGæ£ç´¢å¢å¼ºçæ |
| | | */ |
| | | @AiService( |
| | | wiringMode = EXPLICIT, |
| | | streamingChatModel = "qwenStreamingChatModel", |
| | | chatMemoryProvider = "chatMemoryProviderXiaozhi" |
| | | ) |
| | | public interface KnowledgeChatAgent { |
| | | |
| | | @SystemMessage(""" |
| | | ä½ æ¯ä¼ä¸ç¥è¯åºé®ç婿ã |
| | | |
| | | ä½ éè¦åºäºæä¾çç¥è¯åºå
容åçç¨æ·é®é¢ã |
| | | |
| | | éµå¾ªä»¥ä¸è§åï¼ |
| | | 1. ä¸¥æ ¼åºäºç¥è¯åºå
容åçï¼ä¸è¦ç¼é ä¿¡æ¯ |
| | | 2. 妿ç¥è¯åºä¸æ²¡æç¸å
³ä¿¡æ¯ï¼æç¡®åç¥ç¨æ· |
| | | 3. åçè¦åç¡®ãç®æ´ãææ¡ç |
| | | 4. 妿å
容è¾å¤ï¼ä½¿ç¨åç¹åè¡¨å½¢å¼ |
| | | 5. å¼ç¨æ¥æºæ¶æ³¨æ"æ ¹æ®ç¥è¯åºå
容" |
| | | """) |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import dev.langchain4j.service.MemoryId; |
| | | import dev.langchain4j.service.SystemMessage; |
| | | import dev.langchain4j.service.UserMessage; |
| | | import dev.langchain4j.service.V; |
| | | import dev.langchain4j.service.spring.AiService; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; |
| | | |
| | | @AiService( |
| | | wiringMode = EXPLICIT, |
| | | streamingChatModel = "qwenStreamingChatModel", |
| | | chatMemoryProvider = "chatMemoryProviderManufacturing", |
| | | tools = "manufacturingAgentTools" |
| | | ) |
| | | public interface ManufacturingAgent { |
| | | |
| | | @SystemMessage(fromResource = "manufacturing-agent-prompt.txt") |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import com.ruoyi.ai.tools.ManufacturingAgentTools; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | | @Component |
| | | public class ManufacturingIntentExecutor { |
| | | |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?(\\d{1,2})æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | |
| | | private final ManufacturingAgentTools manufacturingAgentTools; |
| | | |
| | | public ManufacturingIntentExecutor(ManufacturingAgentTools manufacturingAgentTools) { |
| | | this.manufacturingAgentTools = manufacturingAgentTools; |
| | | } |
| | | |
| | | public String tryExecute(String memoryId, String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return null; |
| | | } |
| | | String text = message.trim(); |
| | | String keyword = extractKeyword(text); |
| | | Integer limit = extractLimit(text); |
| | | String startDate = extractStartDate(text); |
| | | String endDate = extractEndDate(text); |
| | | |
| | | if (containsAny(text, "é¢è¦", "åè¦", "é£é©", "æé")) { |
| | | return manufacturingAgentTools.getWarningBoard(memoryId, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "åæ", "ç»è®¡", "è¶å¿", "çæ¿", "æ¥è¡¨", "æ»è§")) { |
| | | return manufacturingAgentTools.analyzeFactory(memoryId, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "å", "å¤ç", "派工", "宿", "éç¯", "è·è¿", "å¤ç½®")) { |
| | | return manufacturingAgentTools.planActions(memoryId, text); |
| | | } |
| | | |
| | | if (containsAny(text, "ç产ç°åº", "ç°åº", "车é´")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "site", keyword, limit, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "计å", "æäº§", "mps")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "plan", keyword, limit, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "å·¥å", "ä½ä¸å", "ä»»å¡å", "ä»»å¡")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "workorder", keyword, limit, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "设å¤", "ç»´ä¿®", "ä¿å
»", "æ
é")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "device", keyword, limit, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "è´¨é", "è´¨æ£", "ä¸åæ ¼", "æ£éª")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "quality", keyword, limit, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "ç©æ", "åºå", "åºä½", "å
¥åº", "åºåº")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "material", keyword, limit, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "å¼å¸¸", "ä¾å¤", "åå·®")) { |
| | | return manufacturingAgentTools.queryDomain(memoryId, "exception", keyword, limit, startDate, endDate, text); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.toLowerCase().contains(keyword.toLowerCase())) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private Integer extractLimit(String text) { |
| | | Matcher matcher = LIMIT_PATTERN.matcher(text); |
| | | return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10; |
| | | } |
| | | |
| | | private String extractStartDate(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | return matcher.find() ? matcher.group(1) : null; |
| | | } |
| | | |
| | | private String extractEndDate(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | if (!matcher.find()) { |
| | | return null; |
| | | } |
| | | return matcher.find() ? matcher.group(1) : null; |
| | | } |
| | | |
| | | private String extractKeyword(String text) { |
| | | String cleaned = text |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("帮æ", "") |
| | | .replace("请", "") |
| | | .replace("ä¸ä¸", "") |
| | | .replace("ææ", "") |
| | | .replace("å
¨é¨", "") |
| | | .replace("ä»å¹´", "") |
| | | .replace("æ¬å¹´", "") |
| | | .replace("å»å¹´", "") |
| | | .replace("æ¬æ", "") |
| | | .replace("䏿", "") |
| | | .replace("æ¬å¨", "") |
| | | .replace("ä¸å¨", "") |
| | | .replace("ä»å¤©", "") |
| | | .replace("æ¨å¤©", "") |
| | | .replace("è¿30天", "") |
| | | .replace("è¿7天", "") |
| | | .replace("è¿15天", "") |
| | | .replace("è¿60天", "") |
| | | .replace("æè¿30天", "") |
| | | .replace("æè¿7天", "") |
| | | .replace("æè¿15天", "") |
| | | .replace("æè¿60天", "") |
| | | .replace("ç产ç°åº", "") |
| | | .replace("ç°åº", "") |
| | | .replace("ç产工å", "") |
| | | .replace("ç产", "") |
| | | .replace("计å", "") |
| | | .replace("æäº§", "") |
| | | .replace("å·¥å", "") |
| | | .replace("设å¤", "") |
| | | .replace("è´¨é", "") |
| | | .replace("ç©æ", "") |
| | | .replace("åºå", "") |
| | | .replace("å¼å¸¸", "") |
| | | .replace("å10æ¡", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | } |
| | |
| | | import dev.langchain4j.service.MemoryId; |
| | | import dev.langchain4j.service.SystemMessage; |
| | | import dev.langchain4j.service.UserMessage; |
| | | import dev.langchain4j.service.V; |
| | | import dev.langchain4j.service.spring.AiService; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | |
| | | public interface PurchaseAgent { |
| | | |
| | | @SystemMessage(fromResource = "purchase-agent-prompt.txt") |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage); |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate); |
| | | } |
| | |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | |
| | | public class PurchaseIntentExecutor { |
| | | |
| | | private static final Pattern ID_PATTERN = Pattern.compile("\\b\\d{1,12}\\b"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?(\\d{1,2})æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?\\s*(\\d{1,2})\\s*æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final Pattern RELATIVE_RANGE_PATTERN = Pattern.compile("(è¿|æè¿)\\s*(\\d{1,3})\\s*(天|å¨|个æ|æ|å¹´)"); |
| | | private static final Pattern HALF_RANGE_PATTERN = Pattern.compile("(æè¿|è¿)?å(个)?(æ|å¹´)"); |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final PurchaseAgentTools purchaseAgentTools; |
| | | |
| | |
| | | return null; |
| | | } |
| | | String text = message.trim(); |
| | | String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); |
| | | if (StringUtils.hasText(quickPromptResponse)) { |
| | | return quickPromptResponse; |
| | | } |
| | | |
| | | if (containsAny(text, "æè¡", "æå", "åå ", "åäº", "åå") && containsAny(text, "ç©æ", "产å", "åææ", "éè´éé¢", "éé¢")) { |
| | | return purchaseAgentTools.rankPurchaseMaterials( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | text, |
| | | extractLimit(text) |
| | | ); |
| | | String keyword = extractKeyword(text); |
| | | Integer limit = extractLimit(text); |
| | | DateRange dateRange = extractDateRange(text); |
| | | String startDate = dateRange.startDate(); |
| | | String endDate = dateRange.endDate(); |
| | | |
| | | if (containsAny(text, "æè¡", "æå", "åå ", "åäº", "åå") |
| | | && containsAny(text, "ç©æ", "产å", "åææ", "éè´éé¢", "éé¢")) { |
| | | return purchaseAgentTools.rankPurchaseMaterials(memoryId, startDate, endDate, text, limit); |
| | | } |
| | | if (containsAny(text, "æªå
¥åº", "å¾
å
¥åº", "没æå
¥åº", "è¿æªå
¥åº")) { |
| | | return purchaseAgentTools.listUnstockedPurchaseOrders( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | extractKeyword(text), |
| | | extractLimit(text) |
| | | ); |
| | | return purchaseAgentTools.listUnstockedPurchaseOrders(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (containsAny(text, "å°è´§å¼å¸¸", "å°è´§æå¼å¸¸", "å¼å¸¸å°è´§", "å°è´§é®é¢", "ä¾åºåå°è´§å¼å¸¸")) { |
| | | return purchaseAgentTools.listArrivalExceptions( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | text, |
| | | extractLimit(text) |
| | | ); |
| | | return purchaseAgentTools.listArrivalExceptions(memoryId, startDate, endDate, text, limit); |
| | | } |
| | | if (containsAny(text, "å¾
仿¬¾", "æªä»æ¬¾", "æªä»æ¸
", "å¾
æ¯ä»", "åºä»")) { |
| | | return purchaseAgentTools.listPendingPaymentOrders( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | extractKeyword(text), |
| | | extractLimit(text) |
| | | ); |
| | | return purchaseAgentTools.listPendingPaymentOrders(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (containsAny(text, "éè´§", "éæ", "ææ¶")) { |
| | | return purchaseAgentTools.listPurchaseReturns( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | extractKeyword(text), |
| | | extractLimit(text) |
| | | ); |
| | | return purchaseAgentTools.listPurchaseReturns(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (isStatsIntent(text)) { |
| | | return purchaseAgentTools.getPurchaseStats( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | text |
| | | ); |
| | | return purchaseAgentTools.getPurchaseStats(memoryId, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "详æ
", "æç»") && extractId(text) != null) { |
| | | return purchaseAgentTools.getPurchaseLedgerDetail(memoryId, extractId(text)); |
| | | |
| | | Long ledgerId = extractId(text); |
| | | if (containsAny(text, "详æ
", "æç»") && ledgerId != null) { |
| | | return purchaseAgentTools.getPurchaseLedgerDetail(memoryId, ledgerId); |
| | | } |
| | | if (containsAny(text, "å°è´¦", "éè´å", "éè´è®¢å", "订å", "åå", "å表", "æ¥è¯¢")) { |
| | | return purchaseAgentTools.listPurchaseLedgers( |
| | | memoryId, |
| | | extractKeyword(text), |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | extractLimit(text) |
| | | ); |
| | | return purchaseAgentTools.listPurchaseLedgers(memoryId, keyword, startDate, endDate, limit); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String tryExecuteQuickPrompt(String memoryId, String text) { |
| | | String normalized = normalizeForMatch(text); |
| | | if ("æ¬æéè´é颿åååçç©ææåªäº".equals(normalized)) { |
| | | return purchaseAgentTools.rankPurchaseMaterials(memoryId, null, null, "æ¬æ", 10); |
| | | } |
| | | if ("åªäºéè´è®¢åè¿æªå
¥åº".equals(normalized)) { |
| | | return purchaseAgentTools.listUnstockedPurchaseOrders(memoryId, null, null, null, 10); |
| | | } |
| | | if ("æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäº".equals(normalized)) { |
| | | return purchaseAgentTools.listArrivalExceptions(memoryId, null, null, "æè¿7天", 10); |
| | | } |
| | | if ("帮æç»è®¡å¾
仿¬¾éè´å".equals(normalized)) { |
| | | return purchaseAgentTools.listPendingPaymentOrders(memoryId, null, null, null, 10); |
| | | } |
| | | if ("ååºæ¬æéè´éè´§æ
åµ".equals(normalized)) { |
| | | return purchaseAgentTools.listPurchaseReturns(memoryId, null, null, null, 10); |
| | | } |
| | | return null; |
| | | } |
| | |
| | | } |
| | | boolean queryWord = containsAny(text, "æ¥è¯¢", "æ¥ç", "çä¸", "çç", "è·å"); |
| | | boolean dataWord = containsAny(text, "æ°æ®", "éé¢", "æ°é", "ååé¢", "仿¬¾é¢", "å票é¢"); |
| | | boolean timeWord = containsAny(text, "ä»å¤©", "æ¬å¨", "æ¬æ", "䏿", "ä»å¹´", "å»å¹´", "è¿åå¹´", "æè¿å个æ", "å个æ") |
| | | || DATE_PATTERN.matcher(text).find(); |
| | | boolean timeWord = containsAny(text, "ä»å¤©", "æ¨å¤©", "æ¬å¨", "ä¸å¨", "æ¬æ", "䏿", "ä»å¹´", "å»å¹´", "è¿åå¹´", "æè¿å个æ", "å个æ") |
| | | || DATE_PATTERN.matcher(text).find() |
| | | || RELATIVE_RANGE_PATTERN.matcher(text).find() |
| | | || HALF_RANGE_PATTERN.matcher(text).find(); |
| | | return queryWord && dataWord && timeWord; |
| | | } |
| | | |
| | |
| | | return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10; |
| | | } |
| | | |
| | | private String extractStartDate(String text) { |
| | | private DateRange extractDateRange(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | return matcher.find() ? matcher.group() : null; |
| | | if (matcher.find()) { |
| | | String first = matcher.group(1); |
| | | String second = matcher.find() ? matcher.group(1) : first; |
| | | return buildDateRange(first, second); |
| | | } |
| | | |
| | | LocalDate today = LocalDate.now(CHINA_ZONE_ID); |
| | | if (text.contains("ä»å¤©")) { |
| | | return new DateRange(formatDate(today), formatDate(today)); |
| | | } |
| | | if (text.contains("æ¨å¤©")) { |
| | | LocalDate yesterday = today.minusDays(1); |
| | | return new DateRange(formatDate(yesterday), formatDate(yesterday)); |
| | | } |
| | | if (text.contains("æ¬å¨")) { |
| | | LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(formatDate(start), formatDate(today)); |
| | | } |
| | | if (text.contains("ä¸å¨")) { |
| | | LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | LocalDate start = thisWeekStart.minusWeeks(1); |
| | | LocalDate end = start.plusDays(6); |
| | | return new DateRange(formatDate(start), formatDate(end)); |
| | | } |
| | | if (text.contains("æ¬æ")) { |
| | | return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today)); |
| | | } |
| | | if (text.contains("䏿")) { |
| | | LocalDate start = today.minusMonths(1).withDayOfMonth(1); |
| | | return new DateRange(formatDate(start), formatDate(start.withDayOfMonth(start.lengthOfMonth()))); |
| | | } |
| | | if (text.contains("ä»å¹´") || text.contains("æ¬å¹´")) { |
| | | return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today)); |
| | | } |
| | | if (text.contains("å»å¹´")) { |
| | | LocalDate start = today.minusYears(1).withDayOfYear(1); |
| | | LocalDate end = start.withDayOfYear(start.lengthOfYear()); |
| | | return new DateRange(formatDate(start), formatDate(end)); |
| | | } |
| | | if (containsAny(text, "è¿åå¹´", "æè¿åå¹´")) { |
| | | return new DateRange(formatDate(today.minusMonths(6).plusDays(1)), formatDate(today)); |
| | | } |
| | | if (containsAny(text, "è¿å个æ", "æè¿å个æ", "å个æ")) { |
| | | return new DateRange(formatDate(today.minusDays(14)), formatDate(today)); |
| | | } |
| | | |
| | | Matcher relativeMatcher = RELATIVE_RANGE_PATTERN.matcher(text); |
| | | if (relativeMatcher.find()) { |
| | | int amount = Integer.parseInt(relativeMatcher.group(2)); |
| | | String unit = relativeMatcher.group(3); |
| | | LocalDate start = switch (unit) { |
| | | case "天" -> today.minusDays(Math.max(amount - 1L, 0)); |
| | | case "å¨" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1); |
| | | case "个æ", "æ" -> today.minusMonths(Math.max(amount, 1)).plusDays(1); |
| | | case "å¹´" -> today.minusYears(Math.max(amount, 1)).plusDays(1); |
| | | default -> today.minusDays(29); |
| | | }; |
| | | return new DateRange(formatDate(start), formatDate(today)); |
| | | } |
| | | |
| | | return new DateRange(null, null); |
| | | } |
| | | |
| | | private String extractEndDate(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | if (!matcher.find()) { |
| | | private DateRange buildDateRange(String start, String end) { |
| | | LocalDate startDate = parseDate(start); |
| | | LocalDate endDate = parseDate(end); |
| | | if (startDate == null || endDate == null) { |
| | | return new DateRange(null, null); |
| | | } |
| | | if (startDate.isAfter(endDate)) { |
| | | LocalDate temp = startDate; |
| | | startDate = endDate; |
| | | endDate = temp; |
| | | } |
| | | return new DateRange(formatDate(startDate), formatDate(endDate)); |
| | | } |
| | | |
| | | private LocalDate parseDate(String text) { |
| | | try { |
| | | return LocalDate.parse(text, DATE_FMT); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | return matcher.find() ? matcher.group() : null; |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? null : date.format(DATE_FMT); |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace("ï¼", "") |
| | | .replace(",", "") |
| | | .replace("ã", "") |
| | | .replace(".", "") |
| | | .replace("ï¼", "") |
| | | .replace("!", "") |
| | | .replace("ï¼", "") |
| | | .replace("?", "") |
| | | .replace("ï¼", "") |
| | | .replace(":", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private String extractKeyword(String text) { |
| | | String cleaned = text |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("çä¸", "") |
| | | .replace("çç", "") |
| | | .replace("请", "") |
| | | .replace("ä¸ä¸", "") |
| | | .replace("éè´", "") |
| | | .replace("éè´å", "") |
| | | .replace("éè´è®¢å", "") |
| | |
| | | .replace("åªäº", "") |
| | | .replace("ååº", "") |
| | | .replace("帮æ", "") |
| | | .replace("ç»è®¡", "") |
| | | .replace("åæ", "") |
| | | .replace("æ¬æ", "") |
| | | .replace("䏿", "") |
| | | .replace("æ¬å¹´", "") |
| | | .replace("ä»å¹´", "") |
| | | .replace("å»å¹´", "") |
| | | .replace("æ¬å¨", "") |
| | | .replace("ä¸å¨", "") |
| | | .replace("ä»å¤©", "") |
| | | .replace("æ¨å¤©", "") |
| | | .replace("è¿30天", "") |
| | | .replace("è¿7天", "") |
| | | .replace("è¿15天", "") |
| | | .replace("è¿60天", "") |
| | | .replace("æè¿30天", "") |
| | | .replace("æè¿7天", "") |
| | | .replace("æè¿15天", "") |
| | | .replace("æè¿60天", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .replace("å10æ¡", "") |
| | | .replace("å20æ¡", "") |
| | | .replace("æè¿20æ¡", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private record DateRange(String startDate, String endDate) { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import dev.langchain4j.service.MemoryId; |
| | | import dev.langchain4j.service.SystemMessage; |
| | | import dev.langchain4j.service.UserMessage; |
| | | import dev.langchain4j.service.V; |
| | | import dev.langchain4j.service.spring.AiService; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; |
| | | |
| | | @AiService( |
| | | wiringMode = EXPLICIT, |
| | | streamingChatModel = "qwenStreamingChatModel", |
| | | chatMemoryProvider = "chatMemoryProviderSales", |
| | | tools = "salesAgentTools" |
| | | ) |
| | | public interface SalesAgent { |
| | | |
| | | @SystemMessage(fromResource = "sales-agent-prompt.txt") |
| | | Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.assistant; |
| | | |
| | | import com.ruoyi.ai.tools.SalesAgentTools; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | | @Component |
| | | public class SalesIntentExecutor { |
| | | |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?\\s*(\\d{1,2})\\s*æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final Pattern RELATIVE_DAY_PATTERN = Pattern.compile("(è¿|æè¿)?\\s*(\\d{1,3})\\s*天"); |
| | | |
| | | private final SalesAgentTools salesAgentTools; |
| | | |
| | | public SalesIntentExecutor(SalesAgentTools salesAgentTools) { |
| | | this.salesAgentTools = salesAgentTools; |
| | | } |
| | | |
| | | public String tryExecute(String memoryId, String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return null; |
| | | } |
| | | String text = message.trim(); |
| | | |
| | | String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); |
| | | if (StringUtils.hasText(quickPromptResponse)) { |
| | | return quickPromptResponse; |
| | | } |
| | | |
| | | String keyword = extractKeyword(text); |
| | | Integer limit = extractLimit(text); |
| | | DateRange dateRange = extractDateRange(text); |
| | | String startDate = dateRange.startDate(); |
| | | String endDate = dateRange.endDate(); |
| | | |
| | | if (containsAny(text, "æµå¤±", "æµå¤±é£é©", "å®¢æ·æµå¤±", "é£é©åæ")) { |
| | | return salesAgentTools.analyzeCustomerChurnRisk(memoryId, startDate, endDate, text, keyword, limit); |
| | | } |
| | | if (containsAny(text, "忬¾", "æ¶æ¬¾", "æ¥ä»·") |
| | | && containsAny(text, "建议", "çç¥", "ä¼å", "æ¹æ¡")) { |
| | | return salesAgentTools.suggestCollectionAndQuotationStrategy( |
| | | memoryId, startDate, endDate, text, keyword, limit, shouldPrioritizeHighRisk(text)); |
| | | } |
| | | if (containsAny(text, "ææ ", "ç»è®¡", "çæ¿", "æ»è§", "ç»è¥åæ")) { |
| | | return salesAgentTools.getSalesDashboard(memoryId, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "å®¢æ·æ¡£æ¡", "ç§æµ·", "å
¬æµ·", "å®¢æ·æ± ")) { |
| | | return salesAgentTools.listCustomerProfiles(memoryId, extractSeaType(text), keyword, limit); |
| | | } |
| | | if (containsAny(text, "é宿¥ä»·", "æ¥ä»·å", "æ¥ä»·", "询价")) { |
| | | return salesAgentTools.listSalesQuotations(memoryId, keyword, startDate, endDate, limit); |
| | | } |
| | | if (containsAny(text, "éå®éè´§", "éè´§", "鿬¾")) { |
| | | return salesAgentTools.listSalesReturns(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (containsAny(text, "客æ·å¾æ¥", "徿¥", "忬¾", "åºæ¶", "æ¥æ¬¾", "æ¶æ¬¾æç»")) { |
| | | return salesAgentTools.listCustomerInteractions(memoryId, keyword, startDate, endDate, limit); |
| | | } |
| | | if (containsAny(text, "åè´§å°è´¦", "åè´§", "ç©æµ", "å¿«é", "è¿è¾")) { |
| | | return salesAgentTools.listShippingLedgers(memoryId, keyword, startDate, endDate, limit); |
| | | } |
| | | if (containsAny(text, "éå®å°è´¦", "éå®åå", "éå®è®¢å", "ååå°è´¦", "订åå°è´¦")) { |
| | | return salesAgentTools.listSalesLedgers(memoryId, keyword, startDate, endDate, limit); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String tryExecuteQuickPrompt(String memoryId, String text) { |
| | | String normalized = normalizeForMatch(text); |
| | | if ("æ¥è¯¢ç§æµ·å®¢æ·æ¡£æ¡å10æ¡".equals(normalized)) { |
| | | return salesAgentTools.listCustomerProfiles(memoryId, "private", null, 10); |
| | | } |
| | | if ("æ¥è¯¢å
¬æµ·å®¢æ·æ¡£æ¡".equals(normalized)) { |
| | | return salesAgentTools.listCustomerProfiles(memoryId, "public", null, 10); |
| | | } |
| | | if ("æ¥è¯¢æ¬æé宿¥ä»·".equals(normalized)) { |
| | | DateRange range = monthRange(); |
| | | return salesAgentTools.listSalesQuotations(memoryId, null, range.startDate(), range.endDate(), 10); |
| | | } |
| | | if ("æ¥è¯¢æ¬æéå®å°è´¦".equals(normalized)) { |
| | | DateRange range = monthRange(); |
| | | return salesAgentTools.listSalesLedgers(memoryId, null, range.startDate(), range.endDate(), 10); |
| | | } |
| | | if ("æ¥è¯¢è¿30天éå®éè´§".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return salesAgentTools.listSalesReturns(memoryId, range.startDate(), range.endDate(), null, 10); |
| | | } |
| | | if ("æ¥è¯¢è¿30天客æ·åæ¬¾å¾æ¥".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return salesAgentTools.listCustomerInteractions(memoryId, null, range.startDate(), range.endDate(), 10); |
| | | } |
| | | if ("æ¥è¯¢æ¬æåè´§å°è´¦".equals(normalized)) { |
| | | DateRange range = monthRange(); |
| | | return salesAgentTools.listShippingLedgers(memoryId, null, range.startDate(), range.endDate(), 10); |
| | | } |
| | | if ("æ¥çé宿æ ç»è®¡".equals(normalized)) { |
| | | return salesAgentTools.getSalesDashboard(memoryId, null, null, "æ¬æ"); |
| | | } |
| | | if ("帮æåå®¢æ·æµå¤±é£é©åæè¿30天å20æ¡".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return salesAgentTools.analyzeCustomerChurnRisk(memoryId, range.startDate(), range.endDate(), "è¿30天", null, 20); |
| | | } |
| | | if ("çæåæ¬¾ä¸æ¥ä»·çç¥å»ºè®®ä¼å
é«é£é©å®¢æ·".equals(normalized)) { |
| | | DateRange range = recentDaysRange(30); |
| | | return salesAgentTools.suggestCollectionAndQuotationStrategy(memoryId, range.startDate(), range.endDate(), "è¿30天", null, 10, true); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String extractSeaType(String text) { |
| | | if (text.contains("å
¬æµ·")) { |
| | | return "public"; |
| | | } |
| | | if (text.contains("ç§æµ·")) { |
| | | return "private"; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer extractLimit(String text) { |
| | | Matcher matcher = LIMIT_PATTERN.matcher(text); |
| | | return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10; |
| | | } |
| | | |
| | | private DateRange extractDateRange(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | if (matcher.find()) { |
| | | String first = matcher.group(1); |
| | | String second = matcher.find() ? matcher.group(1) : first; |
| | | return buildDateRange(first, second); |
| | | } |
| | | if (text.contains("æ¬æ")) { |
| | | return monthRange(); |
| | | } |
| | | if (text.contains("䏿")) { |
| | | return lastMonthRange(); |
| | | } |
| | | if (text.contains("æ¬å¹´") || text.contains("ä»å¹´")) { |
| | | return yearRange(); |
| | | } |
| | | Matcher relativeDayMatcher = RELATIVE_DAY_PATTERN.matcher(text); |
| | | if (relativeDayMatcher.find()) { |
| | | int days = Integer.parseInt(relativeDayMatcher.group(2)); |
| | | return recentDaysRange(days); |
| | | } |
| | | return new DateRange(null, null); |
| | | } |
| | | |
| | | private DateRange buildDateRange(String start, String end) { |
| | | LocalDate startDate = parseDate(start); |
| | | LocalDate endDate = parseDate(end); |
| | | if (startDate == null || endDate == null) { |
| | | return new DateRange(null, null); |
| | | } |
| | | if (startDate.isAfter(endDate)) { |
| | | LocalDate temp = startDate; |
| | | startDate = endDate; |
| | | endDate = temp; |
| | | } |
| | | return new DateRange(formatDate(startDate), formatDate(endDate)); |
| | | } |
| | | |
| | | private DateRange recentDaysRange(int days) { |
| | | LocalDate end = LocalDate.now(); |
| | | int safeDays = Math.max(days, 1); |
| | | LocalDate start = end.minusDays(safeDays - 1L); |
| | | return new DateRange(formatDate(start), formatDate(end)); |
| | | } |
| | | |
| | | private DateRange monthRange() { |
| | | LocalDate today = LocalDate.now(); |
| | | return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today)); |
| | | } |
| | | |
| | | private DateRange lastMonthRange() { |
| | | YearMonth lastMonth = YearMonth.now().minusMonths(1); |
| | | return new DateRange(formatDate(lastMonth.atDay(1)), formatDate(lastMonth.atEndOfMonth())); |
| | | } |
| | | |
| | | private DateRange yearRange() { |
| | | LocalDate today = LocalDate.now(); |
| | | return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today)); |
| | | } |
| | | |
| | | private LocalDate parseDate(String text) { |
| | | try { |
| | | return LocalDate.parse(text, DATE_FMT); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? null : date.format(DATE_FMT); |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace("ï¼", "") |
| | | .replace(",", "") |
| | | .replace("ã", "") |
| | | .replace(".", "") |
| | | .replace("ï¼", "") |
| | | .replace("!", "") |
| | | .replace("ï¼", "") |
| | | .replace("?", "") |
| | | .replace("ï¼", "") |
| | | .replace(":", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private Boolean shouldPrioritizeHighRisk(String text) { |
| | | return containsAny(text, "ä¼å
é«é£é©", "é«é£é©å®¢æ·", "é«é£é©"); |
| | | } |
| | | |
| | | private String extractKeyword(String text) { |
| | | String cleaned = text |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("çä¸", "") |
| | | .replace("çç", "") |
| | | .replace("帮æ", "") |
| | | .replace("请", "") |
| | | .replace("ä¸ä¸", "") |
| | | .replace("éå®", "") |
| | | .replace("å®¢æ·æ¡£æ¡", "") |
| | | .replace("æ¥ä»·å", "") |
| | | .replace("é宿¥ä»·", "") |
| | | .replace("éå®å°è´¦", "") |
| | | .replace("åè´§å°è´¦", "") |
| | | .replace("客æ·å¾æ¥", "") |
| | | .replace("éå®éè´§", "") |
| | | .replace("å10æ¡", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .replace("å20æ¡", "") |
| | | .replace("æè¿20æ¡", "") |
| | | .replace("è¿30天", "") |
| | | .replace("æ¬æ", "") |
| | | .replace("æ¬å¹´", "") |
| | | .replace("ä»å¹´", "") |
| | | .replace("æ¡", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private record DateRange(String startDate, String endDate) { |
| | | } |
| | | } |
| | |
| | | import dev.langchain4j.store.embedding.EmbeddingStore; |
| | | import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore; |
| | | import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import io.pinecone.clients.Index; |
| | | import io.pinecone.clients.Pinecone; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | /** |
| | | * @author :yys |
| | | * @date : 2025/5/2 21:07 |
| | | * åéåå¨é
ç½® |
| | | */ |
| | | @Configuration |
| | | public class EmbeddingStoreConfig { |
| | | |
| | | @Autowired |
| | | private EmbeddingModel embeddingModel; |
| | | @Value("${pinecone.api-key:pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9}") |
| | | private String pineconeApiKey; |
| | | |
| | | @Value("${pinecone.index:xiaozhi-index}") |
| | | private String indexName; |
| | | |
| | | @Value("${pinecone.namespace:knowledge-base}") |
| | | private String namespace; |
| | | |
| | | @Bean |
| | | public EmbeddingStore<TextSegment> embeddingStore() { |
| | | //å建åéåå¨ |
| | | public Pinecone pinecone() { |
| | | return new Pinecone.Builder(pineconeApiKey).build(); |
| | | } |
| | | |
| | | @Bean |
| | | public Index pineconeIndex(Pinecone pinecone) { |
| | | return pinecone.getIndexConnection(indexName); |
| | | } |
| | | |
| | | @Bean |
| | | public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) { |
| | | return PineconeEmbeddingStore.builder() |
| | | .apiKey("pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9") |
| | | .index("xiaozhi-index")//妿æå®çç´¢å¼ä¸åå¨ï¼å°å建ä¸ä¸ªæ°çç´¢å¼ |
| | | .nameSpace("xiaozhi-namespace") //妿æå®çå称空é´ä¸åå¨ï¼å°å建ä¸ä¸ªæ°çåç§° ç©ºé´ |
| | | .apiKey(pineconeApiKey) |
| | | .index(indexName) |
| | | .nameSpace(namespace) |
| | | .createIndex(PineconeServerlessIndexConfig.builder() |
| | | .cloud("AWS") //æå®ç´¢å¼é¨ç½²å¨ AWS äºæå¡ä¸ã |
| | | .region("us-east-1") //æå®ç´¢å¼æå¨ç AWS åºå为 us-east-1ã |
| | | .dimension(embeddingModel.dimension()) //æå®ç´¢å¼çåé维度ï¼è¯¥ç»´åº¦ä¸ embeddedModel çæçåé维度ç¸åã |
| | | .cloud("AWS") |
| | | .region("us-east-1") |
| | | .dimension(embeddingModel.dimension()) |
| | | .build()) |
| | | .build(); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.config; |
| | | |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import dev.langchain4j.memory.chat.ChatMemoryProvider; |
| | | import dev.langchain4j.memory.chat.MessageWindowChatMemory; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | @Configuration |
| | | public class FinancialAgentConfig { |
| | | |
| | | @Bean |
| | | ChatMemoryProvider chatMemoryProviderFinancial(MongoChatMemoryStore mongoChatMemoryStore) { |
| | | return memoryId -> MessageWindowChatMemory.builder() |
| | | .id(memoryId) |
| | | .maxMessages(40) |
| | | .chatMemoryStore(mongoChatMemoryStore) |
| | | .build(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.config; |
| | | |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import dev.langchain4j.memory.chat.ChatMemoryProvider; |
| | | import dev.langchain4j.memory.chat.MessageWindowChatMemory; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | @Configuration |
| | | public class ManufacturingAgentConfig { |
| | | |
| | | @Bean |
| | | ChatMemoryProvider chatMemoryProviderManufacturing(MongoChatMemoryStore mongoChatMemoryStore) { |
| | | return memoryId -> MessageWindowChatMemory.builder() |
| | | .id(memoryId) |
| | | .maxMessages(30) |
| | | .chatMemoryStore(mongoChatMemoryStore) |
| | | .build(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.config; |
| | | |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import dev.langchain4j.memory.chat.ChatMemoryProvider; |
| | | import dev.langchain4j.memory.chat.MessageWindowChatMemory; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | @Configuration |
| | | public class SalesAgentConfig { |
| | | |
| | | @Bean |
| | | ChatMemoryProvider chatMemoryProviderSales(MongoChatMemoryStore mongoChatMemoryStore) { |
| | | return memoryId -> MessageWindowChatMemory.builder() |
| | | .id(memoryId) |
| | | .maxMessages(30) |
| | | .chatMemoryStore(mongoChatMemoryStore) |
| | | .build(); |
| | | } |
| | | } |
| | | |
| | |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | |
| | | public class AiSessionUserContext { |
| | | |
| | | private final Map<String, LoginUser> loginUserByMemoryId = new ConcurrentHashMap<>(); |
| | | private final Map<String, Instant> lastAccessTimeByMemoryId = new ConcurrentHashMap<>(); |
| | | private static final Duration SESSION_TIMEOUT = Duration.ofHours(24); |
| | | |
| | | public void bind(String memoryId, LoginUser loginUser) { |
| | | if (!StringUtils.hasText(memoryId) || loginUser == null) { |
| | | return; |
| | | } |
| | | loginUserByMemoryId.put(memoryId, loginUser); |
| | | lastAccessTimeByMemoryId.put(memoryId, Instant.now()); |
| | | } |
| | | |
| | | public LoginUser get(String memoryId) { |
| | | if (!StringUtils.hasText(memoryId)) { |
| | | return null; |
| | | } |
| | | if (isExpired(memoryId)) { |
| | | remove(memoryId); |
| | | return null; |
| | | } |
| | | lastAccessTimeByMemoryId.put(memoryId, Instant.now()); |
| | | return loginUserByMemoryId.get(memoryId); |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | loginUserByMemoryId.remove(memoryId); |
| | | lastAccessTimeByMemoryId.remove(memoryId); |
| | | } |
| | | |
| | | public void cleanExpiredSessions() { |
| | | Instant now = Instant.now(); |
| | | lastAccessTimeByMemoryId.entrySet().removeIf(entry -> { |
| | | boolean expired = Duration.between(entry.getValue(), now).compareTo(SESSION_TIMEOUT) > 0; |
| | | if (expired) { |
| | | loginUserByMemoryId.remove(entry.getKey()); |
| | | } |
| | | return expired; |
| | | }); |
| | | } |
| | | |
| | | private boolean isExpired(String memoryId) { |
| | | Instant lastAccess = lastAccessTimeByMemoryId.get(memoryId); |
| | | if (lastAccess == null) { |
| | | return true; |
| | | } |
| | | return Duration.between(lastAccess, Instant.now()).compareTo(SESSION_TIMEOUT) > 0; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.ruoyi.ai.assistant.FinancialAgent; |
| | | import com.ruoyi.ai.assistant.FinancialIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.service.AiChatSessionService; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | |
| | | @Tag(name = "è´¢å¡æºè½ä½") |
| | | @RestController |
| | | @RequestMapping("/financial-ai") |
| | | public class FinancialAiController extends BaseController { |
| | | |
| | | private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final FinancialAgent financialAgent; |
| | | private final FinancialIntentExecutor financialIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | |
| | | public FinancialAiController(FinancialAgent financialAgent, |
| | | FinancialIntentExecutor financialIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService) { |
| | | this.financialAgent = financialAgent; |
| | | this.financialIntentExecutor = financialIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | } |
| | | |
| | | @Operation(summary = "è´¢å¡æºè½ä½å¯¹è¯") |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> chat(@RequestBody ChatForm chatForm) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = financialIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | if (isBusinessDataIntent(userMessage)) { |
| | | String noGuessResponse = "æªè¯å«å°å¯æ§è¡çæ°æ®æ¥è¯¢æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é æ°æ®ï¼è¯·è¡¥å
æç¡®æ¶é´èå´ã客æ·ãä¾åºåæåå·ååæ¥è¯¢ã"; |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(noGuessResponse); |
| | | } |
| | | |
| | | return financialAgent.chat(memoryId, userMessage, currentDateForPrompt()) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "è´¢å¡æºè½ä½ä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "è´¢å¡æºè½ä½ä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "å é¤è´¢å¡æºè½ä½ä¼è¯") |
| | | @DeleteMapping("/history/{memoryId}") |
| | | public AjaxResult deleteSession(@PathVariable String memoryId) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private String currentDateForPrompt() { |
| | | return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT); |
| | | } |
| | | |
| | | private boolean isBusinessDataIntent(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return false; |
| | | } |
| | | String text = message.trim(); |
| | | return containsAny(text, |
| | | "æ¥è¯¢", "æ¥ç", "ç»è®¡", "åæ", "建议", "ææ¬æ ¸ç®", "äº§åææ¬", "å·¥åºææ¬", |
| | | "订å婿¶¦", "äºæè®¢å", "ä½å©æ¶¦", "åºåèµé", "åºå积å", "åæ»åºå", |
| | | "ç°éæµ", "忬¾é£é©", "仿¬¾åå", "èµé缺å£", "åºæ¶", "åºä»", |
| | | "å¼å¸¸é¢è¦", "ç»è¥å¼å¸¸", "é£é©é¢è¦", "驾驶è±", "ç»è¥çæ¿", "ç»è¥æ»è§", |
| | | "æ¥æ¥", "卿¥", "ç»è¥æ¥å", "åææ¥å", "ä¸è´¢èå", "å£å¾", "ææ è§£é"); |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.ruoyi.ai.assistant.KnowledgeChatAgent; |
| | | import com.ruoyi.ai.dto.KnowledgeChatRequest; |
| | | import com.ruoyi.ai.service.KnowledgeRagService; |
| | | import com.ruoyi.approve.pojo.KnowledgeBase; |
| | | import com.ruoyi.approve.service.KnowledgeBaseService; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ç¥è¯åºé®çController |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/ai/knowledge") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "ç¥è¯åºé®ç") |
| | | public class KnowledgeChatController { |
| | | |
| | | private final KnowledgeChatAgent knowledgeChatAgent; |
| | | private final KnowledgeRagService knowledgeRagService; |
| | | private final KnowledgeBaseService knowledgeBaseService; |
| | | |
| | | /** |
| | | * ç¥è¯åºé®çï¼æµå¼è¿åï¼ |
| | | */ |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | @Operation(summary = "ç¥è¯åºé®ç") |
| | | public Flux<String> chat(@RequestBody KnowledgeChatRequest request) { |
| | | if (request.getKnowledgeBaseId() == null) { |
| | | return Flux.just("ç¥è¯åºIDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(request.getMemoryId())) { |
| | | return Flux.just("ä¼è¯IDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(request.getQuestion())) { |
| | | return Flux.just("é®é¢ä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | KnowledgeBase knowledgeBase = knowledgeBaseService.getById(request.getKnowledgeBaseId()); |
| | | if (knowledgeBase == null) { |
| | | return Flux.just("ç¥è¯åºä¸åå¨"); |
| | | } |
| | | |
| | | String namespace = "kb-" + request.getKnowledgeBaseId(); |
| | | |
| | | List<String> relevantContents = knowledgeRagService.searchRelevantContent( |
| | | namespace, request.getQuestion(), 5); |
| | | |
| | | if (relevantContents.isEmpty()) { |
| | | return Flux.just("ç¥è¯åºä¸æªæ¾å°ç¸å
³å
容ï¼è¯·å
ä¸ä¼ ç¸å
³ææ¡£ã"); |
| | | } |
| | | |
| | | StringBuilder contextBuilder = new StringBuilder(); |
| | | contextBuilder.append("以䏿¯ä»ç¥è¯åºä¸æ£ç´¢å°çç¸å
³å
容ï¼\n\n"); |
| | | for (int i = 0; i < relevantContents.size(); i++) { |
| | | contextBuilder.append("ãå
容").append(i + 1).append("ã\n"); |
| | | contextBuilder.append(relevantContents.get(i)).append("\n\n"); |
| | | } |
| | | contextBuilder.append("---\n"); |
| | | contextBuilder.append("请åºäºä»¥ä¸ç¥è¯åºå
容åçç¨æ·é®é¢ï¼\n"); |
| | | contextBuilder.append(request.getQuestion()); |
| | | |
| | | return knowledgeChatAgent.chat(request.getMemoryId(), contextBuilder.toString()); |
| | | } |
| | | |
| | | /** |
| | | * ç¥è¯åºå表ï¼ç¨äºéæ©ç¥è¯åºï¼ |
| | | */ |
| | | @GetMapping("/list") |
| | | @Operation(summary = "ç¥è¯åºå表") |
| | | public AjaxResult listKnowledgeBases() { |
| | | List<KnowledgeBase> list = knowledgeBaseService.list(); |
| | | return AjaxResult.success(list); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.ruoyi.ai.assistant.ManufacturingAgent; |
| | | import com.ruoyi.ai.assistant.ManufacturingIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.service.AiChatSessionService; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | |
| | | @Tag(name = "å¶é æºè½å©æ") |
| | | @RestController |
| | | @RequestMapping("/manufacturing-ai") |
| | | public class ManufacturingAiController extends BaseController { |
| | | |
| | | private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final ManufacturingAgent manufacturingAgent; |
| | | private final ManufacturingIntentExecutor manufacturingIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | |
| | | public ManufacturingAiController(ManufacturingAgent manufacturingAgent, |
| | | ManufacturingIntentExecutor manufacturingIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService) { |
| | | this.manufacturingAgent = manufacturingAgent; |
| | | this.manufacturingIntentExecutor = manufacturingIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | } |
| | | |
| | | @Operation(summary = "å¶é 对è¯") |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> chat(@RequestBody ChatForm chatForm) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = manufacturingIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | return manufacturingAgent.chat(memoryId, userMessage, currentDateForPrompt()) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "å¶é ä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "å¶é ä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "å é¤å¶é ä¼è¯") |
| | | @DeleteMapping("/history/{memoryId}") |
| | | public AjaxResult deleteSession(@PathVariable String memoryId) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private String currentDateForPrompt() { |
| | | return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT); |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.ruoyi.ai.assistant.PurchaseAgent; |
| | | import com.ruoyi.ai.assistant.PurchaseIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.bean.PurchaseAiConfirmRequest; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.service.AiChatSessionService; |
| | | import com.ruoyi.ai.service.AiFileTextExtractor; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.basic.mapper.SupplierManageMapper; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | import com.ruoyi.ai.service.PurchaseAiService; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.purchase.dto.PurchaseLedgerDto; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderDto; |
| | | import com.ruoyi.purchase.pojo.PaymentRegistration; |
| | | import com.ruoyi.purchase.service.IPaymentRegistrationService; |
| | | import com.ruoyi.purchase.service.IPurchaseLedgerService; |
| | | import com.ruoyi.purchase.service.PurchaseReturnOrdersService; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import dev.langchain4j.data.image.Image; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.ChatMessage; |
| | | import dev.langchain4j.data.message.Content; |
| | | import dev.langchain4j.data.message.ImageContent; |
| | | import dev.langchain4j.data.message.SystemMessage; |
| | | import dev.langchain4j.data.message.TextContent; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import dev.langchain4j.model.chat.StreamingChatLanguageModel; |
| | | import dev.langchain4j.model.chat.response.ChatResponse; |
| | | import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | 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 org.springframework.web.multipart.MultipartFile; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.Base64; |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | | |
| | | @Tag(name = "éè´æºè½ä½") |
| | | @RestController |
| | | @RequestMapping("/purchase-ai") |
| | | public class PurchaseAiController extends BaseController { |
| | | |
| | | private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::"; |
| | | private static final int MAX_FILE_COUNT = 10; |
| | | private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000; |
| | | private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000; |
| | | private final PurchaseAiService purchaseAiService; |
| | | |
| | | private final PurchaseAgent purchaseAgent; |
| | | private final PurchaseIntentExecutor purchaseIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | private final AiFileTextExtractor aiFileTextExtractor; |
| | | private final ObjectMapper objectMapper; |
| | | private final IPurchaseLedgerService purchaseLedgerService; |
| | | private final IPaymentRegistrationService paymentRegistrationService; |
| | | private final PurchaseReturnOrdersService purchaseReturnOrdersService; |
| | | private final SupplierManageMapper supplierManageMapper; |
| | | private final StreamingChatLanguageModel purchaseVisionStreamingChatModel; |
| | | |
| | | public PurchaseAiController(PurchaseAgent purchaseAgent, |
| | | PurchaseIntentExecutor purchaseIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService, |
| | | AiFileTextExtractor aiFileTextExtractor, |
| | | ObjectMapper objectMapper, |
| | | IPurchaseLedgerService purchaseLedgerService, |
| | | IPaymentRegistrationService paymentRegistrationService, |
| | | PurchaseReturnOrdersService purchaseReturnOrdersService, |
| | | SupplierManageMapper supplierManageMapper, |
| | | @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) { |
| | | this.purchaseAgent = purchaseAgent; |
| | | this.purchaseIntentExecutor = purchaseIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | this.aiFileTextExtractor = aiFileTextExtractor; |
| | | this.objectMapper = objectMapper; |
| | | this.purchaseLedgerService = purchaseLedgerService; |
| | | this.paymentRegistrationService = paymentRegistrationService; |
| | | this.purchaseReturnOrdersService = purchaseReturnOrdersService; |
| | | this.supplierManageMapper = supplierManageMapper; |
| | | this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel; |
| | | public PurchaseAiController(PurchaseAiService purchaseAiService) { |
| | | this.purchaseAiService = purchaseAiService; |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¯¹è¯") |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> chat(@RequestBody ChatForm chatForm) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = purchaseIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | return purchaseAgent.chat(memoryId, userMessage) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | return purchaseAiService.chat(chatForm, loginUser); |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¤æä»¶åæ") |
| | |
| | | public Flux<String> analyzeFiles(@RequestParam("files") MultipartFile[] files, |
| | | @RequestParam(value = "message", required = false) String message, |
| | | @RequestParam(value = "memoryId", required = false) String memoryId) { |
| | | if (files == null || files.length == 0) { |
| | | return Flux.just("filesä¸è½ä¸ºç©º"); |
| | | } |
| | | if (files.length > MAX_FILE_COUNT) { |
| | | return Flux.just("䏿¬¡æå¤åæ" + MAX_FILE_COUNT + "个æä»¶"); |
| | | } |
| | | |
| | | String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString(); |
| | | String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX) |
| | | ? rawMemoryId |
| | | : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId; |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | aiSessionUserContext.bind(finalMemoryId, loginUser); |
| | | |
| | | String finalMessage = StringUtils.hasText(message) |
| | | ? message |
| | | : "请åæè¿äºéè´æä»¶ï¼æåå¯ç¨äºä¸å¡å¤ççæ°æ®ï¼å¹¶æ´çæå¾
客æ·ç¡®è®¤çæ ¼å¼"; |
| | | |
| | | String fileContent; |
| | | try { |
| | | fileContent = buildMultiFileContent(files); |
| | | } catch (IllegalArgumentException ex) { |
| | | return Flux.just(ex.getMessage()); |
| | | } catch (IOException ex) { |
| | | return Flux.just("æä»¶è¯»å失败"); |
| | | } |
| | | |
| | | if (!StringUtils.hasText(fileContent)) { |
| | | return Flux.just("æªæåå°æææä»¶å
容"); |
| | | } |
| | | |
| | | String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent); |
| | | aiChatSessionService.touchSession(finalMemoryId, loginUser, "éè´å¤æä»¶åæ: " + finalMessage); |
| | | |
| | | if (containsImageFile(files)) { |
| | | return chatWithPurchaseVisionModel(finalMemoryId, userPrompt, files) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt)) |
| | | .onErrorResume(NoSuchElementException.class, ex -> { |
| | | mongoChatMemoryStore.deleteMessages(finalMemoryId); |
| | | return purchaseAgent.chat(finalMemoryId, userPrompt); |
| | | }) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | return purchaseAiService.analyzeFiles(files, message, memoryId, loginUser); |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¤æä»¶åæç¡®è®¤å¤ç") |
| | | @PostMapping("/analyze-files/confirm") |
| | | public AjaxResult confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) { |
| | | if (request == null || !StringUtils.hasText(request.getBusinessType())) { |
| | | return AjaxResult.error("businessTypeä¸è½ä¸ºç©º"); |
| | | } |
| | | if (request.getPayload() == null || request.getPayload().isEmpty()) { |
| | | return AjaxResult.error("payloadä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | String businessType = request.getBusinessType().trim(); |
| | | return switch (businessType) { |
| | | case "purchase_ledger" -> processPurchaseLedger(request.getPayload()); |
| | | case "payment_registration" -> processPaymentRegistration(request.getPayload()); |
| | | case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload()); |
| | | default -> AjaxResult.error("æä¸æ¯æè¯¥ä¸å¡ç±»å: " + businessType); |
| | | }; |
| | | } catch (Exception ex) { |
| | | return AjaxResult.error(toCustomerMessage(ex)); |
| | | } |
| | | return purchaseAiService.confirmAnalyzeResult(request); |
| | | } |
| | | |
| | | @Operation(summary = "éè´ä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | return success(purchaseAiService.listSessions(loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "éè´ä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | return success(purchaseAiService.listMessages(memoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "å é¤éè´ä¼è¯") |
| | | @DeleteMapping("/history/{memoryId}") |
| | | public AjaxResult deleteSession(@PathVariable String memoryId) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private String buildMultiFileContent(MultipartFile[] files) throws IOException { |
| | | StringBuilder builder = new StringBuilder(); |
| | | int totalLength = 0; |
| | | for (MultipartFile file : files) { |
| | | String text = aiFileTextExtractor.extractText(file); |
| | | if (!StringUtils.hasText(text)) { |
| | | continue; |
| | | } |
| | | String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH |
| | | ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH) |
| | | : text; |
| | | if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) { |
| | | int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength; |
| | | if (remain <= 0) { |
| | | break; |
| | | } |
| | | limitedText = limitedText.substring(0, remain); |
| | | } |
| | | builder.append("\n--- æä»¶: ") |
| | | .append(file.getOriginalFilename()) |
| | | .append(" ---\n") |
| | | .append(limitedText) |
| | | .append('\n'); |
| | | totalLength += limitedText.length(); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private boolean containsImageFile(MultipartFile[] files) { |
| | | for (MultipartFile file : files) { |
| | | if (aiFileTextExtractor.isImageFile(file)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private Flux<String> chatWithPurchaseVisionModel(String memoryId, String userPrompt, MultipartFile[] files) { |
| | | return Flux.create(sink -> { |
| | | try { |
| | | List<Content> contents = new ArrayList<>(); |
| | | contents.add(TextContent.from(userPrompt)); |
| | | for (MultipartFile file : files) { |
| | | if (!aiFileTextExtractor.isImageFile(file)) { |
| | | continue; |
| | | } |
| | | contents.add(TextContent.from("ä¸é¢è¿å¼ å¾çæä»¶åï¼" + file.getOriginalFilename())); |
| | | contents.add(ImageContent.from(Image.builder() |
| | | .base64Data(Base64.getEncoder().encodeToString(file.getBytes())) |
| | | .mimeType(resolveImageMimeType(file)) |
| | | .build())); |
| | | } |
| | | |
| | | List<ChatMessage> messages = List.of( |
| | | SystemMessage.from("ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä»ææ¬åå¾çä¸è¯å«éè´å°è´¦ãéè´äº§åæç»ã仿¬¾æéè´§ä¿¡æ¯ï¼åªè¾åºåæ³ JSONã"), |
| | | UserMessage.from(contents) |
| | | ); |
| | | purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() { |
| | | @Override |
| | | public void onPartialResponse(String partialResponse) { |
| | | sink.next(partialResponse); |
| | | } |
| | | |
| | | @Override |
| | | public void onCompleteResponse(ChatResponse completeResponse) { |
| | | sink.complete(); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable error) { |
| | | sink.error(error); |
| | | } |
| | | }); |
| | | } catch (Exception ex) { |
| | | sink.next("å¾çæä»¶è¯»å失败ï¼è¯·ç¡®è®¤å¾çæ ¼å¼ä¸º pngãjpgãjpegãwebp æ bmpï¼ä¸å¤§å°ä¸è¶
è¿10MB"); |
| | | sink.complete(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private String resolveImageMimeType(MultipartFile file) { |
| | | String contentType = file.getContentType(); |
| | | if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) { |
| | | return contentType; |
| | | } |
| | | String filename = file.getOriginalFilename(); |
| | | String ext = ""; |
| | | if (StringUtils.hasText(filename) && filename.contains(".")) { |
| | | ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); |
| | | } |
| | | return switch (ext) { |
| | | case "jpg", "jpeg" -> "image/jpeg"; |
| | | case "webp" -> "image/webp"; |
| | | case "bmp" -> "image/bmp"; |
| | | default -> "image/png"; |
| | | }; |
| | | } |
| | | |
| | | private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) { |
| | | return """ |
| | | ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä¸¥æ ¼æ ¹æ®ç¨æ·ä¸ä¼ çå¤ä¸ªæä»¶åç¨æ·è¦æ±æåéè´ä¸å¡æ°æ®ã |
| | | |
| | | ç¨æ·è¦æ±: |
| | | %s |
| | | |
| | | è¾åºè¦æ±: |
| | | 1. åªè¾åºåæ³ JSONï¼ä¸è¦ Markdownï¼ä¸è¦é¢å¤è§£éã |
| | | 2. JSON é¡¶å±å段åºå®ä¸º: |
| | | - success: boolean |
| | | - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown |
| | | - action: confirm_required |
| | | - description: ä¸æè¯´æ |
| | | - confidence: 0å°1çå°æ° |
| | | - missingFields: ç¼ºå¤±åæ®µä¸æåç§°æ°ç»ï¼é¢å客æ·å±ç¤ºï¼ä¸è¦è¾åºè±æå段å |
| | | - warnings: é£é©æç¤ºæ°ç» |
| | | - payload: å¾
客æ·ç¡®è®¤çæ°æ®ï¼å段åå¿
须使ç¨å端 DTO åæ®µå |
| | | - preview: ç»å®¢æ·ç¡®è®¤ç¨ç䏿æè¦æ°ç» |
| | | 3. 妿å¯å¤æä¸ºéè´å°è´¦ï¼businessType ä½¿ç¨ purchase_ledgerï¼payload.purchaseLedgers 为éè´è®¢å/éè´å°è´¦æ°ç»: |
| | | - purchaseLedgers: éè´è®¢å/éè´å°è´¦æ°ç»ï¼æ¯æ¡è®°å½å段åå¿
é¡»ä¸ PurchaseLedgerDto ä¿æä¸è´ |
| | | - 产åæç»å¿
é¡»æ¾å¨æ¯æ¡éè´å°è´¦è®°å½ç productData åæ®µä¸ï¼productData ç±»å为 List<SalesLedgerProduct> |
| | | - ä¸è¦ä¼å
ä½¿ç¨ payload é¡¶å± productDataï¼é¡¶å± productData ä»
ä½ä¸ºæ§æ ¼å¼å
¼å®¹ |
| | | - æä»¶éçâéè´åå·âå°±æ¯âéè´ååå·âï¼ç»ä¸æ å°ä¸º purchaseContractNumber |
| | | - æä»¶éçâéå®åå·âå°±æ¯âéå®ååå·âï¼ç»ä¸æ å°ä¸º salesContractNo |
| | | - æææ¥æå段å¿
é¡»ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30ï¼ä¸è¦è¾åº 4/30/26ã2026/4/30ã2026å¹´4æ30æ¥ æå¸¦æ¶åç§çæ ¼å¼ |
| | | - éè´å°è´¦ä¸éè¦å¨ payload ä¸ä¼ 审æ¹äººï¼ä¸è¦è¾åº approveUserIdsãapproverId |
| | | - missingFields åªå¡«åä¸å¡å¿
填使 æ³è¯å«çåæ®µï¼ä¸è¦æ PurchaseLedgerDto çææç©ºåæ®µé½å为缺失ï¼ç¼ºå¤±é¡¹å¿
é¡»å䏿ï¼ä¾å¦âä¾åºååç§°ââå«ç¨åä»·âï¼ä¸è¦å supplierIdãtaxInclusiveUnitPrice |
| | | - éè´å°è´¦ä¸»è¡¨å¿
å¡«åæ®µä»
æè¿äºå¤æ: purchaseContractNumberãsupplierName æ supplierId |
| | | - productData æ¯æ¡äº§åå¿
å¡«åæ®µ: productCategoryãspecificationModelãunitãquantityãtaxInclusiveUnitPrice æ taxInclusiveTotalPriceï¼å¦æåªæå«ç¨æ»ä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveUnitPriceï¼å¦æåªæå«ç¨åä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveTotalPrice |
| | | - 产ååæ®µæéè´å¯¼å
¥æ¥å£ PurchaseLedgerProductImportDto 对é½: éè´åå·ã产å大类ãè§æ ¼åå·ãåä½ãæ°éãç¨çãå«ç¨åä»·ãå«ç¨æ»ä»·ãå票类åãæ¯å¦è´¨æ£ |
| | | - éè´äº§å type åºå®ä¸º 2 |
| | | - purchaseLedgers æ¯æ¡è®°å½åªä½¿ç¨è¿äº PurchaseLedgerDto åæ®µå: |
| | | entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName |
| | | - productData æ¯æ¡äº§ååªä½¿ç¨è¿äº SalesLedgerProduct åæ®µå: |
| | | productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type |
| | | 4. 妿å¯å¤æä¸ºä»æ¬¾ç»è®°ï¼businessType ä½¿ç¨ payment_registrationï¼payload.records ä¸ºä»æ¬¾ç»è®°æ°ç»ï¼å段尽éå
å« purchaseLedgerIdãsalesLedgerProductIdãcurrentPaymentAmountãpaymentMethodãpaymentDateã |
| | | 5. 妿å¯å¤æä¸ºéè´éè´§ï¼businessType ä½¿ç¨ purchase_return_orderï¼payload æ PurchaseReturnOrderDto ç»ç»ï¼æç»æ¾ purchaseReturnOrderProductsDtosã |
| | | 6. 缺å°ä¸å¡å¤çå¿
须忮µæ¶ï¼ä¸è¦ç¼é IDï¼æåæ®µæ¾å
¥ missingFieldsï¼å¹¶ä»è¿åå¯ç¡®è®¤çèç¨¿æ°æ®ã |
| | | 7. ææä¸æå
å®¹ç´æ¥ä¿çï¼ä¸è¦è½¬ä¹æ Unicodeã |
| | | |
| | | æä»¶å
容: |
| | | %s |
| | | """.formatted(message, fileContent); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception { |
| | | if (payload.containsKey("purchaseLedgers")) { |
| | | return processPurchaseLedgerBatch(payload); |
| | | } |
| | | |
| | | Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, 0); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | return AjaxResult.success("éè´å°è´¦å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception { |
| | | List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers")); |
| | | if (purchaseLedgers.isEmpty()) { |
| | | return AjaxResult.error("purchaseLedgersä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData")); |
| | | List<Map<String, Object>> results = new ArrayList<>(); |
| | | for (int i = 0; i < purchaseLedgers.size(); i++) { |
| | | Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i)); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, i); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | |
| | | List<SalesLedgerProduct> products = dto.getProductData(); |
| | | if (products == null || products.isEmpty()) { |
| | | products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1); |
| | | dto.setProductData(products); |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(products, i); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("index", i); |
| | | item.put("purchaseContractNumber", dto.getPurchaseContractNumber()); |
| | | item.put("supplierId", dto.getSupplierId()); |
| | | item.put("supplierName", dto.getSupplierName()); |
| | | item.put("productCount", products.size()); |
| | | item.put("result", result); |
| | | results.add(item); |
| | | } |
| | | return AjaxResult.success("éè´å°è´¦å·²æ¹éå¤ç", results); |
| | | } |
| | | |
| | | private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap, |
| | | PurchaseLedgerDto dto, |
| | | List<Map<String, Object>> productData, |
| | | boolean onlyOneLedger) { |
| | | List<SalesLedgerProduct> products = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productData) { |
| | | if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) { |
| | | products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class)); |
| | | } |
| | | } |
| | | return products; |
| | | } |
| | | |
| | | private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) { |
| | | Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "éè´è®¢åid", "éè´å°è´¦id"); |
| | | if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | Long productSalesLedgerId = longValue(productMap, "salesLedgerId"); |
| | | if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(dto.getPurchaseContractNumber()) |
| | | && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(ledgerContractNo) |
| | | && productContractNo.trim().equals(ledgerContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(dto.getSalesContractNo()) |
| | | && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(ledgerSalesContractNo) |
| | | && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSupplierName = stringValue(productMap, "supplierName", "ä¾åºååç§°"); |
| | | return StringUtils.hasText(productSupplierName) |
| | | && StringUtils.hasText(dto.getSupplierName()) |
| | | && productSupplierName.trim().equals(dto.getSupplierName().trim()); |
| | | } |
| | | |
| | | private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copyPurchaseLedgerDtoFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "entryDateStart", "å½å
¥å¼å§æ¥æ", "å½å
¥æ¥æå¼å§"); |
| | | putDtoFieldIfPresent(source, target, "entryDateEnd", "å½å
¥ç»ææ¥æ", "å½å
¥æ¥æç»æ"); |
| | | putDtoFieldIfPresent(source, target, "id", "éè´å°è´¦id", "éè´è®¢åid", "主é®"); |
| | | putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "supplierId", "ä¾åºåid", "ä¾åºåID", "ä¾åºååç§°id", "ä¾åºååç§°ID"); |
| | | putDtoFieldIfPresent(source, target, "supplierName", "ä¾åºå", "ä¾åºååç§°"); |
| | | putDtoFieldIfPresent(source, target, "isWhite", "æ¯å¦ç½åå"); |
| | | putDtoFieldIfPresent(source, target, "recorderId", "å½å
¥äººid", "å½å
¥äººID", "å½å
¥äººå§åid", "å½å
¥äººå§åID"); |
| | | putDtoFieldIfPresent(source, target, "recorderName", "å½å
¥äºº", "å½å
¥äººå§å"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNoId", "éå®ååå·id", "éå®ååå·ID", "éå®åå·id", "éå®åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "projectName", "项ç®", "项ç®åç§°"); |
| | | putDtoFieldIfPresent(source, target, "entryDate", "å½å
¥æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "executionDate", "ç¾è®¢æ¥æ", "ååç¾è®¢æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "remarks", "夿³¨", "说æ"); |
| | | putDtoFieldIfPresent(source, target, "attachmentMaterials", "éä»¶ææ", "éä»¶ææè·¯å¾æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "createdAt", "å建æ¶é´", "è®°å½å建æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "updatedAt", "æ´æ°æ¶é´", "è®°å½æåæ´æ°æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "salesLedgerId", "éå®å°è´¦id", "éå®å°è´¦ID", "å
³èéå®å°è´¦ä¸»è¡¨ä¸»é®"); |
| | | putDtoFieldIfPresent(source, target, "hasChildren", "æ¯å¦æå级", "æ¯å¦ææç»"); |
| | | putDtoFieldIfPresent(source, target, "Type", "å°è´¦ç±»å", "ä¸å¡ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "productData", "products", "产åæç»", "éè´äº§åæç»"); |
| | | putDtoFieldIfPresent(source, target, "tempFileIds", "ä¸´æ¶æä»¶id", "ä¸´æ¶æä»¶ID", "ä¸´æ¶æä»¶ids"); |
| | | putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "éä»¶å表", "éå®å°è´¦éä»¶"); |
| | | putDtoFieldIfPresent(source, target, "phoneNumber", "ä¸å¡åææºå·", "ææºå·"); |
| | | putDtoFieldIfPresent(source, target, "businessPersonId", "ä¸å¡åid", "ä¸å¡åID"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID"); |
| | | putDtoFieldIfPresent(source, target, "invoiceNumber", "å票å·", "å票å·ç "); |
| | | putDtoFieldIfPresent(source, target, "invoiceAmount", "å票éé¢", "å票éé¢ï¼å
ï¼"); |
| | | putDtoFieldIfPresent(source, target, "ticketRegistrationId", "æ¥ç¥¨ç»è®°id", "æ¥ç¥¨ç»è®°ID"); |
| | | putDtoFieldIfPresent(source, target, "contractAmount", "ååéé¢", "ååéé¢ï¼äº§åå«ç¨æ»ä»·ï¼"); |
| | | putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "æªæ¥ç¥¨éé¢", "æªæ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "type", "æä»¶ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "paymentMethod", "仿¬¾æ¹å¼"); |
| | | putDtoFieldIfPresent(source, target, "approvalStatus", "审æ¹ç¶æ"); |
| | | putDtoFieldIfPresent(source, target, "templateName", "模æ¿åç§°"); |
| | | target.remove("approveUserIds"); |
| | | target.remove("approverId"); |
| | | normalizeNestedProductData(target); |
| | | attachImportStyleProductData(source, target); |
| | | if (target.get("type") == null) { |
| | | target.put("type", 2); |
| | | } |
| | | target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)); |
| | | normalizePurchaseLedgerDateFields(target); |
| | | return target; |
| | | } |
| | | |
| | | private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) { |
| | | if (target.get("productData") != null) { |
| | | return; |
| | | } |
| | | Map<String, Object> productMap = normalizeSalesLedgerProductMap(source); |
| | | if (hasImportStyleProductData(productMap)) { |
| | | target.put("productData", List.of(productMap)); |
| | | } |
| | | } |
| | | |
| | | private boolean hasImportStyleProductData(Map<String, Object> productMap) { |
| | | return hasMapText(productMap, "productCategory") |
| | | || hasMapText(productMap, "specificationModel") |
| | | || productMap.get("quantity") != null |
| | | || productMap.get("taxInclusiveUnitPrice") != null |
| | | || productMap.get("taxInclusiveTotalPrice") != null; |
| | | } |
| | | |
| | | private boolean hasMapText(Map<String, Object> map, String key) { |
| | | Object value = map.get(key); |
| | | return value != null && StringUtils.hasText(String.valueOf(value)); |
| | | } |
| | | |
| | | private void normalizeNestedProductData(Map<String, Object> target) { |
| | | Object productDataValue = target.get("productData"); |
| | | if (productDataValue == null) { |
| | | return; |
| | | } |
| | | List<Map<String, Object>> productMaps = toMapList(productDataValue); |
| | | List<Map<String, Object>> normalizedProducts = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productMaps) { |
| | | normalizedProducts.add(normalizeSalesLedgerProductMap(productMap)); |
| | | } |
| | | target.put("productData", normalizedProducts); |
| | | } |
| | | |
| | | private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copySalesLedgerProductFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "productCategory", "产å大类", "产ååç§°", "产å", "åå", "ç©æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "specificationModel", "è§æ ¼åå·", "åå·", "è§æ ¼", "产åè§æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "unit", "åä½"); |
| | | putDtoFieldIfPresent(source, target, "quantity", "æ°é", "éè´æ°é"); |
| | | putDtoFieldIfPresent(source, target, "taxRate", "ç¨ç"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "å«ç¨åä»·", "åä»·", "éè´åä»·", "å«ç¨ä»·æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "å«ç¨æ»ä»·", "æ»ä»·", "éè´éé¢", "éé¢", "ååéé¢"); |
| | | putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "ä¸å«ç¨æ»ä»·"); |
| | | putDtoFieldIfPresent(source, target, "invoiceType", "å票类å", "å票类å«"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID", "åå·id", "åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "isChecked", "æ¯å¦è´¨æ£", "æ¯å¦è´¨æ£éª", "è´¨æ£"); |
| | | putDtoFieldIfPresent(source, target, "type", "å°è´¦ç±»å"); |
| | | normalizeProductAmounts(target); |
| | | target.putIfAbsent("type", 2); |
| | | return target; |
| | | } |
| | | |
| | | private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] productFields = { |
| | | "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit", |
| | | "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum", |
| | | "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum", |
| | | "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate", |
| | | "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal", |
| | | "isChecked", "isProduction" |
| | | }; |
| | | for (String field : productFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void normalizeProductAmounts(Map<String, Object> target) { |
| | | BigDecimal quantity = decimalValue(target.get("quantity")); |
| | | BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice")); |
| | | BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) { |
| | | target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP)); |
| | | } |
| | | if (totalPrice == null && unitPrice != null && quantity != null) { |
| | | target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity)); |
| | | } |
| | | BigDecimal taxRate = decimalValue(target.get("taxRate")); |
| | | totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) { |
| | | BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP)); |
| | | target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP)); |
| | | } |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) { |
| | | if (products == null || products.isEmpty()) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < products.size(); i++) { |
| | | SalesLedgerProduct product = products.get(i); |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦ç第" + (i + 1) + "æ¡äº§å"; |
| | | if (!StringUtils.hasText(product.getProductCategory())) { |
| | | return AjaxResult.error(prefix + "缺å°äº§ååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getSpecificationModel())) { |
| | | return AjaxResult.error(prefix + "缺å°è§æ ¼åå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getUnit())) { |
| | | return AjaxResult.error(prefix + "缺å°åä½ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getQuantity() == null) { |
| | | return AjaxResult.error(prefix + "ç¼ºå°æ°é"); |
| | | } |
| | | if (product.getTaxInclusiveUnitPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getTaxInclusiveTotalPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) { |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦"; |
| | | if (!StringUtils.hasText(dto.getPurchaseContractNumber())) { |
| | | return AjaxResult.error(prefix + "缺å°éè´ååå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error(prefix + "缺å°ä¾åºååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void normalizePurchaseLedgerDateFields(Map<String, Object> target) { |
| | | normalizeDateField(target, "entryDate"); |
| | | normalizeDateField(target, "executionDate"); |
| | | normalizeDateField(target, "createdAt"); |
| | | normalizeDateField(target, "updatedAt"); |
| | | } |
| | | |
| | | private void normalizeDateField(Map<String, Object> target, String fieldName) { |
| | | Object value = target.get(fieldName); |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | String normalizedDate = normalizeDateValue(value); |
| | | if (StringUtils.hasText(normalizedDate)) { |
| | | target.put(fieldName, normalizedDate); |
| | | } |
| | | } |
| | | |
| | | private String normalizeDateValue(Object value) { |
| | | if (value instanceof Date date) { |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | if (value instanceof Number number) { |
| | | return LocalDate.of(1899, 12, 30) |
| | | .plusDays(number.longValue()) |
| | | .format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | |
| | | String text = String.valueOf(value).trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') { |
| | | return text.substring(0, 10); |
| | | } |
| | | |
| | | String normalizedText = text.replace("å¹´", "-") |
| | | .replace("æ", "-") |
| | | .replace("æ¥", "") |
| | | .replace(".", "-") |
| | | .replace("/", "-") |
| | | .trim(); |
| | | DateTimeFormatter[] formatters = { |
| | | DateTimeFormatter.ofPattern("yyyy-M-d"), |
| | | DateTimeFormatter.ofPattern("M-d-yyyy"), |
| | | DateTimeFormatter.ofPattern("M-d-yy") |
| | | }; |
| | | for (DateTimeFormatter formatter : formatters) { |
| | | try { |
| | | return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } catch (DateTimeParseException ignored) { |
| | | // Try the next supported input pattern. |
| | | } |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] dtoFields = { |
| | | "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber", |
| | | "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo", |
| | | "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials", |
| | | "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds", |
| | | "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber", |
| | | "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount", |
| | | "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName" |
| | | }; |
| | | for (String field : dtoFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) { |
| | | if (target.containsKey(dtoField) && target.get(dtoField) != null) { |
| | | return; |
| | | } |
| | | for (String alias : aliases) { |
| | | Object value = source.get(alias); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | target.put(dtoField, value); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> toMapList(Object value) { |
| | | if (value == null) { |
| | | return List.of(); |
| | | } |
| | | return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() { |
| | | }); |
| | | } |
| | | |
| | | private String stringValue(Map<String, Object> map, String... keys) { |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | return String.valueOf(value); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Long longValue(Map<String, Object> map, String... keys) { |
| | | String value = stringValue(map, keys); |
| | | if (!StringUtils.hasText(value)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return Long.parseLong(value.trim()); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Object value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | if (value instanceof BigDecimal decimal) { |
| | | return decimal; |
| | | } |
| | | if (value instanceof Number number) { |
| | | return new BigDecimal(String.valueOf(number)); |
| | | } |
| | | String text = String.valueOf(value) |
| | | .replace(",", "") |
| | | .replace("ï¼", "") |
| | | .replace("å
", "") |
| | | .replace("ï¿¥", "") |
| | | .trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return new BigDecimal(text); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String toCustomerMessage(Exception ex) { |
| | | String message = ex.getMessage(); |
| | | if (!StringUtils.hasText(message)) { |
| | | return "å¤ç失败ï¼è¯·æ£æ¥ç¡®è®¤æ°æ®åéè¯"; |
| | | } |
| | | if (message.contains("tax_inclusive_unit_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("tax_inclusive_total_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("entryDate")) { |
| | | return "å¤ç失败ï¼å½å
¥æ¥ææ ¼å¼ä¸æ£ç¡®ï¼è¯·ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30"; |
| | | } |
| | | if (message.contains("supplier")) { |
| | | return "å¤ç失败ï¼ä¾åºåä¿¡æ¯ä¸å®æ´ï¼è¯·ç¡®è®¤ä¾åºååç§°æä¾åºåID"; |
| | | } |
| | | if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) { |
| | | return "å¤ç失败ï¼ç¡®è®¤æ°æ®ä¸å®æ´ææ ¼å¼ä¸æ£ç¡®ï¼è¯·æ£æ¥å¿
å¡«åæ®µåéè¯"; |
| | | } |
| | | return "å¤ç失败ï¼" + message; |
| | | } |
| | | |
| | | private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) { |
| | | if (dto.getSupplierId() != null) { |
| | | return null; |
| | | } |
| | | if (!StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error("ä¾åºåIDä¸è½ä¸ºç©ºï¼æªè¯å«å°ä¾åºååç§°ï¼æ æ³èªå¨å¹é
ä¾åºåID"); |
| | | } |
| | | |
| | | SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>() |
| | | .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim()) |
| | | .last("limit 1")); |
| | | if (supplier == null) { |
| | | return AjaxResult.error("æªæ¾å°ä¾åºåï¼" + dto.getSupplierName() + "ï¼è¯·å
ç»´æ¤ä¾åºåææå¨éæ©ä¾åºåID"); |
| | | } |
| | | dto.setSupplierId(supplier.getId()); |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult processPaymentRegistration(Map<String, Object> payload) { |
| | | Object recordsValue = payload.get("records"); |
| | | List<PaymentRegistration> records; |
| | | if (recordsValue == null) { |
| | | records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class)); |
| | | } else { |
| | | records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() { |
| | | }); |
| | | } |
| | | int result = paymentRegistrationService.insertPaymentRegistration(records); |
| | | return AjaxResult.success("仿¬¾ç»è®°å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) { |
| | | PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class); |
| | | Boolean result = purchaseReturnOrdersService.add(dto); |
| | | return AjaxResult.success("éè´éè´§åå·²å¤ç", result); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | return toAjax(purchaseAiService.deleteSession(memoryId, loginUser)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.ruoyi.ai.assistant.SalesAgent; |
| | | import com.ruoyi.ai.assistant.SalesIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.service.AiChatSessionService; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | |
| | | @Tag(name = "éå®å©ææºè½ä½") |
| | | @RestController |
| | | @RequestMapping("/sales-ai") |
| | | public class SalesAiController extends BaseController { |
| | | |
| | | private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final SalesAgent salesAgent; |
| | | private final SalesIntentExecutor salesIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | |
| | | public SalesAiController(SalesAgent salesAgent, |
| | | SalesIntentExecutor salesIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService) { |
| | | this.salesAgent = salesAgent; |
| | | this.salesIntentExecutor = salesIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | } |
| | | |
| | | @Operation(summary = "éå®å©æå¯¹è¯") |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> chat(@RequestBody ChatForm chatForm) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = salesIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | if (isBusinessDataIntent(userMessage)) { |
| | | String noGuessResponse = "æªè¯å«å°å¯æ§è¡çæ°æ®æ¥è¯¢æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é æ°æ®ï¼è¯·è¡¥å
æç¡®æ¶é´èå´ãå®¢æ·æåå·ååæ¥è¯¢ã"; |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(noGuessResponse); |
| | | } |
| | | |
| | | return salesAgent.chat(memoryId, userMessage, currentDateForPrompt()) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "éå®å©æä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "éå®å©æä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "å é¤éå®å©æä¼è¯") |
| | | @DeleteMapping("/history/{memoryId}") |
| | | public AjaxResult deleteSession(@PathVariable String memoryId) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private boolean isBusinessDataIntent(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return false; |
| | | } |
| | | String text = message.trim(); |
| | | return containsAny(text, |
| | | "æ¥è¯¢", "æ¥ç", "ç»è®¡", "åæ", "建议", "å®¢æ·æ¡£æ¡", "ç§æµ·", "å
¬æµ·", |
| | | "é宿¥ä»·", "éå®å°è´¦", "éå®éè´§", "客æ·å¾æ¥", "åè´§å°è´¦", "忬¾", "æ¥ä»·", "é£é©"); |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String currentDateForPrompt() { |
| | | return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT); |
| | | } |
| | | } |
| | |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.IOException; |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | |
| | | public class XiaozhiController extends BaseController { |
| | | |
| | | private static final String FILE_ANALYZE_MEMORY_PREFIX = "file-analyze::"; |
| | | private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final ApproveTodoAgent approveTodoAgent; |
| | | private final ApproveTodoIntentExecutor approveTodoIntentExecutor; |
| | |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | return approveTodoAgent.chat(memoryId, userMessage) |
| | | if (isApproveTodoBusinessIntent(userMessage)) { |
| | | String noGuessResponse = "æªè¯å«å°å¯æ§è¡ç审æ¹å¾
åæä½æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é å®¡æ¹æ°æ®ï¼è¯·è¡¥å
æµç¨ç¼å·ãæ¶é´èå´ææç¡®æä½æä»¤ååè¯ã"; |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(noGuessResponse); |
| | | } |
| | | |
| | | return approveTodoAgent.chat(memoryId, userMessage, currentDateForPrompt()) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private boolean isApproveTodoBusinessIntent(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return false; |
| | | } |
| | | String text = message.trim(); |
| | | boolean hasDomainWord = containsAny(text, |
| | | "审æ¹", "å¾
å", "æµç¨ç¼å·", "æµç¨å·", "å®¡æ¹æµè½¬", "审æ¹èç¹", "å½å审æ¹äºº", "驳å", "éè¿", "æ¤é", "å é¤"); |
| | | boolean hasIntentWord = containsAny(text, |
| | | "æ¥è¯¢", "æ¥ç", "ååº", "ç»è®¡", "åæ", "åå¸", "éè¿", "驳å", "æ¤é", "å é¤", "ä¿®æ¹", "æåªäº", "å¡å¨"); |
| | | return hasDomainWord && hasIntentWord; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String currentDateForPrompt() { |
| | | return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT); |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.ai.dto; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class AiChatMessageDto { |
| | | |
| | | private String role; |
| | | |
| | | private String content; |
| | | |
| | | private List<String> filePaths; |
| | | |
| | | public AiChatMessageDto(String role, String content) { |
| | | this.role = role; |
| | | this.content = content; |
| | | } |
| | | |
| | | public AiChatMessageDto(String role, String content, List<String> filePaths) { |
| | | this.role = role; |
| | | this.content = content; |
| | | this.filePaths = filePaths; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.dto; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * ç¥è¯åºé®çè¯·æ± |
| | | */ |
| | | @Data |
| | | @Schema(description = "ç¥è¯åºé®ç请æ±") |
| | | public class KnowledgeChatRequest { |
| | | |
| | | @Schema(description = "ç¥è¯åºID", required = true) |
| | | private Long knowledgeBaseId; |
| | | |
| | | @Schema(description = "ä¼è¯IDï¼ç¨äºä¿æä¸ä¸æ", required = true) |
| | | private String memoryId; |
| | | |
| | | @Schema(description = "ç¨æ·æé®å
容", required = true) |
| | | private String question; |
| | | } |
| | |
| | | import org.springframework.data.mongodb.core.mapping.Document; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @AllArgsConstructor |
| | |
| | | |
| | | private String content; |
| | | |
| | | /** |
| | | * 夿件åæç¨æ·æé®ä¿¡æ¯ï¼ä¸æä»¶è·¯å¾åå¼åå¨ï¼ |
| | | */ |
| | | private List<String> analyzeUserQuestions; |
| | | |
| | | /** |
| | | * 夿件åæä¸ä¼ æä»¶è·¯å¾ï¼å¾çå pdf 使ç¨é¢è§å°åï¼å
¶ä»ä½¿ç¨ä¸è½½å°åï¼ |
| | | */ |
| | | private List<String> analyzeFilePaths; |
| | | |
| | | /** |
| | | * 夿件忿¯æ¬¡æé®å¯¹åºçæä»¶è·¯å¾åç» |
| | | */ |
| | | private List<List<String>> analyzeFilePathGroups; |
| | | |
| | | private Date createTime; |
| | | |
| | | private Date updateTime; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.schedule; |
| | | |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | @Component |
| | | public class AiSessionCleanupTask { |
| | | |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | |
| | | public AiSessionCleanupTask(AiSessionUserContext aiSessionUserContext) { |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | } |
| | | |
| | | @Scheduled(cron = "0 0 2 * * ?") |
| | | public void cleanupExpiredSessions() { |
| | | try { |
| | | aiSessionUserContext.cleanExpiredSessions(); |
| | | } catch (Exception e) { |
| | | System.err.println("æ¸
çè¿æAIä¼è¯å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ç¥è¯åºRAGæå¡ |
| | | * è´è´£æä»¶åéåå¤çåæ£ç´¢ |
| | | */ |
| | | public interface KnowledgeRagService { |
| | | |
| | | /** |
| | | * 弿¥å¤çåéå |
| | | */ |
| | | void processVectorAsync(Long vectorId); |
| | | |
| | | /** |
| | | * 忥å¤çåéå |
| | | */ |
| | | void processVector(Long vectorId); |
| | | |
| | | /** |
| | | * æ£ç´¢ç¸å
³å
容 |
| | | * @param namespace å½åç©ºé´ |
| | | * @param query æ¥è¯¢ææ¬ |
| | | * @param maxResults æå¤§ç»ææ° |
| | | * @return ç¸å
³å
容å表 |
| | | */ |
| | | List<String> searchRelevantContent(String namespace, String query, int maxResults); |
| | | |
| | | /** |
| | | * å 餿宿件çåéæ°æ® |
| | | */ |
| | | void deleteEmbeddings(String namespace, Long storageBlobId); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.service; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.ruoyi.ai.assistant.PurchaseAgent; |
| | | import com.ruoyi.ai.assistant.PurchaseIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.bean.PurchaseAiConfirmRequest; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.dto.AiChatMessageDto; |
| | | import com.ruoyi.ai.dto.AiChatSessionDto; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.basic.dto.StorageBlobVO; |
| | | import com.ruoyi.basic.mapper.SupplierManageMapper; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | import com.ruoyi.basic.service.StorageBlobService; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.purchase.dto.PurchaseLedgerDto; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderDto; |
| | | import com.ruoyi.purchase.service.IPurchaseLedgerService; |
| | | import com.ruoyi.purchase.service.PurchaseReturnOrdersService; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import dev.langchain4j.data.image.Image; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.ChatMessage; |
| | | import dev.langchain4j.data.message.Content; |
| | | import dev.langchain4j.data.message.ImageContent; |
| | | import dev.langchain4j.data.message.SystemMessage; |
| | | import dev.langchain4j.data.message.TextContent; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import dev.langchain4j.model.chat.StreamingChatLanguageModel; |
| | | import dev.langchain4j.model.chat.response.ChatResponse; |
| | | import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.Base64; |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | | import java.nio.file.Files; |
| | | |
| | | @Service |
| | | public class PurchaseAiService { |
| | | |
| | | private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::"; |
| | | private static final int MAX_FILE_COUNT = 10; |
| | | private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000; |
| | | private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000; |
| | | private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final PurchaseAgent purchaseAgent; |
| | | private final PurchaseIntentExecutor purchaseIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | private final AiFileTextExtractor aiFileTextExtractor; |
| | | private final ObjectMapper objectMapper; |
| | | private final IPurchaseLedgerService purchaseLedgerService; |
| | | private final PurchaseReturnOrdersService purchaseReturnOrdersService; |
| | | private final StorageBlobService storageBlobService; |
| | | private final SupplierManageMapper supplierManageMapper; |
| | | private final StreamingChatLanguageModel purchaseVisionStreamingChatModel; |
| | | |
| | | public PurchaseAiService(PurchaseAgent purchaseAgent, |
| | | PurchaseIntentExecutor purchaseIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService, |
| | | AiFileTextExtractor aiFileTextExtractor, |
| | | ObjectMapper objectMapper, |
| | | IPurchaseLedgerService purchaseLedgerService, |
| | | PurchaseReturnOrdersService purchaseReturnOrdersService, |
| | | StorageBlobService storageBlobService, |
| | | SupplierManageMapper supplierManageMapper, |
| | | @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) { |
| | | this.purchaseAgent = purchaseAgent; |
| | | this.purchaseIntentExecutor = purchaseIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | this.aiFileTextExtractor = aiFileTextExtractor; |
| | | this.objectMapper = objectMapper; |
| | | this.purchaseLedgerService = purchaseLedgerService; |
| | | this.purchaseReturnOrdersService = purchaseReturnOrdersService; |
| | | this.storageBlobService = storageBlobService; |
| | | this.supplierManageMapper = supplierManageMapper; |
| | | this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel; |
| | | } |
| | | |
| | | public Flux<String> chat(ChatForm chatForm, LoginUser loginUser) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = purchaseIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | if (isPurchaseBusinessIntent(userMessage)) { |
| | | String noGuessResponse = buildNoGuessResponse(); |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(noGuessResponse); |
| | | } |
| | | |
| | | return purchaseAgent.chat(memoryId, userMessage, currentDateForPrompt()) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | | |
| | | public Flux<String> analyzeFiles(MultipartFile[] files, |
| | | String message, |
| | | String memoryId, |
| | | LoginUser loginUser) { |
| | | if (files == null || files.length == 0) { |
| | | return Flux.just("filesä¸è½ä¸ºç©º"); |
| | | } |
| | | if (files.length > MAX_FILE_COUNT) { |
| | | return Flux.just("䏿¬¡æå¤åæ" + MAX_FILE_COUNT + "个æä»¶"); |
| | | } |
| | | |
| | | String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString(); |
| | | String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX) |
| | | ? rawMemoryId |
| | | : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId; |
| | | |
| | | aiSessionUserContext.bind(finalMemoryId, loginUser); |
| | | |
| | | String finalMessage = StringUtils.hasText(message) |
| | | ? message |
| | | : "请åæè¿äºéè´æä»¶ï¼æåå¯ç¨äºä¸å¡å¤ççæ°æ®ï¼å¹¶æ´çæå¾
客æ·ç¡®è®¤çæ ¼å¼"; |
| | | |
| | | List<String> filePaths; |
| | | try { |
| | | List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true); |
| | | filePaths = resolveFileAccessPaths(uploadedFiles); |
| | | } catch (Exception ex) { |
| | | return Flux.just("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | try { |
| | | mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths); |
| | | } catch (Exception ex) { |
| | | return Flux.just("ä¼è¯æä»¶ä¿¡æ¯ä¿å失败"); |
| | | } |
| | | |
| | | String fileContent; |
| | | try { |
| | | fileContent = buildMultiFileContent(files); |
| | | } catch (IllegalArgumentException ex) { |
| | | return Flux.just(ex.getMessage()); |
| | | } catch (IOException ex) { |
| | | return Flux.just("æä»¶è¯»å失败"); |
| | | } |
| | | |
| | | if (!StringUtils.hasText(fileContent)) { |
| | | return Flux.just("æªæåå°æææä»¶å
容"); |
| | | } |
| | | |
| | | String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent); |
| | | aiChatSessionService.touchSession(finalMemoryId, loginUser, "éè´å¤æä»¶åæ: " + finalMessage); |
| | | |
| | | if (containsImageFile(files)) { |
| | | return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt, currentDateForPrompt())) |
| | | .onErrorResume(NoSuchElementException.class, ex -> { |
| | | mongoChatMemoryStore.deleteMessages(finalMemoryId); |
| | | return purchaseAgent.chat(finalMemoryId, userPrompt, currentDateForPrompt()); |
| | | }) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | public AjaxResult confirmAnalyzeResult(PurchaseAiConfirmRequest request) { |
| | | if (request == null || !StringUtils.hasText(request.getBusinessType())) { |
| | | return AjaxResult.error("businessTypeä¸è½ä¸ºç©º"); |
| | | } |
| | | if (request.getPayload() == null || request.getPayload().isEmpty()) { |
| | | return AjaxResult.error("payloadä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | String businessType = request.getBusinessType().trim(); |
| | | return switch (businessType) { |
| | | case "purchase_ledger" -> processPurchaseLedger(request.getPayload()); |
| | | case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload()); |
| | | default -> AjaxResult.error("æä¸æ¯æè¯¥ä¸å¡ç±»å: " + businessType); |
| | | }; |
| | | } catch (Exception ex) { |
| | | return AjaxResult.error(toCustomerMessage(ex)); |
| | | } |
| | | } |
| | | |
| | | public List<AiChatSessionDto> listSessions(LoginUser loginUser) { |
| | | return aiChatSessionService.listCurrentUserSessions(loginUser); |
| | | } |
| | | |
| | | public List<AiChatMessageDto> listMessages(String memoryId, LoginUser loginUser) { |
| | | return aiChatSessionService.listCurrentUserMessages(memoryId, loginUser); |
| | | } |
| | | |
| | | public boolean deleteSession(String memoryId, LoginUser loginUser) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return aiChatSessionService.deleteCurrentUserSession(memoryId, loginUser); |
| | | } |
| | | |
| | | private String buildMultiFileContent(MultipartFile[] files) throws IOException { |
| | | StringBuilder builder = new StringBuilder(); |
| | | int totalLength = 0; |
| | | for (MultipartFile file : files) { |
| | | String text = aiFileTextExtractor.extractText(file); |
| | | if (!StringUtils.hasText(text)) { |
| | | continue; |
| | | } |
| | | String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH |
| | | ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH) |
| | | : text; |
| | | if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) { |
| | | int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength; |
| | | if (remain <= 0) { |
| | | break; |
| | | } |
| | | limitedText = limitedText.substring(0, remain); |
| | | } |
| | | builder.append("\n--- æä»¶: ") |
| | | .append(file.getOriginalFilename()) |
| | | .append(" ---\n") |
| | | .append(limitedText) |
| | | .append('\n'); |
| | | totalLength += limitedText.length(); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private boolean containsImageFile(MultipartFile[] files) { |
| | | for (MultipartFile file : files) { |
| | | if (aiFileTextExtractor.isImageFile(file)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) { |
| | | if (StringUtils.isEmpty(uploadedFiles)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<String> filePaths = new ArrayList<>(); |
| | | for (StorageBlobVO uploadedFile : uploadedFiles) { |
| | | if (uploadedFile == null) { |
| | | continue; |
| | | } |
| | | String selectedPath; |
| | | if (shouldUsePreviewPath(uploadedFile)) { |
| | | selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL()) |
| | | ? uploadedFile.getPreviewURL() |
| | | : uploadedFile.getDownloadURL(); |
| | | } else { |
| | | selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL()) |
| | | ? uploadedFile.getDownloadURL() |
| | | : uploadedFile.getPreviewURL(); |
| | | } |
| | | if (StringUtils.hasText(selectedPath)) { |
| | | filePaths.add(selectedPath); |
| | | } |
| | | } |
| | | return filePaths; |
| | | } |
| | | |
| | | private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) { |
| | | String contentType = uploadedFile.getContentType(); |
| | | if (StringUtils.hasText(contentType)) { |
| | | String normalized = contentType.toLowerCase(Locale.ROOT); |
| | | if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) { |
| | | return true; |
| | | } |
| | | } |
| | | String filename = uploadedFile.getOriginalFilename(); |
| | | if (!StringUtils.hasText(filename) || !filename.contains(".")) { |
| | | return false; |
| | | } |
| | | String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT); |
| | | return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf"); |
| | | } |
| | | |
| | | private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException { |
| | | List<MultipartFile> copies = new ArrayList<>(); |
| | | for (MultipartFile file : files) { |
| | | copies.add(new InMemoryMultipartFile( |
| | | file.getName(), |
| | | file.getOriginalFilename(), |
| | | file.getContentType(), |
| | | file.getBytes() |
| | | )); |
| | | } |
| | | return copies; |
| | | } |
| | | |
| | | private static final class InMemoryMultipartFile implements MultipartFile { |
| | | private final String name; |
| | | private final String originalFilename; |
| | | private final String contentType; |
| | | private final byte[] bytes; |
| | | |
| | | private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) { |
| | | this.name = name; |
| | | this.originalFilename = originalFilename; |
| | | this.contentType = contentType; |
| | | this.bytes = bytes == null ? new byte[0] : bytes; |
| | | } |
| | | |
| | | @Override |
| | | public String getName() { |
| | | return name; |
| | | } |
| | | |
| | | @Override |
| | | public String getOriginalFilename() { |
| | | return originalFilename; |
| | | } |
| | | |
| | | @Override |
| | | public String getContentType() { |
| | | return contentType; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isEmpty() { |
| | | return bytes.length == 0; |
| | | } |
| | | |
| | | @Override |
| | | public long getSize() { |
| | | return bytes.length; |
| | | } |
| | | |
| | | @Override |
| | | public byte[] getBytes() { |
| | | return bytes.clone(); |
| | | } |
| | | |
| | | @Override |
| | | public InputStream getInputStream() { |
| | | return new ByteArrayInputStream(bytes); |
| | | } |
| | | |
| | | @Override |
| | | public void transferTo(File dest) throws IOException, IllegalStateException { |
| | | Files.write(dest.toPath(), bytes); |
| | | } |
| | | } |
| | | |
| | | private Flux<String> chatWithPurchaseVisionModel(String memoryId, |
| | | String userMessage, |
| | | String userPrompt, |
| | | MultipartFile[] files) { |
| | | return Flux.create(sink -> { |
| | | StringBuilder assistantReply = new StringBuilder(); |
| | | try { |
| | | List<Content> contents = new ArrayList<>(); |
| | | contents.add(TextContent.from(userPrompt)); |
| | | for (MultipartFile file : files) { |
| | | if (!aiFileTextExtractor.isImageFile(file)) { |
| | | continue; |
| | | } |
| | | contents.add(TextContent.from("ä¸é¢è¿å¼ å¾çæä»¶åï¼" + file.getOriginalFilename())); |
| | | contents.add(ImageContent.from(Image.builder() |
| | | .base64Data(Base64.getEncoder().encodeToString(file.getBytes())) |
| | | .mimeType(resolveImageMimeType(file)) |
| | | .build())); |
| | | } |
| | | |
| | | List<ChatMessage> messages = List.of( |
| | | SystemMessage.from("ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä»ææ¬åå¾çä¸è¯å«éè´å°è´¦ãéè´äº§åæç»ã仿¬¾æéè´§ä¿¡æ¯ï¼åªè¾åºåæ³ JSONã"), |
| | | UserMessage.from(contents) |
| | | ); |
| | | safeAppendMessages(memoryId, List.of(UserMessage.from("éè´å¤æä»¶åæ: " + userMessage))); |
| | | purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() { |
| | | @Override |
| | | public void onPartialResponse(String partialResponse) { |
| | | if (partialResponse != null) { |
| | | assistantReply.append(partialResponse); |
| | | sink.next(partialResponse); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCompleteResponse(ChatResponse completeResponse) { |
| | | if (StringUtils.hasText(assistantReply.toString())) { |
| | | safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString()))); |
| | | } |
| | | sink.complete(); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable error) { |
| | | sink.error(error); |
| | | } |
| | | }); |
| | | } catch (Exception ex) { |
| | | sink.next("å¾çæä»¶è¯»å失败ï¼è¯·ç¡®è®¤å¾çæ ¼å¼ä¸º pngãjpgãjpegãwebp æ bmpï¼ä¸å¤§å°ä¸è¶
è¿10MB"); |
| | | sink.complete(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private void safeAppendMessages(String memoryId, List<ChatMessage> messages) { |
| | | if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) { |
| | | return; |
| | | } |
| | | try { |
| | | mongoChatMemoryStore.appendMessages(memoryId, messages); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | |
| | | private String resolveImageMimeType(MultipartFile file) { |
| | | String contentType = file.getContentType(); |
| | | if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) { |
| | | return contentType; |
| | | } |
| | | String filename = file.getOriginalFilename(); |
| | | String ext = ""; |
| | | if (StringUtils.hasText(filename) && filename.contains(".")) { |
| | | ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); |
| | | } |
| | | return switch (ext) { |
| | | case "jpg", "jpeg" -> "image/jpeg"; |
| | | case "webp" -> "image/webp"; |
| | | case "bmp" -> "image/bmp"; |
| | | default -> "image/png"; |
| | | }; |
| | | } |
| | | |
| | | private String currentDateForPrompt() { |
| | | return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT); |
| | | } |
| | | |
| | | private boolean isPurchaseBusinessIntent(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return false; |
| | | } |
| | | String text = message.trim(); |
| | | boolean hasDomainWord = containsAny(text, |
| | | "éè´", "éè´å°è´¦", "éè´å", "éè´è®¢å", "ä¾åºå", "ç©æ", "å
¥åº", "å°è´§", "å¾
仿¬¾", |
| | | "仿¬¾", "éè´§", "éæ", "å票", "åå"); |
| | | boolean hasIntentWord = containsAny(text, |
| | | "æ¥è¯¢", "æ¥ç", "ç»è®¡", "åæ", "æè¡", "æå", "ååº", "æåªäº", "æ
åµ", "æç»", "详æ
", "æ¥è¡¨"); |
| | | return hasDomainWord && hasIntentWord; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String buildNoGuessResponse() { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("success", false); |
| | | result.put("type", "purchase_intent_not_recognized"); |
| | | result.put("description", "æªè¯å«å°å¯æ§è¡çéè´æ¥è¯¢æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é æ°æ®ï¼è¯·è¡¥å
æç¡®æ¶é´èå´ãä¾åºåãéè´ååå·æç©æååæ¥è¯¢ã"); |
| | | result.put("summary", Map.of()); |
| | | result.put("data", Map.of( |
| | | "quickPrompts", List.of( |
| | | "æ¬æéè´é颿åååçç©ææåªäºï¼", |
| | | "åªäºéè´è®¢åè¿æªå
¥åºï¼", |
| | | "æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼", |
| | | "帮æç»è®¡å¾
仿¬¾éè´åï¼", |
| | | "ååºæ¬æéè´éè´§æ
åµ" |
| | | ) |
| | | )); |
| | | result.put("charts", Map.of()); |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) { |
| | | return """ |
| | | ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä¸¥æ ¼æ ¹æ®ç¨æ·ä¸ä¼ çå¤ä¸ªæä»¶åç¨æ·è¦æ±æåéè´ä¸å¡æ°æ®ã |
| | | |
| | | ç¨æ·è¦æ±: |
| | | %s |
| | | |
| | | è¾åºè¦æ±: |
| | | 1. åªè¾åºåæ³ JSONï¼ä¸è¦ Markdownï¼ä¸è¦é¢å¤è§£éã |
| | | 2. JSON é¡¶å±å段åºå®ä¸º: |
| | | - success: boolean |
| | | - businessType: purchase_ledger | purchase_return_order | unknown |
| | | - action: confirm_required |
| | | - description: ä¸æè¯´æ |
| | | - confidence: 0å°1çå°æ° |
| | | - missingFields: ç¼ºå¤±åæ®µä¸æåç§°æ°ç»ï¼é¢å客æ·å±ç¤ºï¼ä¸è¦è¾åºè±æå段å |
| | | - warnings: é£é©æç¤ºæ°ç» |
| | | - payload: å¾
客æ·ç¡®è®¤çæ°æ®ï¼å段åå¿
须使ç¨å端 DTO åæ®µå |
| | | - preview: ç»å®¢æ·ç¡®è®¤ç¨ç䏿æè¦æ°ç» |
| | | 3. 妿å¯å¤æä¸ºéè´å°è´¦ï¼businessType ä½¿ç¨ purchase_ledgerï¼payload.purchaseLedgers 为éè´è®¢å/éè´å°è´¦æ°ç»: |
| | | - purchaseLedgers: éè´è®¢å/éè´å°è´¦æ°ç»ï¼æ¯æ¡è®°å½å段åå¿
é¡»ä¸ PurchaseLedgerDto ä¿æä¸è´ |
| | | - 产åæç»å¿
é¡»æ¾å¨æ¯æ¡éè´å°è´¦è®°å½ç productData åæ®µä¸ï¼productData ç±»å为 List<SalesLedgerProduct> |
| | | - ä¸è¦ä¼å
ä½¿ç¨ payload é¡¶å± productDataï¼é¡¶å± productData ä»
ä½ä¸ºæ§æ ¼å¼å
¼å®¹ |
| | | - æä»¶éçâéè´åå·âå°±æ¯âéè´ååå·âï¼ç»ä¸æ å°ä¸º purchaseContractNumber |
| | | - æä»¶éçâéå®åå·âå°±æ¯âéå®ååå·âï¼ç»ä¸æ å°ä¸º salesContractNo |
| | | - æææ¥æå段å¿
é¡»ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30ï¼ä¸è¦è¾åº 4/30/26ã2026/4/30ã2026å¹´4æ30æ¥ æå¸¦æ¶åç§çæ ¼å¼ |
| | | - éè´å°è´¦ä¸éè¦å¨ payload ä¸ä¼ 审æ¹äººï¼ä¸è¦è¾åº approveUserIdsãapproverId |
| | | - missingFields åªå¡«åä¸å¡å¿
填使 æ³è¯å«çåæ®µï¼ä¸è¦æ PurchaseLedgerDto çææç©ºåæ®µé½å为缺失ï¼ç¼ºå¤±é¡¹å¿
é¡»å䏿ï¼ä¾å¦âä¾åºååç§°ââå«ç¨åä»·âï¼ä¸è¦å supplierIdãtaxInclusiveUnitPrice |
| | | - éè´å°è´¦ä¸»è¡¨å¿
å¡«åæ®µä»
æè¿äºå¤æ: purchaseContractNumberãsupplierName æ supplierId |
| | | - productData æ¯æ¡äº§åå¿
å¡«åæ®µ: productCategoryãspecificationModelãunitãquantityãtaxInclusiveUnitPrice æ taxInclusiveTotalPriceï¼å¦æåªæå«ç¨æ»ä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveUnitPriceï¼å¦æåªæå«ç¨åä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveTotalPrice |
| | | - 产ååæ®µæéè´å¯¼å
¥æ¥å£ PurchaseLedgerProductImportDto 对é½: éè´åå·ã产å大类ãè§æ ¼åå·ãåä½ãæ°éãç¨çãå«ç¨åä»·ãå«ç¨æ»ä»·ãå票类åãæ¯å¦è´¨æ£ |
| | | - éè´äº§å type åºå®ä¸º 2 |
| | | - purchaseLedgers æ¯æ¡è®°å½åªä½¿ç¨è¿äº PurchaseLedgerDto åæ®µå: |
| | | entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName |
| | | - productData æ¯æ¡äº§ååªä½¿ç¨è¿äº SalesLedgerProduct åæ®µå: |
| | | productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type |
| | | 4. 妿å¯å¤æä¸ºä»æ¬¾ç»è®°ï¼businessType ä½¿ç¨ payload.records ä¸ºä»æ¬¾ç»è®°æ°ç»ï¼å段尽éå
å« purchaseLedgerIdãsalesLedgerProductIdãcurrentPaymentAmountãpaymentMethodãpaymentDateã |
| | | 5. 妿å¯å¤æä¸ºéè´éè´§ï¼businessType ä½¿ç¨ purchase_return_orderï¼payload æ PurchaseReturnOrderDto ç»ç»ï¼æç»æ¾ purchaseReturnOrderProductsDtosã |
| | | 6. 缺å°ä¸å¡å¤çå¿
须忮µæ¶ï¼ä¸è¦ç¼é IDï¼æåæ®µæ¾å
¥ missingFieldsï¼å¹¶ä»è¿åå¯ç¡®è®¤çèç¨¿æ°æ®ã |
| | | 7. ææä¸æå
å®¹ç´æ¥ä¿çï¼ä¸è¦è½¬ä¹æ Unicodeã |
| | | |
| | | æä»¶å
容: |
| | | %s |
| | | """.formatted(message, fileContent); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception { |
| | | if (payload.containsKey("purchaseLedgers")) { |
| | | return processPurchaseLedgerBatch(payload); |
| | | } |
| | | |
| | | Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, 0); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | return AjaxResult.success("éè´å°è´¦å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception { |
| | | List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers")); |
| | | if (purchaseLedgers.isEmpty()) { |
| | | return AjaxResult.error("purchaseLedgersä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData")); |
| | | List<Map<String, Object>> results = new ArrayList<>(); |
| | | for (int i = 0; i < purchaseLedgers.size(); i++) { |
| | | Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i)); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, i); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | |
| | | List<SalesLedgerProduct> products = dto.getProductData(); |
| | | if (products == null || products.isEmpty()) { |
| | | products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1); |
| | | dto.setProductData(products); |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(products, i); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("index", i); |
| | | item.put("purchaseContractNumber", dto.getPurchaseContractNumber()); |
| | | item.put("supplierId", dto.getSupplierId()); |
| | | item.put("supplierName", dto.getSupplierName()); |
| | | item.put("productCount", products.size()); |
| | | item.put("result", result); |
| | | results.add(item); |
| | | } |
| | | return AjaxResult.success("éè´å°è´¦å·²æ¹éå¤ç", results); |
| | | } |
| | | |
| | | private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap, |
| | | PurchaseLedgerDto dto, |
| | | List<Map<String, Object>> productData, |
| | | boolean onlyOneLedger) { |
| | | List<SalesLedgerProduct> products = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productData) { |
| | | if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) { |
| | | products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class)); |
| | | } |
| | | } |
| | | return products; |
| | | } |
| | | |
| | | private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) { |
| | | Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "éè´è®¢åid", "éè´å°è´¦id"); |
| | | if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | Long productSalesLedgerId = longValue(productMap, "salesLedgerId"); |
| | | if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(dto.getPurchaseContractNumber()) |
| | | && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(ledgerContractNo) |
| | | && productContractNo.trim().equals(ledgerContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(dto.getSalesContractNo()) |
| | | && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(ledgerSalesContractNo) |
| | | && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSupplierName = stringValue(productMap, "supplierName", "ä¾åºååç§°"); |
| | | return StringUtils.hasText(productSupplierName) |
| | | && StringUtils.hasText(dto.getSupplierName()) |
| | | && productSupplierName.trim().equals(dto.getSupplierName().trim()); |
| | | } |
| | | |
| | | private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copyPurchaseLedgerDtoFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "entryDateStart", "å½å
¥å¼å§æ¥æ", "å½å
¥æ¥æå¼å§"); |
| | | putDtoFieldIfPresent(source, target, "entryDateEnd", "å½å
¥ç»ææ¥æ", "å½å
¥æ¥æç»æ"); |
| | | putDtoFieldIfPresent(source, target, "id", "éè´å°è´¦id", "éè´è®¢åid", "主é®"); |
| | | putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "supplierId", "ä¾åºåid", "ä¾åºåID", "ä¾åºååç§°id", "ä¾åºååç§°ID"); |
| | | putDtoFieldIfPresent(source, target, "supplierName", "ä¾åºå", "ä¾åºååç§°"); |
| | | putDtoFieldIfPresent(source, target, "isWhite", "æ¯å¦ç½åå"); |
| | | putDtoFieldIfPresent(source, target, "recorderId", "å½å
¥äººid", "å½å
¥äººID", "å½å
¥äººå§åid", "å½å
¥äººå§åID"); |
| | | putDtoFieldIfPresent(source, target, "recorderName", "å½å
¥äºº", "å½å
¥äººå§å"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNoId", "éå®ååå·id", "éå®ååå·ID", "éå®åå·id", "éå®åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "projectName", "项ç®", "项ç®åç§°"); |
| | | putDtoFieldIfPresent(source, target, "entryDate", "å½å
¥æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "executionDate", "ç¾è®¢æ¥æ", "ååç¾è®¢æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "remarks", "夿³¨", "说æ"); |
| | | putDtoFieldIfPresent(source, target, "attachmentMaterials", "éä»¶ææ", "éä»¶ææè·¯å¾æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "createdAt", "å建æ¶é´", "è®°å½å建æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "updatedAt", "æ´æ°æ¶é´", "è®°å½æåæ´æ°æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "salesLedgerId", "éå®å°è´¦id", "éå®å°è´¦ID", "å
³èéå®å°è´¦ä¸»è¡¨ä¸»é®"); |
| | | putDtoFieldIfPresent(source, target, "hasChildren", "æ¯å¦æå级", "æ¯å¦ææç»"); |
| | | putDtoFieldIfPresent(source, target, "Type", "å°è´¦ç±»å", "ä¸å¡ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "productData", "products", "产åæç»", "éè´äº§åæç»"); |
| | | putDtoFieldIfPresent(source, target, "tempFileIds", "ä¸´æ¶æä»¶id", "ä¸´æ¶æä»¶ID", "ä¸´æ¶æä»¶ids"); |
| | | putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "éä»¶å表", "éå®å°è´¦éä»¶"); |
| | | putDtoFieldIfPresent(source, target, "phoneNumber", "ä¸å¡åææºå·", "ææºå·"); |
| | | putDtoFieldIfPresent(source, target, "businessPersonId", "ä¸å¡åid", "ä¸å¡åID"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID"); |
| | | putDtoFieldIfPresent(source, target, "invoiceNumber", "å票å·", "å票å·ç "); |
| | | putDtoFieldIfPresent(source, target, "invoiceAmount", "å票éé¢", "å票éé¢ï¼å
ï¼"); |
| | | putDtoFieldIfPresent(source, target, "ticketRegistrationId", "æ¥ç¥¨ç»è®°id", "æ¥ç¥¨ç»è®°ID"); |
| | | putDtoFieldIfPresent(source, target, "contractAmount", "ååéé¢", "ååéé¢ï¼äº§åå«ç¨æ»ä»·ï¼"); |
| | | putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "æªæ¥ç¥¨éé¢", "æªæ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "type", "æä»¶ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "paymentMethod", "仿¬¾æ¹å¼"); |
| | | putDtoFieldIfPresent(source, target, "approvalStatus", "审æ¹ç¶æ"); |
| | | putDtoFieldIfPresent(source, target, "templateName", "模æ¿åç§°"); |
| | | target.remove("approveUserIds"); |
| | | target.remove("approverId"); |
| | | normalizeNestedProductData(target); |
| | | attachImportStyleProductData(source, target); |
| | | if (target.get("type") == null) { |
| | | target.put("type", 2); |
| | | } |
| | | target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)); |
| | | normalizePurchaseLedgerDateFields(target); |
| | | return target; |
| | | } |
| | | |
| | | private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) { |
| | | if (target.get("productData") != null) { |
| | | return; |
| | | } |
| | | Map<String, Object> productMap = normalizeSalesLedgerProductMap(source); |
| | | if (hasImportStyleProductData(productMap)) { |
| | | target.put("productData", List.of(productMap)); |
| | | } |
| | | } |
| | | |
| | | private boolean hasImportStyleProductData(Map<String, Object> productMap) { |
| | | return hasMapText(productMap, "productCategory") |
| | | || hasMapText(productMap, "specificationModel") |
| | | || productMap.get("quantity") != null |
| | | || productMap.get("taxInclusiveUnitPrice") != null |
| | | || productMap.get("taxInclusiveTotalPrice") != null; |
| | | } |
| | | |
| | | private boolean hasMapText(Map<String, Object> map, String key) { |
| | | Object value = map.get(key); |
| | | return value != null && StringUtils.hasText(String.valueOf(value)); |
| | | } |
| | | |
| | | private void normalizeNestedProductData(Map<String, Object> target) { |
| | | Object productDataValue = target.get("productData"); |
| | | if (productDataValue == null) { |
| | | return; |
| | | } |
| | | List<Map<String, Object>> productMaps = toMapList(productDataValue); |
| | | List<Map<String, Object>> normalizedProducts = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productMaps) { |
| | | normalizedProducts.add(normalizeSalesLedgerProductMap(productMap)); |
| | | } |
| | | target.put("productData", normalizedProducts); |
| | | } |
| | | |
| | | private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copySalesLedgerProductFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "productCategory", "产å大类", "产ååç§°", "产å", "åå", "ç©æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "specificationModel", "è§æ ¼åå·", "åå·", "è§æ ¼", "产åè§æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "unit", "åä½"); |
| | | putDtoFieldIfPresent(source, target, "quantity", "æ°é", "éè´æ°é"); |
| | | putDtoFieldIfPresent(source, target, "taxRate", "ç¨ç"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "å«ç¨åä»·", "åä»·", "éè´åä»·", "å«ç¨ä»·æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "å«ç¨æ»ä»·", "æ»ä»·", "éè´éé¢", "éé¢", "ååéé¢"); |
| | | putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "ä¸å«ç¨æ»ä»·"); |
| | | putDtoFieldIfPresent(source, target, "invoiceType", "å票类å", "å票类å«"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID", "åå·id", "åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "isChecked", "æ¯å¦è´¨æ£", "æ¯å¦è´¨æ£éª", "è´¨æ£"); |
| | | putDtoFieldIfPresent(source, target, "type", "å°è´¦ç±»å"); |
| | | normalizeProductAmounts(target); |
| | | target.putIfAbsent("type", 2); |
| | | return target; |
| | | } |
| | | |
| | | private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] productFields = { |
| | | "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit", |
| | | "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum", |
| | | "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum", |
| | | "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate", |
| | | "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal", |
| | | "isChecked", "isProduction" |
| | | }; |
| | | for (String field : productFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void normalizeProductAmounts(Map<String, Object> target) { |
| | | BigDecimal quantity = decimalValue(target.get("quantity")); |
| | | BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice")); |
| | | BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) { |
| | | target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP)); |
| | | } |
| | | if (totalPrice == null && unitPrice != null && quantity != null) { |
| | | target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity)); |
| | | } |
| | | BigDecimal taxRate = decimalValue(target.get("taxRate")); |
| | | totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) { |
| | | BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP)); |
| | | target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP)); |
| | | } |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) { |
| | | if (products == null || products.isEmpty()) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < products.size(); i++) { |
| | | SalesLedgerProduct product = products.get(i); |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦ç第" + (i + 1) + "æ¡äº§å"; |
| | | if (!StringUtils.hasText(product.getProductCategory())) { |
| | | return AjaxResult.error(prefix + "缺å°äº§ååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getSpecificationModel())) { |
| | | return AjaxResult.error(prefix + "缺å°è§æ ¼åå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getUnit())) { |
| | | return AjaxResult.error(prefix + "缺å°åä½ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getQuantity() == null) { |
| | | return AjaxResult.error(prefix + "ç¼ºå°æ°é"); |
| | | } |
| | | if (product.getTaxInclusiveUnitPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getTaxInclusiveTotalPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) { |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦"; |
| | | if (!StringUtils.hasText(dto.getPurchaseContractNumber())) { |
| | | return AjaxResult.error(prefix + "缺å°éè´ååå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error(prefix + "缺å°ä¾åºååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void normalizePurchaseLedgerDateFields(Map<String, Object> target) { |
| | | normalizeDateField(target, "entryDate"); |
| | | normalizeDateField(target, "executionDate"); |
| | | normalizeDateField(target, "createdAt"); |
| | | normalizeDateField(target, "updatedAt"); |
| | | } |
| | | |
| | | private void normalizeDateField(Map<String, Object> target, String fieldName) { |
| | | Object value = target.get(fieldName); |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | String normalizedDate = normalizeDateValue(value); |
| | | if (StringUtils.hasText(normalizedDate)) { |
| | | target.put(fieldName, normalizedDate); |
| | | } |
| | | } |
| | | |
| | | private String normalizeDateValue(Object value) { |
| | | if (value instanceof Date date) { |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | if (value instanceof Number number) { |
| | | return LocalDate.of(1899, 12, 30) |
| | | .plusDays(number.longValue()) |
| | | .format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | |
| | | String text = String.valueOf(value).trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') { |
| | | return text.substring(0, 10); |
| | | } |
| | | |
| | | String normalizedText = text.replace("å¹´", "-") |
| | | .replace("æ", "-") |
| | | .replace("æ¥", "") |
| | | .replace(".", "-") |
| | | .replace("/", "-") |
| | | .trim(); |
| | | DateTimeFormatter[] formatters = { |
| | | DateTimeFormatter.ofPattern("yyyy-M-d"), |
| | | DateTimeFormatter.ofPattern("M-d-yyyy"), |
| | | DateTimeFormatter.ofPattern("M-d-yy") |
| | | }; |
| | | for (DateTimeFormatter formatter : formatters) { |
| | | try { |
| | | return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } catch (DateTimeParseException ignored) { |
| | | // Try the next supported input pattern. |
| | | } |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] dtoFields = { |
| | | "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber", |
| | | "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo", |
| | | "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials", |
| | | "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds", |
| | | "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber", |
| | | "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount", |
| | | "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName" |
| | | }; |
| | | for (String field : dtoFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) { |
| | | if (target.containsKey(dtoField) && target.get(dtoField) != null) { |
| | | return; |
| | | } |
| | | for (String alias : aliases) { |
| | | Object value = source.get(alias); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | target.put(dtoField, value); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> toMapList(Object value) { |
| | | if (value == null) { |
| | | return List.of(); |
| | | } |
| | | return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() { |
| | | }); |
| | | } |
| | | |
| | | private String stringValue(Map<String, Object> map, String... keys) { |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | return String.valueOf(value); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Long longValue(Map<String, Object> map, String... keys) { |
| | | String value = stringValue(map, keys); |
| | | if (!StringUtils.hasText(value)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return Long.parseLong(value.trim()); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Object value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | if (value instanceof BigDecimal decimal) { |
| | | return decimal; |
| | | } |
| | | if (value instanceof Number number) { |
| | | return new BigDecimal(String.valueOf(number)); |
| | | } |
| | | String text = String.valueOf(value) |
| | | .replace(",", "") |
| | | .replace("ï¼", "") |
| | | .replace("å
", "") |
| | | .replace("ï¿¥", "") |
| | | .trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return new BigDecimal(text); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String toCustomerMessage(Exception ex) { |
| | | String message = ex.getMessage(); |
| | | if (!StringUtils.hasText(message)) { |
| | | return "å¤ç失败ï¼è¯·æ£æ¥ç¡®è®¤æ°æ®åéè¯"; |
| | | } |
| | | if (message.contains("tax_inclusive_unit_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("tax_inclusive_total_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("entryDate")) { |
| | | return "å¤ç失败ï¼å½å
¥æ¥ææ ¼å¼ä¸æ£ç¡®ï¼è¯·ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30"; |
| | | } |
| | | if (message.contains("supplier")) { |
| | | return "å¤ç失败ï¼ä¾åºåä¿¡æ¯ä¸å®æ´ï¼è¯·ç¡®è®¤ä¾åºååç§°æä¾åºåID"; |
| | | } |
| | | if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) { |
| | | return "å¤ç失败ï¼ç¡®è®¤æ°æ®ä¸å®æ´ææ ¼å¼ä¸æ£ç¡®ï¼è¯·æ£æ¥å¿
å¡«åæ®µåéè¯"; |
| | | } |
| | | return "å¤ç失败ï¼" + message; |
| | | } |
| | | |
| | | private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) { |
| | | if (dto.getSupplierId() != null) { |
| | | return null; |
| | | } |
| | | if (!StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error("ä¾åºåIDä¸è½ä¸ºç©ºï¼æªè¯å«å°ä¾åºååç§°ï¼æ æ³èªå¨å¹é
ä¾åºåID"); |
| | | } |
| | | |
| | | SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>() |
| | | .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim()) |
| | | .last("limit 1")); |
| | | if (supplier == null) { |
| | | return AjaxResult.error("æªæ¾å°ä¾åºåï¼" + dto.getSupplierName() + "ï¼è¯·å
ç»´æ¤ä¾åºåææå¨éæ©ä¾åºåID"); |
| | | } |
| | | dto.setSupplierId(supplier.getId()); |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) { |
| | | PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class); |
| | | Boolean result = purchaseReturnOrdersService.add(dto); |
| | | return AjaxResult.success("éè´éè´§åå·²å¤ç", result); |
| | | } |
| | | } |
| | |
| | | return new LinkedList<>(); |
| | | } |
| | | List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId); |
| | | return messages.stream().map(this::convertMessage).collect(Collectors.toList()); |
| | | List<AiChatMessageDto> messageDtos = messages.stream().map(this::convertMessage).collect(Collectors.toList()); |
| | | List<List<String>> analyzeFilePathGroups = mongoChatMemoryStore.getAnalyzeFilePathGroups(memoryId); |
| | | attachAnalyzeFilePaths(messageDtos, analyzeFilePathGroups); |
| | | return messageDtos; |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | return new AiChatMessageDto("unknown", String.valueOf(message)); |
| | | } |
| | | |
| | | private void attachAnalyzeFilePaths(List<AiChatMessageDto> messages, |
| | | List<List<String>> analyzeFilePathGroups) { |
| | | if (StringUtils.isEmpty(messages) || StringUtils.isEmpty(analyzeFilePathGroups)) { |
| | | return; |
| | | } |
| | | int analyzeIndex = 0; |
| | | for (AiChatMessageDto message : messages) { |
| | | if (!"user".equals(message.getRole()) || analyzeIndex >= analyzeFilePathGroups.size()) { |
| | | continue; |
| | | } |
| | | List<String> filePaths = analyzeFilePathGroups.get(analyzeIndex); |
| | | if (!StringUtils.isEmpty(filePaths)) { |
| | | message.setFilePaths(filePaths); |
| | | } |
| | | analyzeIndex++; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.service.impl; |
| | | |
| | | import com.ruoyi.ai.service.KnowledgeRagService; |
| | | import com.ruoyi.approve.pojo.KnowledgeBaseVector; |
| | | import com.ruoyi.approve.service.KnowledgeBaseVectorService; |
| | | import com.ruoyi.basic.pojo.StorageBlob; |
| | | import com.ruoyi.basic.service.StorageBlobService; |
| | | import com.ruoyi.common.config.FileProperties; |
| | | import com.google.protobuf.Struct; |
| | | import com.google.protobuf.Value; |
| | | import dev.langchain4j.data.embedding.Embedding; |
| | | import dev.langchain4j.data.segment.TextSegment; |
| | | import dev.langchain4j.model.embedding.EmbeddingModel; |
| | | import dev.langchain4j.store.embedding.EmbeddingSearchRequest; |
| | | import dev.langchain4j.store.embedding.EmbeddingSearchResult; |
| | | import dev.langchain4j.store.embedding.EmbeddingStore; |
| | | import io.pinecone.clients.Index; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.io.File; |
| | | import java.nio.charset.Charset; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * ç¥è¯åºRAGæå¡å®ç° |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class KnowledgeRagServiceImpl implements KnowledgeRagService { |
| | | |
| | | private final KnowledgeBaseVectorService knowledgeBaseVectorService; |
| | | private final StorageBlobService storageBlobService; |
| | | private final EmbeddingModel embeddingModel; |
| | | private final EmbeddingStore<TextSegment> embeddingStore; |
| | | private final FileProperties fileProperties; |
| | | private final Index pineconeIndex; |
| | | |
| | | @org.springframework.beans.factory.annotation.Value("${pinecone.namespace:knowledge-base}") |
| | | private String namespace; |
| | | |
| | | public KnowledgeRagServiceImpl( |
| | | KnowledgeBaseVectorService knowledgeBaseVectorService, |
| | | StorageBlobService storageBlobService, |
| | | EmbeddingModel embeddingModel, |
| | | EmbeddingStore<TextSegment> embeddingStore, |
| | | FileProperties fileProperties, |
| | | Index pineconeIndex) { |
| | | this.knowledgeBaseVectorService = knowledgeBaseVectorService; |
| | | this.storageBlobService = storageBlobService; |
| | | this.embeddingModel = embeddingModel; |
| | | this.embeddingStore = embeddingStore; |
| | | this.fileProperties = fileProperties; |
| | | this.pineconeIndex = pineconeIndex; |
| | | } |
| | | |
| | | private static final int CHUNK_SIZE = 500; |
| | | private static final int CHUNK_OVERLAP = 100; |
| | | private static final long CHUNK_THRESHOLD_BYTES = 80L * 1024 * 1024; |
| | | private static final int EMBEDDING_MAX_LENGTH = 8000; |
| | | |
| | | @Override |
| | | @Async("threadPoolTaskExecutor") |
| | | public void processVectorAsync(Long vectorId) { |
| | | log.info("å¼å§å¼æ¥åéåå¤ç: vectorId={}, thread={}", vectorId, Thread.currentThread().getName()); |
| | | processVector(vectorId); |
| | | } |
| | | |
| | | @Override |
| | | public void processVector(Long vectorId) { |
| | | log.info("å¼å§å¤çåéå: vectorId={}", vectorId); |
| | | KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId); |
| | | if (vector == null) { |
| | | log.error("åéè®°å½ä¸åå¨: {}", vectorId); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | knowledgeBaseVectorService.updateVectorStatus(vectorId, |
| | | KnowledgeBaseVector.STATUS_PROCESSING, null, null); |
| | | |
| | | StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId()); |
| | | if (blob == null) { |
| | | throw new RuntimeException("æä»¶ä¸åå¨: " + vector.getStorageBlobId()); |
| | | } |
| | | |
| | | File file = getFile(blob); |
| | | log.info("æä»¶è·¯å¾: {}, æ¯å¦åå¨: {}", file.getAbsolutePath(), file.exists()); |
| | | long fileSize = file.length(); |
| | | |
| | | String content = extractFileContent(file, vector.getFileName()); |
| | | log.info("æä»¶å
容é¿åº¦: {}", content != null ? content.length() : 0); |
| | | |
| | | if (content == null || content.trim().isEmpty()) { |
| | | throw new RuntimeException("æä»¶å
容为空"); |
| | | } |
| | | |
| | | List<TextSegment> chunks; |
| | | boolean needChunk = fileSize > CHUNK_THRESHOLD_BYTES || content.length() > EMBEDDING_MAX_LENGTH; |
| | | if (needChunk) { |
| | | log.info("å¼å§åç: fileSize={}, contentLength={}", fileSize, content.length()); |
| | | chunks = splitText(content, vector); |
| | | log.info("åç宿ï¼å
± {} 个å", chunks.size()); |
| | | } else { |
| | | log.info("æä»¶è¾å°ï¼ä¸è¿è¡åç"); |
| | | Map<String, Object> metadata = buildMetadata(vector); |
| | | chunks = List.of(TextSegment.from(content, new dev.langchain4j.data.document.Metadata(metadata))); |
| | | } |
| | | |
| | | int chunkCount = 0; |
| | | for (TextSegment chunk : chunks) { |
| | | Embedding embedding = embeddingModel.embed(chunk).content(); |
| | | embeddingStore.add(embedding, chunk); |
| | | chunkCount++; |
| | | } |
| | | |
| | | knowledgeBaseVectorService.updateVectorStatus(vectorId, |
| | | KnowledgeBaseVector.STATUS_COMPLETED, chunkCount, null); |
| | | |
| | | log.info("åéåå¤ç宿: vectorId={}, chunkCount={}", vectorId, chunkCount); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("åéåå¤ç失败: vectorId={}", vectorId, e); |
| | | knowledgeBaseVectorService.updateVectorStatus(vectorId, |
| | | KnowledgeBaseVector.STATUS_FAILED, null, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<String> searchRelevantContent(String namespace, String query, int maxResults) { |
| | | try { |
| | | Embedding queryEmbedding = embeddingModel.embed(query).content(); |
| | | |
| | | EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() |
| | | .queryEmbedding(queryEmbedding) |
| | | .maxResults(maxResults) |
| | | .minScore(0.7) |
| | | .build(); |
| | | |
| | | EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest); |
| | | |
| | | return searchResult.matches().stream() |
| | | .map(match -> match.embedded().text()) |
| | | .collect(Collectors.toList()); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("åéæ£ç´¢å¤±è´¥: namespace={}", namespace, e); |
| | | return new ArrayList<>(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void deleteEmbeddings(String namespace, Long storageBlobId) { |
| | | log.info("å é¤åéæ°æ®: namespace={}, storageBlobId={}", namespace, storageBlobId); |
| | | try { |
| | | Struct filter = Struct.newBuilder() |
| | | .putFields("storageBlobId", Value.newBuilder() |
| | | .setStructValue(Struct.newBuilder() |
| | | .putFields("$eq", Value.newBuilder() |
| | | .setNumberValue(storageBlobId.doubleValue()) |
| | | .build())) |
| | | .build()) |
| | | .build(); |
| | | |
| | | List<String> emptyIds = new ArrayList<>(); |
| | | pineconeIndex.delete(emptyIds, false, this.namespace, filter); |
| | | log.info("åéå é¤å®æ: storageBlobId={}", storageBlobId); |
| | | } catch (Exception e) { |
| | | log.error("å é¤åéæ°æ®å¤±è´¥: namespace={}, storageBlobId={}", namespace, storageBlobId, e); |
| | | } |
| | | } |
| | | |
| | | private File getFile(StorageBlob blob) { |
| | | String path = blob.getPath(); |
| | | if (path != null && !path.isEmpty()) { |
| | | return new File(new File(fileProperties.getPath(), path), blob.getUidFilename()); |
| | | } |
| | | return new File(fileProperties.getPath(), blob.getUidFilename()); |
| | | } |
| | | |
| | | private String extractFileContent(File file, String fileName) throws Exception { |
| | | String ext = getFileExtension(fileName); |
| | | |
| | | if (isPlainText(ext)) { |
| | | return readFileWithEncoding(file); |
| | | } |
| | | |
| | | if ("docx".equals(ext)) { |
| | | return extractDocx(file); |
| | | } |
| | | |
| | | if ("xlsx".equals(ext)) { |
| | | return extractXlsx(file); |
| | | } |
| | | |
| | | if ("xls".equals(ext)) { |
| | | return extractXls(file); |
| | | } |
| | | |
| | | return readFileWithEncoding(file); |
| | | } |
| | | |
| | | private String readFileWithEncoding(File file) throws Exception { |
| | | byte[] bytes = Files.readAllBytes(file.toPath()); |
| | | |
| | | String utf8Content = new String(bytes, StandardCharsets.UTF_8); |
| | | if (isValidUtf8(utf8Content)) { |
| | | log.debug("æä»¶ç¼ç : UTF-8"); |
| | | return utf8Content; |
| | | } |
| | | |
| | | try { |
| | | Charset gbk = Charset.forName("GBK"); |
| | | String gbkContent = new String(bytes, gbk); |
| | | log.debug("æä»¶ç¼ç : GBK"); |
| | | return gbkContent; |
| | | } catch (Exception e) { |
| | | log.warn("ç¼ç æ£æµå¤±è´¥ï¼ä½¿ç¨ UTF-8"); |
| | | return utf8Content; |
| | | } |
| | | } |
| | | |
| | | private boolean isValidUtf8(String decoded) { |
| | | // æ£æ¥æ¿æ¢å符 U+FFFD (UTF-8 è§£ç 失败æ¶åºç°) |
| | | if (decoded.contains("�")) { |
| | | return false; |
| | | } |
| | | int invalidCount = 0; |
| | | int checkLen = Math.min(decoded.length(), 1000); |
| | | for (int i = 0; i < checkLen; i++) { |
| | | char c = decoded.charAt(i); |
| | | // æ£æ¥ç§æä½¿ç¨åºå (U+E000-U+F8FF) æå¼å¸¸æ§å¶å符 |
| | | if ((c >= 'î' && c <= '') || (c < ' ' && c != '\n' && c != '\r' && c != '\t')) { |
| | | invalidCount++; |
| | | } |
| | | } |
| | | return invalidCount < checkLen * 0.05; |
| | | } |
| | | |
| | | private String getFileExtension(String fileName) { |
| | | if (fileName == null || !fileName.contains(".")) { |
| | | return ""; |
| | | } |
| | | return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); |
| | | } |
| | | |
| | | private boolean isPlainText(String ext) { |
| | | return "txt".equals(ext) || "md".equals(ext) || "json".equals(ext) |
| | | || "csv".equals(ext) || "xml".equals(ext) || "yaml".equals(ext) |
| | | || "yml".equals(ext); |
| | | } |
| | | |
| | | private String extractDocx(File file) throws Exception { |
| | | try (var doc = new org.apache.poi.xwpf.usermodel.XWPFDocument(new java.io.FileInputStream(file)); |
| | | var extractor = new org.apache.poi.xwpf.extractor.XWPFWordExtractor(doc)) { |
| | | return extractor.getText(); |
| | | } |
| | | } |
| | | |
| | | private String extractXlsx(File file) throws Exception { |
| | | try (var workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook(file)) { |
| | | return extractWorkbook(workbook); |
| | | } |
| | | } |
| | | |
| | | private String extractXls(File file) throws Exception { |
| | | try (var workbook = new org.apache.poi.hssf.usermodel.HSSFWorkbook(new java.io.FileInputStream(file))) { |
| | | return extractWorkbook(workbook); |
| | | } |
| | | } |
| | | |
| | | private String extractWorkbook(org.apache.poi.ss.usermodel.Workbook workbook) { |
| | | StringBuilder text = new StringBuilder(); |
| | | var formatter = new org.apache.poi.ss.usermodel.DataFormatter(); |
| | | for (int i = 0; i < workbook.getNumberOfSheets(); i++) { |
| | | var sheet = workbook.getSheetAt(i); |
| | | text.append("Sheet: ").append(sheet.getSheetName()).append("\n"); |
| | | for (var row : sheet) { |
| | | for (var cell : row) { |
| | | text.append(formatter.formatCellValue(cell)).append("\t"); |
| | | } |
| | | text.append("\n"); |
| | | } |
| | | } |
| | | return text.toString(); |
| | | } |
| | | |
| | | private List<TextSegment> splitText(String content, KnowledgeBaseVector vector) { |
| | | List<TextSegment> chunks = new ArrayList<>(); |
| | | |
| | | if (content.length() <= CHUNK_SIZE) { |
| | | Map<String, Object> metadata = buildMetadata(vector); |
| | | chunks.add(TextSegment.from(content, new dev.langchain4j.data.document.Metadata(metadata))); |
| | | return chunks; |
| | | } |
| | | |
| | | int start = 0; |
| | | int chunkIndex = 0; |
| | | while (start < content.length()) { |
| | | int end = Math.min(start + CHUNK_SIZE, content.length()); |
| | | |
| | | if (end < content.length()) { |
| | | int lastPeriod = content.lastIndexOf('ã', end); |
| | | int lastNewline = content.lastIndexOf('\n', end); |
| | | int boundary = Math.max(lastPeriod, lastNewline); |
| | | if (boundary > start + CHUNK_SIZE / 2) { |
| | | end = boundary + 1; |
| | | } |
| | | } |
| | | |
| | | String chunkText = content.substring(start, end).trim(); |
| | | if (!chunkText.isEmpty()) { |
| | | Map<String, Object> metadata = buildMetadata(vector); |
| | | metadata.put("chunkIndex", chunkIndex); |
| | | chunks.add(TextSegment.from(chunkText, new dev.langchain4j.data.document.Metadata(metadata))); |
| | | chunkIndex++; |
| | | } |
| | | |
| | | start = end - CHUNK_OVERLAP; |
| | | if (start < 0) start = 0; |
| | | if (start >= content.length() - CHUNK_OVERLAP) break; |
| | | } |
| | | |
| | | return chunks; |
| | | } |
| | | |
| | | private Map<String, Object> buildMetadata(KnowledgeBaseVector vector) { |
| | | Map<String, Object> metadata = new HashMap<>(); |
| | | metadata.put("knowledgeBaseId", vector.getKnowledgeBaseId()); |
| | | metadata.put("storageBlobId", vector.getStorageBlobId()); |
| | | metadata.put("fileName", vector.getFileName()); |
| | | metadata.put("namespace", vector.getNamespace()); |
| | | return metadata; |
| | | } |
| | | } |
| | |
| | | import org.springframework.data.mongodb.core.query.Query; |
| | | import org.springframework.data.mongodb.core.query.Update; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.CollectionUtils; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.Date; |
| | | import java.util.LinkedList; |
| | |
| | | |
| | | @Override |
| | | public List<ChatMessage> getMessages(Object memoryId) { |
| | | Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId))); |
| | | ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class); |
| | | ChatMessages chatMessages = findChatMessages(memoryId); |
| | | if (chatMessages == null || chatMessages.getContent() == null) { |
| | | return new LinkedList<>(); |
| | | } |
| | |
| | | updateMessages(memoryId, messages); |
| | | } |
| | | |
| | | public void appendAnalyzeFileContext(Object memoryId, String userQuestion, List<String> filePaths) { |
| | | String memoryIdValue = memoryIdString(memoryId); |
| | | if (!StringUtils.hasText(memoryIdValue)) { |
| | | return; |
| | | } |
| | | List<String> validFilePaths = new LinkedList<>(); |
| | | if (!CollectionUtils.isEmpty(filePaths)) { |
| | | for (String filePath : filePaths) { |
| | | if (StringUtils.hasText(filePath)) { |
| | | validFilePaths.add(filePath); |
| | | } |
| | | } |
| | | } |
| | | if (!StringUtils.hasText(userQuestion) && validFilePaths.isEmpty()) { |
| | | return; |
| | | } |
| | | Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue)); |
| | | Update update = new Update(); |
| | | update.set("memoryId", memoryIdValue); |
| | | update.set("updateTime", new Date()); |
| | | update.setOnInsert("createTime", new Date()); |
| | | if (StringUtils.hasText(userQuestion)) { |
| | | update.push("analyzeUserQuestions", userQuestion); |
| | | } |
| | | if (!validFilePaths.isEmpty()) { |
| | | update.push("analyzeFilePaths").each(validFilePaths.toArray()); |
| | | update.push("analyzeFilePathGroups", validFilePaths); |
| | | } |
| | | mongoTemplate.upsert(query, update, ChatMessages.class); |
| | | } |
| | | |
| | | public List<String> getAnalyzeUserQuestions(Object memoryId) { |
| | | ChatMessages chatMessages = findChatMessages(memoryId); |
| | | if (chatMessages == null || CollectionUtils.isEmpty(chatMessages.getAnalyzeUserQuestions())) { |
| | | return new LinkedList<>(); |
| | | } |
| | | return new LinkedList<>(chatMessages.getAnalyzeUserQuestions()); |
| | | } |
| | | |
| | | public List<List<String>> getAnalyzeFilePathGroups(Object memoryId) { |
| | | ChatMessages chatMessages = findChatMessages(memoryId); |
| | | if (chatMessages == null) { |
| | | return new LinkedList<>(); |
| | | } |
| | | if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePathGroups())) { |
| | | if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePaths())) { |
| | | return new LinkedList<>(); |
| | | } |
| | | List<List<String>> fallback = new LinkedList<>(); |
| | | fallback.add(new LinkedList<>(chatMessages.getAnalyzeFilePaths())); |
| | | return fallback; |
| | | } |
| | | List<List<String>> groups = new LinkedList<>(); |
| | | for (List<String> group : chatMessages.getAnalyzeFilePathGroups()) { |
| | | if (CollectionUtils.isEmpty(group)) { |
| | | groups.add(new LinkedList<>()); |
| | | } else { |
| | | groups.add(new LinkedList<>(group)); |
| | | } |
| | | } |
| | | return groups; |
| | | } |
| | | |
| | | private String memoryIdString(Object memoryId) { |
| | | return memoryId == null ? "" : memoryId.toString(); |
| | | } |
| | | |
| | | private ChatMessages findChatMessages(Object memoryId) { |
| | | Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId))); |
| | | return mongoTemplate.findOne(query, ChatMessages.class); |
| | | } |
| | | } |
| | |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.YearMonth; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | |
| | | |
| | | private static final int DEFAULT_LIMIT = 10; |
| | | private static final int MAX_LIMIT = 20; |
| | | private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); |
| | | private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
| | | |
| | | private final ApproveProcessMapper approveProcessMapper; |
| | |
| | | @P(value = "审æ¹ç±»åç¼å·ï¼å¯ä¸ä¼ ", required = false) Integer approveType, |
| | | @P(value = "å
³é®åï¼å¯å¹é
æµç¨ç¼å·ãæ é¢ãç³è¯·äººãå½å审æ¹äºº", required = false) String keyword, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§20", required = false) Integer limit, |
| | | @P(value = "æ¥è¯¢èå´ï¼å¯éå¼ï¼relatedãapplicantãapproverï¼related 表示å½åç¨æ·ç¸å
³ï¼applicant 表示æåèµ·çï¼approver 表示å¾
æå¤çç", required = false) String scope) { |
| | | @P(value = "æ¥è¯¢èå´ï¼å¯éå¼ï¼relatedãapplicantãapproverï¼related 表示å½åç¨æ·ç¸å
³ï¼applicant 表示æåèµ·çï¼approver 表示å¾
æå¤çç", required = false) String scope, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦ ä»å¤©ãæ¬æãè¿30天ï¼å¯ä¸ä¼ ", required = false) String timeRange) { |
| | | |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | Long userId = loginUser.getUserId(); |
| | | Integer statusCode = parseStatus(status); |
| | | String normalizedScope = normalizeScope(scope); |
| | | boolean hasDateFilter = StringUtils.hasText(startDate) || StringUtils.hasText(endDate) || StringUtils.hasText(timeRange); |
| | | DateRange dateRange = hasDateFilter ? resolveDateRange(startDate, endDate, timeRange) : null; |
| | | |
| | | LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(ApproveProcess::getApproveDelete, 0); |
| | |
| | | } |
| | | } |
| | | |
| | | if (dateRange != null) { |
| | | wrapper.ge(ApproveProcess::getCreateTime, dateRange.start().atStartOfDay()) |
| | | .lt(ApproveProcess::getCreateTime, dateRange.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | |
| | | wrapper.orderByDesc(ApproveProcess::getCreateTime) |
| | | .last("limit " + normalizeLimit(limit)); |
| | | |
| | |
| | | "statusFilter", StringUtils.hasText(status) ? status : "all", |
| | | "approveType", approveType == null ? "" : approveType, |
| | | "keyword", keyword == null ? "" : keyword, |
| | | "scope", normalizedScope |
| | | "scope", normalizedScope, |
| | | "timeRange", dateRange == null ? "all" : dateRange.label(), |
| | | "startDate", dateRange == null ? "" : dateRange.start().toString(), |
| | | "endDate", dateRange == null ? "" : dateRange.end().toString() |
| | | ), |
| | | Map.of("columns", todoColumns(), "items", items), |
| | | Map.of()); |
| | |
| | | } |
| | | |
| | | private String formatDate(Date value) { |
| | | return value == null ? "" : DATE_FORMAT.format(value); |
| | | if (value == null) { |
| | | return ""; |
| | | } |
| | | return value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DATE_FORMATTER); |
| | | } |
| | | |
| | | private long countByStatus(List<ApproveProcess> processes, int status) { |
| | |
| | | |
| | | private Date parseDate(String dateText) { |
| | | try { |
| | | return DATE_FORMAT.parse(dateText); |
| | | } catch (ParseException e) { |
| | | LocalDate localDate = LocalDate.parse(dateText, DATE_FORMATTER); |
| | | return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } catch (Exception e) { |
| | | throw new IllegalArgumentException("æ¥ææ ¼å¼å¿
é¡»æ¯ yyyy-MM-dd"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.tools; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; |
| | | import com.ruoyi.account.mapper.AccountStatementMapper; |
| | | import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper; |
| | | import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; |
| | | import com.ruoyi.account.pojo.AccountStatement; |
| | | import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.account.service.impl.AccountingServiceImpl; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.basic.mapper.CustomerMapper; |
| | | import com.ruoyi.basic.mapper.ProductMapper; |
| | | import com.ruoyi.basic.mapper.ProductModelMapper; |
| | | import com.ruoyi.basic.mapper.SupplierManageMapper; |
| | | import com.ruoyi.basic.pojo.Customer; |
| | | import com.ruoyi.basic.pojo.Product; |
| | | import com.ruoyi.basic.pojo.ProductModel; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.device.mapper.DeviceLedgerMapper; |
| | | import com.ruoyi.device.mapper.DeviceRepairMapper; |
| | | import com.ruoyi.device.pojo.DeviceLedger; |
| | | import com.ruoyi.device.pojo.DeviceRepair; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper; |
| | | import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage; |
| | | import com.ruoyi.production.mapper.ProductionAccountMapper; |
| | | import com.ruoyi.production.mapper.ProductionOperationTaskMapper; |
| | | import com.ruoyi.production.mapper.ProductionOrderMapper; |
| | | import com.ruoyi.production.mapper.ProductionPlanMapper; |
| | | import com.ruoyi.production.mapper.ProductionProductMainMapper; |
| | | import com.ruoyi.production.mapper.ProductionProductOutputMapper; |
| | | import com.ruoyi.production.pojo.ProductionAccount; |
| | | import com.ruoyi.production.pojo.ProductionOperationTask; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionPlan; |
| | | import com.ruoyi.production.pojo.ProductionProductMain; |
| | | import com.ruoyi.production.pojo.ProductionProductOutput; |
| | | import com.ruoyi.sales.mapper.SalesLedgerMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | | import com.ruoyi.sales.pojo.SalesLedger; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import com.ruoyi.stock.mapper.StockInventoryMapper; |
| | | import com.ruoyi.stock.pojo.StockInventory; |
| | | import com.ruoyi.technology.mapper.TechnologyOperationMapper; |
| | | import com.ruoyi.technology.pojo.TechnologyOperation; |
| | | import dev.langchain4j.agent.tool.P; |
| | | import dev.langchain4j.agent.tool.Tool; |
| | | import dev.langchain4j.agent.tool.ToolMemoryId; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.YearMonth; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Component |
| | | public class FinancialAgentTools { |
| | | |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final Pattern RELATIVE_PATTERN = Pattern.compile("(è¿|æè¿)?\\s*(\\d+)\\s*(天|å¨|个æ|æ|å¹´)"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); |
| | | private static final int DEFAULT_LIMIT = 10; |
| | | private static final int MAX_LIMIT = 50; |
| | | |
| | | private static final BigDecimal DEFAULT_FALLBACK_MATERIAL_COST_RATE = new BigDecimal("0.60"); |
| | | private static final BigDecimal DEFAULT_LABOR_COST_RATE = new BigDecimal("0.15"); |
| | | private static final BigDecimal DEFAULT_OVERHEAD_COST_RATE = new BigDecimal("0.10"); |
| | | |
| | | private static final BigDecimal SME_RECEIVABLE_RISK_THRESHOLD = new BigDecimal("500000"); |
| | | private static final BigDecimal SME_INVENTORY_RISK_THRESHOLD = new BigDecimal("1000000"); |
| | | private static final BigDecimal SME_PROFIT_WARNING_RATE = new BigDecimal("0.08"); |
| | | |
| | | private final SalesLedgerMapper salesLedgerMapper; |
| | | private final SalesLedgerProductMapper salesLedgerProductMapper; |
| | | private final ProductionAccountMapper productionAccountMapper; |
| | | private final ProductionProductMainMapper productionProductMainMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final ProductionProductOutputMapper productionProductOutputMapper; |
| | | private final TechnologyOperationMapper technologyOperationMapper; |
| | | private final DeviceLedgerMapper deviceLedgerMapper; |
| | | private final DeviceRepairMapper deviceRepairMapper; |
| | | private final ProcurementRecordMapper procurementRecordMapper; |
| | | private final ProcurementRecordOutMapper procurementRecordOutMapper; |
| | | private final StockInventoryMapper stockInventoryMapper; |
| | | private final AccountSalesCollectionMapper accountSalesCollectionMapper; |
| | | private final AccountPurchasePaymentMapper accountPurchasePaymentMapper; |
| | | private final AccountStatementMapper accountStatementMapper; |
| | | private final CustomerMapper customerMapper; |
| | | private final SupplierManageMapper supplierManageMapper; |
| | | private final ProductModelMapper productModelMapper; |
| | | private final ProductMapper productMapper; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | |
| | | public FinancialAgentTools(SalesLedgerMapper salesLedgerMapper, |
| | | SalesLedgerProductMapper salesLedgerProductMapper, |
| | | ProductionAccountMapper productionAccountMapper, |
| | | ProductionProductMainMapper productionProductMainMapper, |
| | | ProductionOperationTaskMapper productionOperationTaskMapper, |
| | | ProductionOrderMapper productionOrderMapper, |
| | | ProductionPlanMapper productionPlanMapper, |
| | | ProductionProductOutputMapper productionProductOutputMapper, |
| | | TechnologyOperationMapper technologyOperationMapper, |
| | | DeviceLedgerMapper deviceLedgerMapper, |
| | | DeviceRepairMapper deviceRepairMapper, |
| | | ProcurementRecordMapper procurementRecordMapper, |
| | | ProcurementRecordOutMapper procurementRecordOutMapper, |
| | | StockInventoryMapper stockInventoryMapper, |
| | | AccountSalesCollectionMapper accountSalesCollectionMapper, |
| | | AccountPurchasePaymentMapper accountPurchasePaymentMapper, |
| | | AccountStatementMapper accountStatementMapper, |
| | | CustomerMapper customerMapper, |
| | | SupplierManageMapper supplierManageMapper, |
| | | ProductModelMapper productModelMapper, |
| | | ProductMapper productMapper, |
| | | AiSessionUserContext aiSessionUserContext) { |
| | | this.salesLedgerMapper = salesLedgerMapper; |
| | | this.salesLedgerProductMapper = salesLedgerProductMapper; |
| | | this.productionAccountMapper = productionAccountMapper; |
| | | this.productionProductMainMapper = productionProductMainMapper; |
| | | this.productionOperationTaskMapper = productionOperationTaskMapper; |
| | | this.productionOrderMapper = productionOrderMapper; |
| | | this.productionPlanMapper = productionPlanMapper; |
| | | this.productionProductOutputMapper = productionProductOutputMapper; |
| | | this.technologyOperationMapper = technologyOperationMapper; |
| | | this.deviceLedgerMapper = deviceLedgerMapper; |
| | | this.deviceRepairMapper = deviceRepairMapper; |
| | | this.procurementRecordMapper = procurementRecordMapper; |
| | | this.procurementRecordOutMapper = procurementRecordOutMapper; |
| | | this.stockInventoryMapper = stockInventoryMapper; |
| | | this.accountSalesCollectionMapper = accountSalesCollectionMapper; |
| | | this.accountPurchasePaymentMapper = accountPurchasePaymentMapper; |
| | | this.accountStatementMapper = accountStatementMapper; |
| | | this.customerMapper = customerMapper; |
| | | this.supplierManageMapper = supplierManageMapper; |
| | | this.productModelMapper = productModelMapper; |
| | | this.productMapper = productMapper; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | } |
| | | |
| | | @Tool(name = "è´¢å¡ç¥è¯æ£ç´¢", value = "æè´¢å¡ç»è¥é®é¢æ£ç´¢ä¸è´¢èåç¥è¯çæ®µä¸ææ å£å¾ï¼ä½ä¸ºRAGä¸ä¸æã") |
| | | public String retrieveFinancialKnowledge(@ToolMemoryId String memoryId, |
| | | @P(value = "é®é¢æå
³é®è¯ï¼ä¾å¦å©æ¶¦ä¸éãåºåå¨è½¬ãèµé缺å£") String question) { |
| | | List<KnowledgeDoc> knowledgeDocs = financeKnowledgeBase(); |
| | | String normalized = normalizeForMatch(question); |
| | | List<KnowledgeDoc> ranked = knowledgeDocs.stream() |
| | | .sorted(Comparator.comparingInt((KnowledgeDoc doc) -> keywordHitCount(doc.keywords(), normalized)).reversed()) |
| | | .filter(doc -> keywordHitCount(doc.keywords(), normalized) > 0 || !StringUtils.hasText(normalized)) |
| | | .limit(5) |
| | | .toList(); |
| | | |
| | | List<Map<String, Object>> items = ranked.stream().map(doc -> { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("topic", doc.topic()); |
| | | map.put("knowledge", doc.knowledge()); |
| | | map.put("relatedTables", doc.relatedTables()); |
| | | map.put("suggestedQuestions", doc.suggestedQuestions()); |
| | | return map; |
| | | }).toList(); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("question", safe(question)); |
| | | summary.put("hitCount", items.size()); |
| | | summary.put("retrievalMode", "keyword_rag"); |
| | | return jsonResponse(true, "financial_rag_knowledge", "å·²è¿åè´¢å¡ç¥è¯æ£ç´¢ç»æ", summary, Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | @Tool(name = "æºè½ææ¬æ ¸ç®", value = "èªå¨æ ¸ç®äº§åææ¬ãå·¥åºææ¬ãäººå·¥ææ¬ãè®¾å¤ææ§ãæææèä¸è®¢å婿¶¦ã") |
| | | public String calculateIntelligentCost(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦æ¬æãè¿30天", required = false) String timeRange, |
| | | @P(value = "å
³é®è¯ï¼å¯å¹é
ååå·/客æ·/项ç®", required = false) String keyword, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§50", required = false) Integer limit) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | if (loginUser == null) { |
| | | return jsonResponse(false, "financial_cost_accounting", "ç¨æ·ä¿¡æ¯è·å失败", Map.of(), Map.of(), Map.of()); |
| | | } |
| | | |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, "è¿30天"); |
| | | AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, keyword, limit); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("orderCount", bundle.orderMetrics().size()); |
| | | summary.put("totalRevenue", bundle.totalRevenue()); |
| | | summary.put("totalMaterialCost", bundle.totalMaterialCost()); |
| | | summary.put("totalLaborCost", bundle.totalLaborCost()); |
| | | summary.put("totalDepreciationCost", bundle.totalDepreciationCost()); |
| | | summary.put("totalScrapCost", bundle.totalScrapCost()); |
| | | summary.put("totalCost", bundle.totalCost()); |
| | | summary.put("totalProfit", bundle.totalProfit()); |
| | | summary.put("profitRate", toPercent(rate(bundle.totalProfit(), bundle.totalRevenue()))); |
| | | |
| | | List<Map<String, Object>> orderItems = bundle.orderMetrics().stream() |
| | | .map(this::toOrderCostItem) |
| | | .toList(); |
| | | List<Map<String, Object>> processItems = bundle.processCostRanking().entrySet().stream() |
| | | .map(entry -> { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("processName", entry.getKey()); |
| | | map.put("cost", entry.getValue()); |
| | | return map; |
| | | }).toList(); |
| | | |
| | | List<Map<String, Object>> topCustomerItems = buildCustomerProfitTop(bundle.orderMetrics(), 5); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("costCompositionPieOption", |
| | | buildCostCompositionPie(bundle.totalMaterialCost(), bundle.totalLaborCost(), bundle.totalDepreciationCost(), bundle.totalScrapCost())); |
| | | charts.put("orderProfitBarOption", buildOrderProfitBar(bundle.orderMetrics())); |
| | | charts.put("processCostBarOption", buildProcessCostBar(bundle.processCostRanking())); |
| | | |
| | | return jsonResponse(true, "financial_cost_accounting", "已宿æºè½ææ¬æ ¸ç®", summary, |
| | | Map.of( |
| | | "orders", orderItems, |
| | | "processCostRanking", processItems, |
| | | "topCustomers", topCustomerItems |
| | | ), |
| | | charts |
| | | ); |
| | | } |
| | | |
| | | @Tool(name = "订å婿¶¦åæ", value = "è¯å«ä½å©æ¶¦/äºæè®¢åï¼è¾åºåå åæåä¼å建议ã") |
| | | public String analyzeOrderProfit(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦æ¬æãè¿30天", required = false) String timeRange, |
| | | @P(value = "å
³é®è¯ï¼å¯å¹é
ååå·/客æ·/项ç®", required = false) String keyword, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§50", required = false) Integer limit) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | if (loginUser == null) { |
| | | return jsonResponse(false, "financial_order_profit_analysis", "ç¨æ·ä¿¡æ¯è·å失败", Map.of(), Map.of(), Map.of()); |
| | | } |
| | | |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, "è¿30天"); |
| | | AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, keyword, limit); |
| | | List<OrderProfitMetric> metrics = bundle.orderMetrics(); |
| | | |
| | | List<OrderProfitMetric> riskyOrders = metrics.stream() |
| | | .filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0 || item.profitRate().compareTo(SME_PROFIT_WARNING_RATE) < 0) |
| | | .sorted(Comparator.comparing(OrderProfitMetric::profitRate) |
| | | .thenComparing(OrderProfitMetric::profit)) |
| | | .toList(); |
| | | |
| | | Map<String, BigDecimal> customerProfitMap = new LinkedHashMap<>(); |
| | | for (OrderProfitMetric metric : metrics) { |
| | | customerProfitMap.merge(metric.customerName(), metric.profit(), BigDecimal::add); |
| | | } |
| | | Map.Entry<String, BigDecimal> topCustomer = customerProfitMap.entrySet().stream() |
| | | .max(Map.Entry.comparingByValue()) |
| | | .orElse(Map.entry("ææ æ°æ®", BigDecimal.ZERO)); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("orderCount", metrics.size()); |
| | | summary.put("lossOrderCount", metrics.stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count()); |
| | | summary.put("lowProfitOrderCount", riskyOrders.size()); |
| | | summary.put("avgProfitRate", toPercent(avgRate(metrics))); |
| | | summary.put("topCustomerByProfit", topCustomer.getKey()); |
| | | summary.put("topCustomerProfit", topCustomer.getValue()); |
| | | |
| | | List<Map<String, Object>> riskyItems = riskyOrders.stream() |
| | | .limit(normalizeLimit(limit)) |
| | | .map(this::toRiskOrderItem) |
| | | .toList(); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("profitDistributionOption", buildProfitDistributionBar(metrics)); |
| | | charts.put("lossOrderTrendOption", buildLossOrderTrendLine(metrics)); |
| | | charts.put("customerProfitTopOption", buildCustomerProfitBar(customerProfitMap)); |
| | | |
| | | return jsonResponse(true, "financial_order_profit_analysis", "å·²å®æè®¢å婿¶¦åæ", summary, |
| | | Map.of( |
| | | "riskOrders", riskyItems, |
| | | "allOrders", metrics.stream().map(this::toOrderCostItem).toList(), |
| | | "customerProfitTop", buildCustomerProfitTop(metrics, 10) |
| | | ), |
| | | charts |
| | | ); |
| | | } |
| | | |
| | | @Tool(name = "åºåèµéåæ", value = "åæåºå积åãåæ»åºåãèµéå ç¨ä¸å¨è½¬çã") |
| | | public String analyzeInventoryCapital(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦æ¬æãè¿30天", required = false) String timeRange, |
| | | @P(value = "å
³é®è¯ï¼å¯å¹é
产ååç§°/åå·", required = false) String keyword, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§50", required = false) Integer limit) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, "è¿30天"); |
| | | int finalLimit = normalizeLimit(limit); |
| | | |
| | | List<StockInventory> inventoryRows = queryStockInventory(loginUser); |
| | | if (inventoryRows.isEmpty()) { |
| | | return jsonResponse(true, "financial_inventory_capital_analysis", "å½åæ åºåæ°æ®", |
| | | rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of()); |
| | | } |
| | | |
| | | Set<Long> modelIds = inventoryRows.stream() |
| | | .map(StockInventory::getProductModelId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | Map<Long, ProductModel> productModelMap = queryProductModels(modelIds); |
| | | Map<Long, Product> productMap = queryProducts(productModelMap.values()); |
| | | Map<Long, BigDecimal> avgUnitCostByModelId = queryAverageUnitCostByModel(loginUser, modelIds); |
| | | OutboundStats outboundStats = queryOutboundStats(loginUser, modelIds, range); |
| | | |
| | | List<InventoryMetric> metrics = buildInventoryMetrics(inventoryRows, productModelMap, productMap, avgUnitCostByModelId, outboundStats) |
| | | .stream() |
| | | .filter(metric -> matchInventoryKeyword(metric, keyword)) |
| | | .sorted(Comparator.comparing(InventoryMetric::inventoryValue).reversed()) |
| | | .toList(); |
| | | |
| | | BigDecimal totalInventoryValue = metrics.stream().map(InventoryMetric::inventoryValue).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal stagnantValue = metrics.stream() |
| | | .filter(metric -> metric.stagnantDays() >= 90) |
| | | .map(InventoryMetric::inventoryValue) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | long stagnantCount = metrics.stream().filter(metric -> metric.stagnantDays() >= 90).count(); |
| | | long overstockCount = metrics.stream().filter(InventoryMetric::overstock).count(); |
| | | BigDecimal totalOutboundCost = outboundStats.totalOutboundCost(); |
| | | BigDecimal turnoverDays = totalOutboundCost.compareTo(BigDecimal.ZERO) > 0 |
| | | ? totalInventoryValue.multiply(BigDecimal.valueOf(daysBetween(range.start(), range.end()) + 1L)) |
| | | .divide(totalOutboundCost, 2, RoundingMode.HALF_UP) |
| | | : BigDecimal.ZERO; |
| | | |
| | | List<Map<String, Object>> items = metrics.stream() |
| | | .limit(finalLimit) |
| | | .map(this::toInventoryItem) |
| | | .toList(); |
| | | |
| | | Map<String, Object> summary = rangeSummary(range, metrics.size(), keyword); |
| | | summary.put("totalInventoryValue", totalInventoryValue); |
| | | summary.put("stagnantValue", stagnantValue); |
| | | summary.put("stagnantCount", stagnantCount); |
| | | summary.put("overstockCount", overstockCount); |
| | | summary.put("turnoverDays", turnoverDays); |
| | | summary.put("capitalOccupation", totalInventoryValue); |
| | | summary.put("totalOutboundCost", totalOutboundCost); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("inventoryValueTopOption", buildInventoryTopBar(metrics)); |
| | | charts.put("inventoryAgingPieOption", buildInventoryAgingPie(metrics)); |
| | | charts.put("inventoryTurnoverGauge", buildTurnoverGauge(turnoverDays)); |
| | | |
| | | return jsonResponse(true, "financial_inventory_capital_analysis", "已宿åºåèµéåæ", summary, Map.of("items", items), charts); |
| | | } |
| | | |
| | | @Tool(name = "åºæ¶åºä»ä¸ç°éæµé¢æµ", value = "颿µæªæ¥ç°éæµã忬¾é£é©ã仿¬¾ååä¸èµé缺å£ã") |
| | | public String forecastCashFlow(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦è¿90å¤©ãæ¬å¹´", required = false) String timeRange, |
| | | @P(value = "颿µæä»½æ°ï¼é»è®¤3ï¼æå¤§6", required = false) Integer forecastMonths) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, "è¿90天"); |
| | | int months = forecastMonths == null || forecastMonths <= 0 ? 3 : Math.min(forecastMonths, 6); |
| | | |
| | | List<AccountSalesCollection> collections = queryCollections(loginUser, range); |
| | | List<AccountPurchasePayment> payments = queryPayments(loginUser, range); |
| | | List<MonthlyCashFlow> monthlyActual = buildMonthlyCashFlow(range, collections, payments); |
| | | List<MonthlyCashFlow> monthlyForecast = forecastMonthlyCashFlow(monthlyActual, months); |
| | | |
| | | StatementSnapshot snapshot = buildStatementSnapshot(loginUser); |
| | | BigDecimal receivableTotal = snapshot.receivableTotal(); |
| | | BigDecimal payableTotal = snapshot.payableTotal(); |
| | | BigDecimal forecastNetSum = monthlyForecast.stream().map(MonthlyCashFlow::netFlow).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal coverage = receivableTotal.add(maxZero(forecastNetSum)); |
| | | BigDecimal fundGap = maxZero(payableTotal.subtract(coverage)); |
| | | |
| | | Map<String, String> customerNameMap = queryCustomerNameMap(snapshot.receivableTop().stream().map(StatementMetric::entityId).collect(Collectors.toSet())); |
| | | Map<String, String> supplierNameMap = querySupplierNameMap(snapshot.payableTop().stream().map(StatementMetric::entityId).collect(Collectors.toSet())); |
| | | |
| | | List<Map<String, Object>> receivableRiskItems = snapshot.receivableTop().stream().map(item -> toStatementRiskItem(item, customerNameMap, "customer")).toList(); |
| | | List<Map<String, Object>> payablePressureItems = snapshot.payableTop().stream().map(item -> toStatementRiskItem(item, supplierNameMap, "supplier")).toList(); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("actualIncomeTotal", collections.stream().map(AccountSalesCollection::getCollectionAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | summary.put("actualExpenseTotal", payments.stream().map(AccountPurchasePayment::getPaymentAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | summary.put("receivableBalance", receivableTotal); |
| | | summary.put("payableBalance", payableTotal); |
| | | summary.put("forecastNetSum", forecastNetSum); |
| | | summary.put("fundGap", fundGap); |
| | | summary.put("forecastMonths", months); |
| | | summary.put("collectionRiskLevel", riskLevelByAmount(receivableTotal)); |
| | | summary.put("paymentPressureLevel", riskLevelByAmount(payableTotal)); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("cashFlowTrendOption", buildCashflowTrend(monthlyActual, monthlyForecast)); |
| | | charts.put("receivablePayableBarOption", buildReceivablePayableBar(receivableTotal, payableTotal)); |
| | | charts.put("fundGapGaugeOption", buildFundGapGauge(fundGap)); |
| | | |
| | | return jsonResponse(true, "financial_cashflow_forecast", "å·²å®æåºæ¶åºä»ä¸ç°éæµé¢æµ", summary, |
| | | Map.of( |
| | | "actualMonthly", monthlyActual.stream().map(this::toMonthlyCashFlowItem).toList(), |
| | | "forecastMonthly", monthlyForecast.stream().map(this::toMonthlyCashFlowItem).toList(), |
| | | "receivableRiskTop", receivableRiskItems, |
| | | "payablePressureTop", payablePressureItems |
| | | ), |
| | | charts |
| | | ); |
| | | } |
| | | |
| | | @Tool(name = "ç»è¥å¼å¸¸é¢è¦", value = "è¯å«ææ¬å¼å¸¸ã婿¶¦å¼å¸¸ã忬¾å¼å¸¸ã订åé£é©ãåºåå¼å¸¸ã") |
| | | public String detectBusinessAnomalies(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦è¿30天", required = false) String timeRange, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§50", required = false) Integer limit) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, "è¿30天"); |
| | | int finalLimit = normalizeLimit(limit); |
| | | |
| | | AnalysisBundle currentBundle = buildOrderProfitBundle(loginUser, range, null, Math.max(finalLimit, 30)); |
| | | DateRange prevRange = previousSameLengthRange(range); |
| | | AnalysisBundle prevBundle = buildOrderProfitBundle(loginUser, prevRange, null, 50); |
| | | |
| | | BigDecimal currentCostRate = rate(currentBundle.totalCost(), currentBundle.totalRevenue()); |
| | | BigDecimal prevCostRate = rate(prevBundle.totalCost(), prevBundle.totalRevenue()); |
| | | BigDecimal costRateDiff = currentCostRate.subtract(prevCostRate); |
| | | |
| | | StatementSnapshot snapshot = buildStatementSnapshot(loginUser); |
| | | List<InventoryMetric> inventoryMetrics = buildInventoryMetrics( |
| | | queryStockInventory(loginUser), |
| | | queryProductModels(Collections.emptySet()), |
| | | Map.of(), |
| | | queryAverageUnitCostByModel(loginUser, Collections.emptySet()), |
| | | queryOutboundStats(loginUser, Collections.emptySet(), range) |
| | | ); |
| | | |
| | | List<Map<String, Object>> anomalyItems = new ArrayList<>(); |
| | | if (costRateDiff.compareTo(new BigDecimal("0.10")) > 0) { |
| | | anomalyItems.add(anomalyItem("high", "ææ¬å¼å¸¸", "å使¶å
¥ææ¬çè¾ä¸å¨æä¸åè¶
è¿10%", Map.of( |
| | | "currentCostRate", toPercent(currentCostRate), |
| | | "previousCostRate", toPercent(prevCostRate), |
| | | "delta", toPercent(costRateDiff) |
| | | ))); |
| | | } |
| | | long lossCount = currentBundle.orderMetrics().stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count(); |
| | | if (lossCount > 0) { |
| | | anomalyItems.add(anomalyItem("high", "婿¶¦å¼å¸¸", "æ£æµå°äºæè®¢å", Map.of("lossOrderCount", lossCount))); |
| | | } |
| | | if (snapshot.receivableTotal().compareTo(snapshot.payableTotal().multiply(new BigDecimal("1.2"))) > 0) { |
| | | anomalyItems.add(anomalyItem("medium", "忬¾å¼å¸¸", "åºæ¶ä½é¢æ¾èé«äºåºä»ï¼å款ååå大", Map.of( |
| | | "receivableBalance", snapshot.receivableTotal(), |
| | | "payableBalance", snapshot.payableTotal() |
| | | ))); |
| | | } |
| | | long overdueOrderCount = currentBundle.orderMetrics().stream() |
| | | .filter(item -> item.deliveryDate() != null && item.deliveryDate().isBefore(LocalDate.now()) && item.profitRate().compareTo(new BigDecimal("0.10")) < 0) |
| | | .count(); |
| | | if (overdueOrderCount > 0) { |
| | | anomalyItems.add(anomalyItem("medium", "订åé£é©", "åå¨ä½å©æ¶¦ä¸äº¤ä»å·²é¾æè®¢å", Map.of("overdueRiskOrderCount", overdueOrderCount))); |
| | | } |
| | | long stagnantCount = inventoryMetrics.stream().filter(item -> item.stagnantDays() >= 90).count(); |
| | | if (stagnantCount > 0) { |
| | | anomalyItems.add(anomalyItem("medium", "åºåå¼å¸¸", "åå¨è¶
è¿90天æªå¨è½¬åºå", Map.of("stagnantCount", stagnantCount))); |
| | | } |
| | | |
| | | List<Map<String, Object>> topAnomalies = anomalyItems.stream().limit(finalLimit).toList(); |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("anomalyCount", topAnomalies.size()); |
| | | summary.put("highRiskCount", topAnomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count()); |
| | | summary.put("mediumRiskCount", topAnomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count()); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("anomalyLevelPieOption", buildAnomalyLevelPie(topAnomalies)); |
| | | charts.put("anomalyTypeBarOption", buildAnomalyTypeBar(topAnomalies)); |
| | | return jsonResponse(true, "financial_business_anomaly_warning", "已宿ç»è¥å¼å¸¸é¢è¦åæ", summary, |
| | | Map.of("items", topAnomalies), charts); |
| | | } |
| | | |
| | | @Tool(name = "AIç»è¥é©¾é©¶è±", value = "宿¶å±ç¤ºäº§å¼ã婿¶¦ãåºåã忬¾ã设å¤å©ç¨çã订å婿¶¦ççæ ¸å¿ç»è¥ææ ã") |
| | | public String getBusinessCockpit(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦æ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, "æ¬æ"); |
| | | |
| | | AnalysisBundle profitBundle = buildOrderProfitBundle(loginUser, range, null, 30); |
| | | StatementSnapshot snapshot = buildStatementSnapshot(loginUser); |
| | | List<StockInventory> inventories = queryStockInventory(loginUser); |
| | | BigDecimal inventoryValue = estimateInventoryValue(loginUser, inventories); |
| | | |
| | | long deviceTotal = countDevices(loginUser); |
| | | long repairingCount = countRepairingDevices(loginUser); |
| | | BigDecimal deviceUtilization = deviceTotal > 0 |
| | | ? new BigDecimal(deviceTotal - repairingCount).multiply(ONE_HUNDRED).divide(new BigDecimal(deviceTotal), 2, RoundingMode.HALF_UP) |
| | | : BigDecimal.ZERO; |
| | | |
| | | BigDecimal outputValue = profitBundle.totalRevenue(); |
| | | BigDecimal profitRate = rate(profitBundle.totalProfit(), profitBundle.totalRevenue()); |
| | | BigDecimal collectionRate = snapshot.receivableTotal().compareTo(BigDecimal.ZERO) > 0 |
| | | ? ONE_HUNDRED.subtract(rate(snapshot.receivableTotal(), snapshot.receivableTotal().add(snapshot.payableTotal())).multiply(ONE_HUNDRED)) |
| | | : BigDecimal.ZERO; |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("outputValue", outputValue); |
| | | summary.put("profit", profitBundle.totalProfit()); |
| | | summary.put("profitRate", toPercent(profitRate)); |
| | | summary.put("inventoryValue", inventoryValue); |
| | | summary.put("receivableBalance", snapshot.receivableTotal()); |
| | | summary.put("payableBalance", snapshot.payableTotal()); |
| | | summary.put("collectionRate", toPercent(collectionRate.divide(ONE_HUNDRED, 4, RoundingMode.HALF_UP))); |
| | | summary.put("deviceUtilizationRate", deviceUtilization + "%"); |
| | | summary.put("orderProfitRate", toPercent(avgRate(profitBundle.orderMetrics()))); |
| | | |
| | | Map<String, Object> indicators = new LinkedHashMap<>(); |
| | | indicators.put("产å¼", outputValue); |
| | | indicators.put("婿¶¦", profitBundle.totalProfit()); |
| | | indicators.put("åºåèµéå ç¨", inventoryValue); |
| | | indicators.put("åºæ¶ä½é¢", snapshot.receivableTotal()); |
| | | indicators.put("设å¤å©ç¨ç", deviceUtilization + "%"); |
| | | indicators.put("订åå¹³å婿¶¦ç", toPercent(avgRate(profitBundle.orderMetrics()))); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("kpiCardData", indicators); |
| | | charts.put("profitTrendOption", buildOrderProfitBar(profitBundle.orderMetrics())); |
| | | charts.put("receivablePayableBarOption", buildReceivablePayableBar(snapshot.receivableTotal(), snapshot.payableTotal())); |
| | | charts.put("inventoryProfitGaugeOption", buildInventoryProfitGauge(inventoryValue, profitBundle.totalProfit())); |
| | | |
| | | return jsonResponse(true, "financial_business_cockpit", "å·²çæAIç»è¥é©¾é©¶è±æ°æ®", summary, |
| | | Map.of( |
| | | "orderProfitTop", profitBundle.orderMetrics().stream() |
| | | .sorted(Comparator.comparing(OrderProfitMetric::profit).reversed()) |
| | | .limit(10) |
| | | .map(this::toOrderCostItem) |
| | | .toList(), |
| | | "indicators", indicators |
| | | ), |
| | | charts |
| | | ); |
| | | } |
| | | |
| | | @Tool(name = "æ¥æ¥å¨æ¥èªå¨çæ", value = "èªå¨è¾åºç»è¥åææ¥æ¥/卿¥ä¸é£é©å»ºè®®ã") |
| | | public String generateOperationReport(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-dd", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-dd", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼å¦ä»å¤©ãæ¬å¨", required = false) String timeRange, |
| | | @P(value = "æ¥åç±»å daily/weekly", required = false) String reportType) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange, |
| | | "weekly".equalsIgnoreCase(reportType) ? "æ¬å¨" : "ä»å¤©"); |
| | | String type = "weekly".equalsIgnoreCase(reportType) ? "weekly" : "daily"; |
| | | |
| | | AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, null, 30); |
| | | StatementSnapshot snapshot = buildStatementSnapshot(loginUser); |
| | | BigDecimal inventoryValue = estimateInventoryValue(loginUser, queryStockInventory(loginUser)); |
| | | long lossCount = bundle.orderMetrics().stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count(); |
| | | |
| | | List<String> conclusions = new ArrayList<>(); |
| | | conclusions.add("è¥æ¶" + bundle.totalRevenue() + "ï¼å©æ¶¦" + bundle.totalProfit() + "ï¼å©æ¶¦ç" + toPercent(rate(bundle.totalProfit(), bundle.totalRevenue())) + "ã"); |
| | | conclusions.add("åºæ¶ä½é¢" + snapshot.receivableTotal() + "ï¼åºä»ä½é¢" + snapshot.payableTotal() + "ï¼åºåèµéå ç¨" + inventoryValue + "ã"); |
| | | if (lossCount > 0) { |
| | | conclusions.add("åç°äºæè®¢å" + lossCount + "个ï¼å»ºè®®ä¼å
夿 ¸æææèåå·¥åºäººå·¥æçã"); |
| | | } else { |
| | | conclusions.add("å½åæªåç°äºæè®¢åï¼å»ºè®®æç»è·è¸ªä½äº8%婿¶¦ç订åã"); |
| | | } |
| | | if (snapshot.receivableTotal().compareTo(snapshot.payableTotal()) > 0) { |
| | | conclusions.add("忬¾åååé«ï¼å»ºè®®é对é«åºæ¶å®¢æ·æ§è¡åå±å¬æ¶ä¸è´¦æä¼åã"); |
| | | } else { |
| | | conclusions.add("èµéåå坿§ï¼å»ºè®®ä¿æä»æ¬¾è®¡åä¸éè´èå¥èå¨ã"); |
| | | } |
| | | |
| | | List<Map<String, Object>> riskSuggestions = new ArrayList<>(); |
| | | if (lossCount > 0) { |
| | | riskSuggestions.add(riskSuggestion("婿¶¦é£é©", "é«", "夿 ¸äºæè®¢åBOMåå·¥åºå·¥èµå®é¢ï¼å¿
è¦æ¶è°æ´æ¥ä»·ä¸äº¤ä»èå¥ã")); |
| | | } |
| | | if (snapshot.receivableTotal().compareTo(SME_RECEIVABLE_RISK_THRESHOLD) > 0) { |
| | | riskSuggestions.add(riskSuggestion("忬¾é£é©", "ä¸", "å¯¹åºæ¶TOP客æ·å»ºç«å¨åº¦å款计åï¼å¹¶è®¾ç½®é¢è¦éå¼ã")); |
| | | } |
| | | if (inventoryValue.compareTo(SME_INVENTORY_RISK_THRESHOLD) > 0) { |
| | | riskSuggestions.add(riskSuggestion("åºåé£é©", "ä¸", "对é«éé¢åæ»åºåæ§è¡éä»·ãæ¿ä»£åç产æ¶èçç¥ã")); |
| | | } |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("reportType", type); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("orderCount", bundle.orderMetrics().size()); |
| | | summary.put("lossOrderCount", lossCount); |
| | | summary.put("riskSuggestionCount", riskSuggestions.size()); |
| | | |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | data.put("headline", "weekly".equals(type) ? "ç»è¥å¨æ¥" : "ç»è¥æ¥æ¥"); |
| | | data.put("conclusions", conclusions); |
| | | data.put("riskSuggestions", riskSuggestions); |
| | | data.put("orderProfitTop", bundle.orderMetrics().stream() |
| | | .sorted(Comparator.comparing(OrderProfitMetric::profitRate)) |
| | | .limit(10) |
| | | .map(this::toRiskOrderItem) |
| | | .toList()); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("reportProfitBarOption", buildOrderProfitBar(bundle.orderMetrics())); |
| | | charts.put("reportReceivablePayableOption", buildReceivablePayableBar(snapshot.receivableTotal(), snapshot.payableTotal())); |
| | | return jsonResponse(true, "financial_operation_report", "å·²èªå¨çæç»è¥åææ¥å", summary, data, charts); |
| | | } |
| | | |
| | | private AnalysisBundle buildOrderProfitBundle(LoginUser loginUser, DateRange range, String keyword, Integer limit) { |
| | | List<SalesLedger> ledgers = querySalesLedgers(loginUser, range, keyword, limit); |
| | | if (ledgers.isEmpty()) { |
| | | return AnalysisBundle.empty(); |
| | | } |
| | | |
| | | List<Long> ledgerIds = ledgers.stream().map(SalesLedger::getId).filter(Objects::nonNull).toList(); |
| | | List<SalesLedgerProduct> ledgerProducts = queryLedgerProducts(loginUser, ledgerIds); |
| | | Map<Long, List<SalesLedgerProduct>> productsByLedgerId = ledgerProducts.stream() |
| | | .collect(Collectors.groupingBy(SalesLedgerProduct::getSalesLedgerId)); |
| | | |
| | | MaterialCostResult materialCostResult = calculateMaterialCost(loginUser, range, ledgerProducts); |
| | | ProductionCostContext productionCostContext = calculateProductionCost(loginUser, range, ledgers, ledgerProducts, materialCostResult.avgUnitCostByModelId()); |
| | | BigDecimal totalDepreciation = calculateTotalDepreciation(loginUser); |
| | | |
| | | BigDecimal totalRevenue = ledgers.stream() |
| | | .map(SalesLedger::getContractAmount) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | Map<Long, BigDecimal> depreciationCostByLedger = allocateDepreciation(ledgers, totalDepreciation, totalRevenue); |
| | | |
| | | List<OrderProfitMetric> metrics = new ArrayList<>(); |
| | | for (SalesLedger ledger : ledgers) { |
| | | BigDecimal revenue = defaultDecimal(ledger.getContractAmount()); |
| | | BigDecimal materialCost = materialCostResult.materialCostByLedgerId().getOrDefault(ledger.getId(), fallbackMaterialCost(productsByLedgerId.get(ledger.getId()), revenue)); |
| | | BigDecimal laborCost = productionCostContext.laborCostByLedgerId().getOrDefault(ledger.getId(), BigDecimal.ZERO); |
| | | BigDecimal scrapCost = productionCostContext.scrapCostByLedgerId().getOrDefault(ledger.getId(), BigDecimal.ZERO); |
| | | BigDecimal depreciationCost = depreciationCostByLedger.getOrDefault(ledger.getId(), BigDecimal.ZERO); |
| | | BigDecimal totalCost = materialCost.add(laborCost).add(scrapCost).add(depreciationCost); |
| | | BigDecimal profit = revenue.subtract(totalCost); |
| | | BigDecimal profitRate = rate(profit, revenue); |
| | | String riskLevel = profit.compareTo(BigDecimal.ZERO) < 0 |
| | | ? "high" |
| | | : (profitRate.compareTo(new BigDecimal("0.08")) < 0 ? "medium" : "low"); |
| | | List<String> reasons = buildProfitReasons(revenue, materialCost, laborCost, scrapCost, profit, profitRate); |
| | | String suggestion = buildProfitSuggestion(riskLevel, reasons); |
| | | |
| | | metrics.add(new OrderProfitMetric( |
| | | ledger.getId(), |
| | | safe(ledger.getSalesContractNo()), |
| | | safe(ledger.getCustomerName()), |
| | | safe(ledger.getProjectName()), |
| | | toLocalDate(ledger.getEntryDate()), |
| | | ledger.getDeliveryDate(), |
| | | revenue, |
| | | materialCost, |
| | | laborCost, |
| | | depreciationCost, |
| | | scrapCost, |
| | | totalCost, |
| | | profit, |
| | | profitRate, |
| | | riskLevel, |
| | | reasons, |
| | | suggestion |
| | | )); |
| | | } |
| | | |
| | | metrics.sort(Comparator.comparing(OrderProfitMetric::entryDate, Comparator.nullsLast(Comparator.reverseOrder())) |
| | | .thenComparing(OrderProfitMetric::ledgerId, Comparator.nullsLast(Comparator.reverseOrder()))); |
| | | BigDecimal totalMaterialCost = metrics.stream().map(OrderProfitMetric::materialCost).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalLaborCost = metrics.stream().map(OrderProfitMetric::laborCost).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalScrapCost = metrics.stream().map(OrderProfitMetric::scrapCost).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalDepreciationCost = metrics.stream().map(OrderProfitMetric::depreciationCost).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalCost = metrics.stream().map(OrderProfitMetric::totalCost).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalProfit = metrics.stream().map(OrderProfitMetric::profit).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | |
| | | return new AnalysisBundle( |
| | | metrics, |
| | | productionCostContext.processCostRanking(), |
| | | totalRevenue, |
| | | totalMaterialCost, |
| | | totalLaborCost, |
| | | totalDepreciationCost, |
| | | totalScrapCost, |
| | | totalCost, |
| | | totalProfit |
| | | ); |
| | | } |
| | | |
| | | private MaterialCostResult calculateMaterialCost(LoginUser loginUser, DateRange range, List<SalesLedgerProduct> ledgerProducts) { |
| | | if (ledgerProducts.isEmpty()) { |
| | | return new MaterialCostResult(Map.of(), Map.of()); |
| | | } |
| | | List<Long> ledgerProductIds = ledgerProducts.stream().map(SalesLedgerProduct::getId).filter(Objects::nonNull).toList(); |
| | | Set<Long> productModelIds = ledgerProducts.stream().map(SalesLedgerProduct::getProductModelId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | Map<Long, Long> productIdToLedgerId = ledgerProducts.stream() |
| | | .filter(item -> item.getId() != null && item.getSalesLedgerId() != null) |
| | | .collect(Collectors.toMap(SalesLedgerProduct::getId, SalesLedgerProduct::getSalesLedgerId, (a, b) -> a)); |
| | | |
| | | Map<Long, BigDecimal> avgUnitCostByModelId = queryAverageUnitCostByModel(loginUser, productModelIds); |
| | | LambdaQueryWrapper<ProcurementRecordOut> outWrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(outWrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId); |
| | | applyDeptFilter(outWrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId); |
| | | outWrapper.eq(ProcurementRecordOut::getType, 2) |
| | | .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds); |
| | | if (range.hasDateFilter()) { |
| | | outWrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(outWrapper)); |
| | | |
| | | Set<Integer> storageIds = outList.stream() |
| | | .map(ProcurementRecordOut::getProcurementRecordStorageId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | Map<Integer, ProcurementRecordStorage> storageMap = storageIds.isEmpty() |
| | | ? Map.of() |
| | | : defaultList(procurementRecordMapper.selectBatchIds(storageIds)).stream() |
| | | .collect(Collectors.toMap(ProcurementRecordStorage::getId, item -> item, (a, b) -> a)); |
| | | |
| | | Map<Long, BigDecimal> materialCostByLedgerId = new HashMap<>(); |
| | | for (ProcurementRecordOut out : outList) { |
| | | Long ledgerId = productIdToLedgerId.get(out.getSalesLedgerProductId()); |
| | | if (ledgerId == null) { |
| | | continue; |
| | | } |
| | | ProcurementRecordStorage storage = storageMap.get(out.getProcurementRecordStorageId()); |
| | | BigDecimal unitPrice = storage == null ? BigDecimal.ZERO : defaultDecimal(storage.getUnitPrice()); |
| | | BigDecimal quantity = defaultDecimal(out.getInboundNum()); |
| | | BigDecimal cost = quantity.multiply(unitPrice); |
| | | materialCostByLedgerId.merge(ledgerId, cost, BigDecimal::add); |
| | | } |
| | | return new MaterialCostResult(materialCostByLedgerId, avgUnitCostByModelId); |
| | | } |
| | | |
| | | private ProductionCostContext calculateProductionCost(LoginUser loginUser, |
| | | DateRange range, |
| | | List<SalesLedger> ledgers, |
| | | List<SalesLedgerProduct> ledgerProducts, |
| | | Map<Long, BigDecimal> avgUnitCostByModelId) { |
| | | if (ledgers.isEmpty()) { |
| | | return ProductionCostContext.empty(); |
| | | } |
| | | |
| | | Set<Long> ledgerIds = ledgers.stream().map(SalesLedger::getId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | Map<Long, Set<Long>> productModelToLedgerIds = new HashMap<>(); |
| | | for (SalesLedgerProduct product : ledgerProducts) { |
| | | if (product.getProductModelId() == null || product.getSalesLedgerId() == null) { |
| | | continue; |
| | | } |
| | | productModelToLedgerIds.computeIfAbsent(product.getProductModelId(), key -> new HashSet<>()).add(product.getSalesLedgerId()); |
| | | } |
| | | |
| | | LambdaQueryWrapper<ProductionPlan> planWrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(planWrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | planWrapper.in(ProductionPlan::getSalesLedgerId, ledgerIds); |
| | | List<ProductionPlan> plans = defaultList(productionPlanMapper.selectList(planWrapper)); |
| | | Map<Long, Long> planIdToLedgerId = plans.stream() |
| | | .filter(item -> item.getId() != null && item.getSalesLedgerId() != null) |
| | | .collect(Collectors.toMap(ProductionPlan::getId, ProductionPlan::getSalesLedgerId, (a, b) -> a)); |
| | | |
| | | LambdaQueryWrapper<ProductionOrder> orderWrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(orderWrapper, loginUser.getCurrentDeptId(), ProductionOrder::getDeptId); |
| | | if (range.hasDateFilter()) { |
| | | orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2)) |
| | | .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1)); |
| | | } |
| | | List<ProductionOrder> orders = defaultList(productionOrderMapper.selectList(orderWrapper)); |
| | | |
| | | Map<Long, Set<Long>> orderIdToLedgerIds = new HashMap<>(); |
| | | for (ProductionOrder order : orders) { |
| | | Set<Long> orderLedgers = new HashSet<>(); |
| | | for (Long planId : parseIdList(order.getProductionPlanIds())) { |
| | | Long ledgerId = planIdToLedgerId.get(planId); |
| | | if (ledgerId != null) { |
| | | orderLedgers.add(ledgerId); |
| | | } |
| | | } |
| | | if (orderLedgers.isEmpty() && order.getProductModelId() != null) { |
| | | orderLedgers.addAll(productModelToLedgerIds.getOrDefault(order.getProductModelId(), Set.of())); |
| | | } |
| | | if (!orderLedgers.isEmpty()) { |
| | | orderIdToLedgerIds.put(order.getId(), orderLedgers); |
| | | } |
| | | } |
| | | if (orderIdToLedgerIds.isEmpty()) { |
| | | return ProductionCostContext.empty(); |
| | | } |
| | | |
| | | LambdaQueryWrapper<ProductionOperationTask> taskWrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(taskWrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | taskWrapper.in(ProductionOperationTask::getProductionOrderId, orderIdToLedgerIds.keySet()); |
| | | List<ProductionOperationTask> tasks = defaultList(productionOperationTaskMapper.selectList(taskWrapper)); |
| | | Map<Long, Long> taskIdToOrderId = tasks.stream() |
| | | .filter(item -> item.getId() != null && item.getProductionOrderId() != null) |
| | | .collect(Collectors.toMap(ProductionOperationTask::getId, ProductionOperationTask::getProductionOrderId, (a, b) -> a)); |
| | | if (taskIdToOrderId.isEmpty()) { |
| | | return ProductionCostContext.empty(); |
| | | } |
| | | |
| | | LambdaQueryWrapper<ProductionProductMain> mainWrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(mainWrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId); |
| | | mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet()); |
| | | if (range.hasDateFilter()) { |
| | | mainWrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2)) |
| | | .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1)); |
| | | } |
| | | List<ProductionProductMain> mainList = defaultList(productionProductMainMapper.selectList(mainWrapper)); |
| | | Map<Long, Set<Long>> mainIdToLedgers = new HashMap<>(); |
| | | for (ProductionProductMain main : mainList) { |
| | | Long orderId = taskIdToOrderId.get(main.getProductionOperationTaskId()); |
| | | if (orderId == null) { |
| | | continue; |
| | | } |
| | | Set<Long> ledgerSet = orderIdToLedgerIds.get(orderId); |
| | | if (ledgerSet == null || ledgerSet.isEmpty()) { |
| | | continue; |
| | | } |
| | | mainIdToLedgers.put(main.getId(), ledgerSet); |
| | | } |
| | | if (mainIdToLedgers.isEmpty()) { |
| | | return ProductionCostContext.empty(); |
| | | } |
| | | |
| | | LambdaQueryWrapper<ProductionAccount> accountWrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(accountWrapper, loginUser.getCurrentDeptId(), ProductionAccount::getDeptId); |
| | | accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet()); |
| | | if (range.hasDateFilter()) { |
| | | accountWrapper.ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay()) |
| | | .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | List<ProductionAccount> accountList = defaultList(productionAccountMapper.selectList(accountWrapper)); |
| | | |
| | | Map<String, BigDecimal> salaryQuotaByOperation = defaultList(technologyOperationMapper.selectList(new LambdaQueryWrapper<TechnologyOperation>() |
| | | .select(TechnologyOperation::getName, TechnologyOperation::getSalaryQuota))) |
| | | .stream() |
| | | .filter(item -> StringUtils.hasText(item.getName())) |
| | | .collect(Collectors.toMap(TechnologyOperation::getName, item -> defaultDecimal(item.getSalaryQuota()), (a, b) -> a)); |
| | | |
| | | Map<Long, BigDecimal> laborCostByLedger = new HashMap<>(); |
| | | Map<String, BigDecimal> processCostMap = new HashMap<>(); |
| | | for (ProductionAccount account : accountList) { |
| | | Set<Long> ledgerSet = mainIdToLedgers.get(account.getProductionProductMainId()); |
| | | if (ledgerSet == null || ledgerSet.isEmpty()) { |
| | | continue; |
| | | } |
| | | BigDecimal cost = estimateLaborCost(account, salaryQuotaByOperation); |
| | | if (cost.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | BigDecimal split = cost.divide(new BigDecimal(ledgerSet.size()), 4, RoundingMode.HALF_UP); |
| | | for (Long ledgerId : ledgerSet) { |
| | | laborCostByLedger.merge(ledgerId, split, BigDecimal::add); |
| | | } |
| | | processCostMap.merge(safe(account.getTechnologyOperationName()), cost, BigDecimal::add); |
| | | } |
| | | |
| | | LambdaQueryWrapper<ProductionProductOutput> outputWrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(outputWrapper, loginUser.getCurrentDeptId(), ProductionProductOutput::getDeptId); |
| | | outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet()); |
| | | if (range.hasDateFilter()) { |
| | | outputWrapper.ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | List<ProductionProductOutput> outputList = defaultList(productionProductOutputMapper.selectList(outputWrapper)); |
| | | Map<Long, BigDecimal> scrapCostByLedger = new HashMap<>(); |
| | | for (ProductionProductOutput output : outputList) { |
| | | Set<Long> ledgerSet = mainIdToLedgers.get(output.getProductionProductMainId()); |
| | | if (ledgerSet == null || ledgerSet.isEmpty()) { |
| | | continue; |
| | | } |
| | | BigDecimal scrapQty = defaultDecimal(output.getScrapQty()); |
| | | if (scrapQty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | BigDecimal unitCost = avgUnitCostByModelId.getOrDefault(output.getProductModelId(), BigDecimal.ZERO); |
| | | BigDecimal scrapCost = scrapQty.multiply(unitCost); |
| | | BigDecimal split = scrapCost.divide(new BigDecimal(ledgerSet.size()), 4, RoundingMode.HALF_UP); |
| | | for (Long ledgerId : ledgerSet) { |
| | | scrapCostByLedger.merge(ledgerId, split, BigDecimal::add); |
| | | } |
| | | } |
| | | |
| | | Map<String, BigDecimal> processCostRanking = processCostMap.entrySet().stream() |
| | | .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed()) |
| | | .limit(10) |
| | | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new)); |
| | | |
| | | return new ProductionCostContext(laborCostByLedger, scrapCostByLedger, processCostRanking); |
| | | } |
| | | |
| | | private List<SalesLedger> querySalesLedgers(LoginUser loginUser, DateRange range, String keyword, Integer limit) { |
| | | LambdaQueryWrapper<SalesLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), SalesLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedger::getDeptId); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(SalesLedger::getSalesContractNo, keyword) |
| | | .or().like(SalesLedger::getCustomerContractNo, keyword) |
| | | .or().like(SalesLedger::getCustomerName, keyword) |
| | | .or().like(SalesLedger::getProjectName, keyword) |
| | | .or().like(SalesLedger::getSalesman, keyword)); |
| | | } |
| | | if (range.hasDateFilter()) { |
| | | wrapper.ge(SalesLedger::getEntryDate, toDate(range.start())) |
| | | .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end())); |
| | | } |
| | | wrapper.orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId); |
| | | if (limit != null && limit > 0) { |
| | | wrapper.last("limit " + normalizeLimit(limit)); |
| | | } |
| | | return defaultList(salesLedgerMapper.selectList(wrapper)); |
| | | } |
| | | |
| | | private List<SalesLedgerProduct> queryLedgerProducts(LoginUser loginUser, List<Long> ledgerIds) { |
| | | if (ledgerIds == null || ledgerIds.isEmpty()) { |
| | | return List.of(); |
| | | } |
| | | LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedgerProduct::getDeptId); |
| | | wrapper.in(SalesLedgerProduct::getSalesLedgerId, ledgerIds) |
| | | .eq(SalesLedgerProduct::getType, 1); |
| | | return defaultList(salesLedgerProductMapper.selectList(wrapper)); |
| | | } |
| | | |
| | | private List<StockInventory> queryStockInventory(LoginUser loginUser) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | return defaultList(stockInventoryMapper.selectList(wrapper)); |
| | | } |
| | | |
| | | private Map<Long, ProductModel> queryProductModels(Set<Long> modelIds) { |
| | | if (modelIds == null || modelIds.isEmpty()) { |
| | | return defaultList(productModelMapper.selectList(null)).stream() |
| | | .filter(item -> item.getId() != null) |
| | | .collect(Collectors.toMap(ProductModel::getId, item -> item, (a, b) -> a)); |
| | | } |
| | | LambdaQueryWrapper<ProductModel> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.in(ProductModel::getId, modelIds); |
| | | return defaultList(productModelMapper.selectList(wrapper)).stream() |
| | | .filter(item -> item.getId() != null) |
| | | .collect(Collectors.toMap(ProductModel::getId, item -> item, (a, b) -> a)); |
| | | } |
| | | |
| | | private Map<Long, Product> queryProducts(Collection<ProductModel> models) { |
| | | Set<Long> productIds = models == null ? Set.of() : models.stream() |
| | | .map(ProductModel::getProductId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | if (productIds.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.in(Product::getId, productIds); |
| | | return defaultList(productMapper.selectList(wrapper)).stream() |
| | | .filter(item -> item.getId() != null) |
| | | .collect(Collectors.toMap(Product::getId, item -> item, (a, b) -> a)); |
| | | } |
| | | |
| | | private Map<Long, BigDecimal> queryAverageUnitCostByModel(LoginUser loginUser, Set<Long> productModelIds) { |
| | | LambdaQueryWrapper<ProcurementRecordStorage> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementRecordStorage::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementRecordStorage::getDeptId); |
| | | wrapper.in(ProcurementRecordStorage::getType, List.of(1, 2)); |
| | | if (productModelIds != null && !productModelIds.isEmpty()) { |
| | | wrapper.in(ProcurementRecordStorage::getProductModelId, productModelIds); |
| | | } |
| | | List<ProcurementRecordStorage> rows = defaultList(procurementRecordMapper.selectList(wrapper)); |
| | | Map<Long, BigDecimal> amountByModel = new HashMap<>(); |
| | | Map<Long, BigDecimal> qtyByModel = new HashMap<>(); |
| | | for (ProcurementRecordStorage row : rows) { |
| | | if (row.getProductModelId() == null) { |
| | | continue; |
| | | } |
| | | BigDecimal qty = defaultDecimal(row.getInboundNum()); |
| | | if (qty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | BigDecimal amount = defaultDecimal(row.getUnitPrice()).multiply(qty); |
| | | amountByModel.merge(row.getProductModelId(), amount, BigDecimal::add); |
| | | qtyByModel.merge(row.getProductModelId(), qty, BigDecimal::add); |
| | | } |
| | | Map<Long, BigDecimal> result = new HashMap<>(); |
| | | for (Map.Entry<Long, BigDecimal> entry : amountByModel.entrySet()) { |
| | | BigDecimal qty = qtyByModel.get(entry.getKey()); |
| | | if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | result.put(entry.getKey(), entry.getValue().divide(qty, 6, RoundingMode.HALF_UP)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private OutboundStats queryOutboundStats(LoginUser loginUser, Set<Long> productModelIds, DateRange range) { |
| | | LambdaQueryWrapper<ProcurementRecordOut> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId); |
| | | if (productModelIds != null && !productModelIds.isEmpty()) { |
| | | wrapper.in(ProcurementRecordOut::getProductModelId, productModelIds); |
| | | } |
| | | if (range.hasDateFilter()) { |
| | | wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(wrapper)); |
| | | if (outList.isEmpty()) { |
| | | return OutboundStats.empty(); |
| | | } |
| | | Set<Integer> storageIds = outList.stream() |
| | | .map(ProcurementRecordOut::getProcurementRecordStorageId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | Map<Integer, ProcurementRecordStorage> storageMap = storageIds.isEmpty() |
| | | ? Map.of() |
| | | : defaultList(procurementRecordMapper.selectBatchIds(storageIds)).stream() |
| | | .collect(Collectors.toMap(ProcurementRecordStorage::getId, item -> item, (a, b) -> a)); |
| | | |
| | | Map<Long, BigDecimal> outboundQtyByModel = new HashMap<>(); |
| | | Map<Long, LocalDateTime> lastOutboundTimeByModel = new HashMap<>(); |
| | | BigDecimal totalOutboundCost = BigDecimal.ZERO; |
| | | for (ProcurementRecordOut out : outList) { |
| | | Long modelId = out.getProductModelId(); |
| | | if (modelId == null) { |
| | | continue; |
| | | } |
| | | BigDecimal qty = defaultDecimal(out.getInboundNum()); |
| | | outboundQtyByModel.merge(modelId, qty, BigDecimal::add); |
| | | if (out.getCreateTime() != null) { |
| | | LocalDateTime existing = lastOutboundTimeByModel.get(modelId); |
| | | if (existing == null || out.getCreateTime().isAfter(existing)) { |
| | | lastOutboundTimeByModel.put(modelId, out.getCreateTime()); |
| | | } |
| | | } |
| | | ProcurementRecordStorage storage = storageMap.get(out.getProcurementRecordStorageId()); |
| | | BigDecimal unitPrice = storage == null ? BigDecimal.ZERO : defaultDecimal(storage.getUnitPrice()); |
| | | totalOutboundCost = totalOutboundCost.add(unitPrice.multiply(qty)); |
| | | } |
| | | return new OutboundStats(outboundQtyByModel, lastOutboundTimeByModel, totalOutboundCost); |
| | | } |
| | | |
| | | private List<InventoryMetric> buildInventoryMetrics(List<StockInventory> inventoryRows, |
| | | Map<Long, ProductModel> productModelMap, |
| | | Map<Long, Product> productMap, |
| | | Map<Long, BigDecimal> avgUnitCostByModelId, |
| | | OutboundStats outboundStats) { |
| | | Map<Long, InventoryMetricBuilder> metricBuilderByModel = new HashMap<>(); |
| | | for (StockInventory row : inventoryRows) { |
| | | if (row.getProductModelId() == null) { |
| | | continue; |
| | | } |
| | | InventoryMetricBuilder builder = metricBuilderByModel.computeIfAbsent(row.getProductModelId(), InventoryMetricBuilder::new); |
| | | builder.addQuantity(maxZero(defaultDecimal(row.getQualitity()).subtract(defaultDecimal(row.getLockedQuantity())))); |
| | | builder.addLockedQuantity(defaultDecimal(row.getLockedQuantity())); |
| | | builder.addWarnNum(defaultDecimal(row.getWarnNum())); |
| | | if (row.getCreateTime() != null) { |
| | | builder.updateFirstInTime(row.getCreateTime()); |
| | | } |
| | | } |
| | | |
| | | List<InventoryMetric> result = new ArrayList<>(); |
| | | LocalDate today = LocalDate.now(); |
| | | for (InventoryMetricBuilder builder : metricBuilderByModel.values()) { |
| | | Long modelId = builder.modelId(); |
| | | ProductModel model = productModelMap.get(modelId); |
| | | Product product = model == null ? null : productMap.get(model.getProductId()); |
| | | BigDecimal unitCost = avgUnitCostByModelId.getOrDefault(modelId, BigDecimal.ZERO); |
| | | BigDecimal value = builder.quantity().multiply(unitCost); |
| | | LocalDateTime lastOutTime = outboundStats.lastOutboundTimeByModel().get(modelId); |
| | | long stagnantDays; |
| | | if (lastOutTime != null) { |
| | | stagnantDays = daysBetween(lastOutTime.toLocalDate(), today); |
| | | } else if (builder.firstInTime() != null) { |
| | | stagnantDays = daysBetween(builder.firstInTime().toLocalDate(), today); |
| | | } else { |
| | | stagnantDays = 0; |
| | | } |
| | | BigDecimal outQty = outboundStats.outboundQtyByModel().getOrDefault(modelId, BigDecimal.ZERO); |
| | | boolean overstock = builder.warnNum().compareTo(BigDecimal.ZERO) > 0 |
| | | && builder.quantity().compareTo(builder.warnNum().multiply(new BigDecimal("3"))) > 0; |
| | | result.add(new InventoryMetric( |
| | | modelId, |
| | | product == null ? "æªç¥äº§å" : safe(product.getProductName()), |
| | | model == null ? "æªç¥åå·" : safe(model.getModel()), |
| | | builder.quantity(), |
| | | builder.lockedQuantity(), |
| | | unitCost, |
| | | value, |
| | | outQty, |
| | | stagnantDays, |
| | | overstock |
| | | )); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<AccountSalesCollection> queryCollections(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId); |
| | | if (range.hasDateFilter()) { |
| | | wrapper.ge(AccountSalesCollection::getCollectionDate, range.start()) |
| | | .le(AccountSalesCollection::getCollectionDate, range.end()); |
| | | } |
| | | wrapper.orderByAsc(AccountSalesCollection::getCollectionDate); |
| | | return defaultList(accountSalesCollectionMapper.selectList(wrapper)); |
| | | } |
| | | |
| | | private List<AccountPurchasePayment> queryPayments(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId); |
| | | if (range.hasDateFilter()) { |
| | | wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start()) |
| | | .le(AccountPurchasePayment::getPaymentDate, range.end()); |
| | | } |
| | | wrapper.orderByAsc(AccountPurchasePayment::getPaymentDate); |
| | | return defaultList(accountPurchasePaymentMapper.selectList(wrapper)); |
| | | } |
| | | |
| | | private List<MonthlyCashFlow> buildMonthlyCashFlow(DateRange range, |
| | | List<AccountSalesCollection> collections, |
| | | List<AccountPurchasePayment> payments) { |
| | | Map<YearMonth, BigDecimal> incomeByMonth = new LinkedHashMap<>(); |
| | | Map<YearMonth, BigDecimal> expenseByMonth = new LinkedHashMap<>(); |
| | | DateRange monthlyRange = range.hasDateFilter() ? range : inferCashFlowRange(collections, payments); |
| | | if (!monthlyRange.hasDateFilter()) { |
| | | return List.of(); |
| | | } |
| | | YearMonth startMonth = YearMonth.from(monthlyRange.start()); |
| | | YearMonth endMonth = YearMonth.from(monthlyRange.end()); |
| | | for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) { |
| | | incomeByMonth.put(month, BigDecimal.ZERO); |
| | | expenseByMonth.put(month, BigDecimal.ZERO); |
| | | } |
| | | |
| | | for (AccountSalesCollection row : collections) { |
| | | if (row.getCollectionDate() == null) { |
| | | continue; |
| | | } |
| | | YearMonth month = YearMonth.from(row.getCollectionDate()); |
| | | if (incomeByMonth.containsKey(month)) { |
| | | incomeByMonth.put(month, incomeByMonth.get(month).add(defaultDecimal(row.getCollectionAmount()))); |
| | | } |
| | | } |
| | | for (AccountPurchasePayment row : payments) { |
| | | if (row.getPaymentDate() == null) { |
| | | continue; |
| | | } |
| | | YearMonth month = YearMonth.from(row.getPaymentDate()); |
| | | if (expenseByMonth.containsKey(month)) { |
| | | expenseByMonth.put(month, expenseByMonth.get(month).add(defaultDecimal(row.getPaymentAmount()))); |
| | | } |
| | | } |
| | | |
| | | List<MonthlyCashFlow> result = new ArrayList<>(); |
| | | for (YearMonth month : incomeByMonth.keySet()) { |
| | | BigDecimal income = incomeByMonth.get(month); |
| | | BigDecimal expense = expenseByMonth.getOrDefault(month, BigDecimal.ZERO); |
| | | result.add(new MonthlyCashFlow(month.toString(), income, expense, income.subtract(expense))); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private DateRange inferCashFlowRange(List<AccountSalesCollection> collections, |
| | | List<AccountPurchasePayment> payments) { |
| | | LocalDate min = null; |
| | | LocalDate max = null; |
| | | for (AccountSalesCollection row : defaultList(collections)) { |
| | | if (row.getCollectionDate() == null) { |
| | | continue; |
| | | } |
| | | min = min == null || row.getCollectionDate().isBefore(min) ? row.getCollectionDate() : min; |
| | | max = max == null || row.getCollectionDate().isAfter(max) ? row.getCollectionDate() : max; |
| | | } |
| | | for (AccountPurchasePayment row : defaultList(payments)) { |
| | | if (row.getPaymentDate() == null) { |
| | | continue; |
| | | } |
| | | min = min == null || row.getPaymentDate().isBefore(min) ? row.getPaymentDate() : min; |
| | | max = max == null || row.getPaymentDate().isAfter(max) ? row.getPaymentDate() : max; |
| | | } |
| | | return min == null || max == null ? new DateRange(null, null, "å
¨é¨") : new DateRange(min, max, "å
¨é¨"); |
| | | } |
| | | |
| | | private List<MonthlyCashFlow> forecastMonthlyCashFlow(List<MonthlyCashFlow> actual, int forecastMonths) { |
| | | if (actual.isEmpty()) { |
| | | List<MonthlyCashFlow> defaults = new ArrayList<>(); |
| | | YearMonth now = YearMonth.now(); |
| | | for (int i = 1; i <= forecastMonths; i++) { |
| | | YearMonth month = now.plusMonths(i); |
| | | defaults.add(new MonthlyCashFlow(month.toString(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)); |
| | | } |
| | | return defaults; |
| | | } |
| | | List<BigDecimal> series = actual.stream().map(MonthlyCashFlow::netFlow).toList(); |
| | | BigDecimal avg = series.stream().reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | .divide(new BigDecimal(series.size()), 4, RoundingMode.HALF_UP); |
| | | BigDecimal slope = BigDecimal.ZERO; |
| | | if (series.size() > 1) { |
| | | slope = series.get(series.size() - 1).subtract(series.get(0)) |
| | | .divide(new BigDecimal(series.size() - 1), 4, RoundingMode.HALF_UP); |
| | | } |
| | | YearMonth lastMonth = YearMonth.parse(actual.get(actual.size() - 1).month()); |
| | | List<MonthlyCashFlow> forecast = new ArrayList<>(); |
| | | for (int i = 1; i <= forecastMonths; i++) { |
| | | YearMonth month = lastMonth.plusMonths(i); |
| | | BigDecimal net = avg.add(slope.multiply(new BigDecimal(i))).setScale(2, RoundingMode.HALF_UP); |
| | | BigDecimal income = net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO; |
| | | BigDecimal expense = net.compareTo(BigDecimal.ZERO) >= 0 ? BigDecimal.ZERO : net.abs(); |
| | | forecast.add(new MonthlyCashFlow(month.toString(), income, expense, net)); |
| | | } |
| | | return forecast; |
| | | } |
| | | |
| | | private StatementSnapshot buildStatementSnapshot(LoginUser loginUser) { |
| | | LambdaQueryWrapper<AccountStatement> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountStatement::getDeptId); |
| | | wrapper.orderByDesc(AccountStatement::getStatementMonth); |
| | | List<AccountStatement> rows = defaultList(accountStatementMapper.selectList(wrapper)); |
| | | if (rows.isEmpty()) { |
| | | return StatementSnapshot.empty(); |
| | | } |
| | | |
| | | Map<String, AccountStatement> latestByEntity = new HashMap<>(); |
| | | for (AccountStatement row : rows) { |
| | | if (row.getAccountType() == null || row.getCustomerId() == null || !StringUtils.hasText(row.getStatementMonth())) { |
| | | continue; |
| | | } |
| | | String key = row.getAccountType() + "::" + row.getCustomerId(); |
| | | AccountStatement existing = latestByEntity.get(key); |
| | | if (existing == null || row.getStatementMonth().compareTo(existing.getStatementMonth()) > 0) { |
| | | latestByEntity.put(key, row); |
| | | } |
| | | } |
| | | |
| | | BigDecimal receivableTotal = BigDecimal.ZERO; |
| | | BigDecimal payableTotal = BigDecimal.ZERO; |
| | | List<StatementMetric> receivableMetrics = new ArrayList<>(); |
| | | List<StatementMetric> payableMetrics = new ArrayList<>(); |
| | | for (AccountStatement row : latestByEntity.values()) { |
| | | BigDecimal closing = defaultDecimal(row.getClosingBalance()); |
| | | if (Objects.equals(row.getAccountType(), 1)) { |
| | | receivableTotal = receivableTotal.add(closing); |
| | | receivableMetrics.add(new StatementMetric(String.valueOf(row.getCustomerId()), closing, |
| | | defaultDecimal(row.getCurrentPlan()), defaultDecimal(row.getCurrentActually()), safe(row.getStatementMonth()))); |
| | | } else if (Objects.equals(row.getAccountType(), 2)) { |
| | | payableTotal = payableTotal.add(closing); |
| | | payableMetrics.add(new StatementMetric(String.valueOf(row.getCustomerId()), closing, |
| | | defaultDecimal(row.getCurrentPlan()), defaultDecimal(row.getCurrentActually()), safe(row.getStatementMonth()))); |
| | | } |
| | | } |
| | | receivableMetrics.sort(Comparator.comparing(StatementMetric::closingBalance).reversed()); |
| | | payableMetrics.sort(Comparator.comparing(StatementMetric::closingBalance).reversed()); |
| | | |
| | | return new StatementSnapshot( |
| | | receivableTotal, |
| | | payableTotal, |
| | | receivableMetrics.stream().limit(10).toList(), |
| | | payableMetrics.stream().limit(10).toList() |
| | | ); |
| | | } |
| | | |
| | | private BigDecimal calculateTotalDepreciation(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId); |
| | | wrapper.eq(DeviceLedger::getIsDepr, 1); |
| | | List<DeviceLedger> devices = defaultList(deviceLedgerMapper.selectList(wrapper)); |
| | | BigDecimal total = BigDecimal.ZERO; |
| | | for (DeviceLedger device : devices) { |
| | | total = total.add(defaultDecimal(AccountingServiceImpl.calculatePreciseDepreciation(device))); |
| | | } |
| | | return total; |
| | | } |
| | | |
| | | private Map<Long, BigDecimal> allocateDepreciation(List<SalesLedger> ledgers, BigDecimal totalDepreciation, BigDecimal totalRevenue) { |
| | | if (ledgers.isEmpty() || totalDepreciation.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return Map.of(); |
| | | } |
| | | Map<Long, BigDecimal> result = new HashMap<>(); |
| | | if (totalRevenue.compareTo(BigDecimal.ZERO) <= 0) { |
| | | BigDecimal avg = totalDepreciation.divide(new BigDecimal(ledgers.size()), 4, RoundingMode.HALF_UP); |
| | | for (SalesLedger ledger : ledgers) { |
| | | result.put(ledger.getId(), avg); |
| | | } |
| | | return result; |
| | | } |
| | | for (SalesLedger ledger : ledgers) { |
| | | BigDecimal revenue = defaultDecimal(ledger.getContractAmount()); |
| | | BigDecimal ratio = revenue.divide(totalRevenue, 6, RoundingMode.HALF_UP); |
| | | result.put(ledger.getId(), totalDepreciation.multiply(ratio)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private BigDecimal fallbackMaterialCost(List<SalesLedgerProduct> products, BigDecimal revenue) { |
| | | if (products != null && !products.isEmpty()) { |
| | | BigDecimal productAmount = products.stream() |
| | | .map(SalesLedgerProduct::getTaxExclusiveTotalPrice) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (productAmount.compareTo(BigDecimal.ZERO) > 0) { |
| | | return productAmount; |
| | | } |
| | | } |
| | | |
| | | BigDecimal materialCost = revenue.multiply(DEFAULT_FALLBACK_MATERIAL_COST_RATE); |
| | | BigDecimal laborCost = revenue.multiply(DEFAULT_LABOR_COST_RATE); |
| | | BigDecimal overheadCost = revenue.multiply(DEFAULT_OVERHEAD_COST_RATE); |
| | | |
| | | return materialCost.add(laborCost).add(overheadCost); |
| | | } |
| | | |
| | | private BigDecimal estimateTotalCost(BigDecimal revenue, List<SalesLedgerProduct> products) { |
| | | if (revenue == null || revenue.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | BigDecimal materialCost = BigDecimal.ZERO; |
| | | if (products != null && !products.isEmpty()) { |
| | | materialCost = products.stream() |
| | | .map(SalesLedgerProduct::getTaxExclusiveTotalPrice) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | } |
| | | |
| | | if (materialCost.compareTo(BigDecimal.ZERO) <= 0) { |
| | | materialCost = revenue.multiply(DEFAULT_FALLBACK_MATERIAL_COST_RATE); |
| | | } |
| | | |
| | | BigDecimal laborCost = revenue.multiply(DEFAULT_LABOR_COST_RATE); |
| | | BigDecimal overheadCost = revenue.multiply(DEFAULT_OVERHEAD_COST_RATE); |
| | | |
| | | return materialCost.add(laborCost).add(overheadCost); |
| | | } |
| | | |
| | | private Map<String, String> queryCustomerNameMap(Set<String> idSet) { |
| | | if (idSet == null || idSet.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | Set<Long> ids = idSet.stream() |
| | | .map(this::toLongOrNull) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | if (ids.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.in(Customer::getId, ids); |
| | | return defaultList(customerMapper.selectList(wrapper)).stream() |
| | | .collect(Collectors.toMap(item -> String.valueOf(item.getId()), Customer::getCustomerName, (a, b) -> a)); |
| | | } |
| | | |
| | | private Map<String, String> querySupplierNameMap(Set<String> idSet) { |
| | | if (idSet == null || idSet.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | Set<Long> ids = idSet.stream() |
| | | .map(this::toLongOrNull) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | if (ids.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | LambdaQueryWrapper<SupplierManage> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.in(SupplierManage::getId, ids); |
| | | return defaultList(supplierManageMapper.selectList(wrapper)).stream() |
| | | .collect(Collectors.toMap(item -> String.valueOf(item.getId()), SupplierManage::getSupplierName, (a, b) -> a)); |
| | | } |
| | | |
| | | private String riskLevelByAmount(BigDecimal amount) { |
| | | if (amount.compareTo(new BigDecimal("5000000")) >= 0) { |
| | | return "high"; |
| | | } |
| | | if (amount.compareTo(new BigDecimal("1000000")) >= 0) { |
| | | return "medium"; |
| | | } |
| | | return "low"; |
| | | } |
| | | |
| | | private long countDevices(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId); |
| | | return deviceLedgerMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countRepairingDevices(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId); |
| | | wrapper.in(DeviceRepair::getStatus, List.of(0, 3)); |
| | | return deviceRepairMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private BigDecimal estimateInventoryValue(LoginUser loginUser, List<StockInventory> inventories) { |
| | | if (inventories == null || inventories.isEmpty()) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | Set<Long> modelIds = inventories.stream().map(StockInventory::getProductModelId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | Map<Long, BigDecimal> costMap = queryAverageUnitCostByModel(loginUser, modelIds); |
| | | BigDecimal total = BigDecimal.ZERO; |
| | | for (StockInventory inventory : inventories) { |
| | | BigDecimal qty = maxZero(defaultDecimal(inventory.getQualitity()).subtract(defaultDecimal(inventory.getLockedQuantity()))); |
| | | BigDecimal unit = costMap.getOrDefault(inventory.getProductModelId(), BigDecimal.ZERO); |
| | | total = total.add(qty.multiply(unit)); |
| | | } |
| | | return total; |
| | | } |
| | | |
| | | private DateRange previousSameLengthRange(DateRange range) { |
| | | if (!range.hasDateFilter()) { |
| | | return new DateRange(null, null, "å
¨é¨"); |
| | | } |
| | | long days = daysBetween(range.start(), range.end()) + 1L; |
| | | LocalDate prevEnd = range.start().minusDays(1); |
| | | LocalDate prevStart = prevEnd.minusDays(days - 1L); |
| | | return new DateRange(prevStart, prevEnd, prevStart + "è³" + prevEnd); |
| | | } |
| | | |
| | | private List<String> buildProfitReasons(BigDecimal revenue, |
| | | BigDecimal materialCost, |
| | | BigDecimal laborCost, |
| | | BigDecimal scrapCost, |
| | | BigDecimal profit, |
| | | BigDecimal profitRate) { |
| | | List<String> reasons = new ArrayList<>(); |
| | | BigDecimal materialRate = rate(materialCost, revenue); |
| | | if (materialRate.compareTo(new BigDecimal("0.70")) >= 0) { |
| | | reasons.add("ææææ¬å æ¯è¶
è¿70%"); |
| | | } else if (materialRate.compareTo(new BigDecimal("0.55")) >= 0) { |
| | | reasons.add("ææææ¬å æ¯åé«"); |
| | | } |
| | | BigDecimal laborRate = rate(laborCost, revenue); |
| | | if (laborRate.compareTo(new BigDecimal("0.20")) >= 0) { |
| | | reasons.add("äººå·¥ææ¬å æ¯è¶
è¿20%"); |
| | | } else if (laborRate.compareTo(new BigDecimal("0.12")) >= 0) { |
| | | reasons.add("äººå·¥ææ¬å¢é¿åå¿«"); |
| | | } |
| | | BigDecimal scrapRate = rate(scrapCost, revenue); |
| | | if (scrapRate.compareTo(new BigDecimal("0.05")) >= 0) { |
| | | reasons.add("æ¥åºæèå æ¯åé«"); |
| | | } |
| | | if (profit.compareTo(BigDecimal.ZERO) < 0) { |
| | | reasons.add("订åå¤äºäºæç¶æ"); |
| | | } else if (profitRate.compareTo(new BigDecimal("0.08")) < 0) { |
| | | reasons.add("婿¶¦çä½äº8%"); |
| | | } |
| | | if (reasons.isEmpty()) { |
| | | reasons.add("ææ¬ç»æå¤äºåçåºé´"); |
| | | } |
| | | return reasons; |
| | | } |
| | | |
| | | private String buildProfitSuggestion(String riskLevel, List<String> reasons) { |
| | | if ("high".equals(riskLevel)) { |
| | | return "ä¼å
夿 ¸BOMç¨éä¸å·¥åºå®é¢ï¼å¿
è¦æ¶è°æ´æ¥ä»·å仿¬¾æ¡æ¬¾ï¼å¹¶éå¶è¶
è´¦æäº¤ä»ã"; |
| | | } |
| | | if ("medium".equals(riskLevel)) { |
| | | return "建议ä¼åéè´æ¹æ¬¡åå·¥åºæäº§ï¼æå䏿¬¡åæ ¼ç并忥æ§è¡æ¯å©é¢è¦ã"; |
| | | } |
| | | if (reasons.stream().anyMatch(item -> item.contains("ææ"))) { |
| | | return "ä¿æææéè´ææ¬çæ¿ï¼æå¨è·è¸ªä¸»è¦ææåä»·æ³¢å¨ã"; |
| | | } |
| | | return "ç»´æå½åç»è¥èå¥ï¼ç»§ç»è·è¸ªè®¢å婿¶¦çå忬¾æçã"; |
| | | } |
| | | |
| | | private Map<String, Object> toOrderCostItem(OrderProfitMetric metric) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("ledgerId", metric.ledgerId()); |
| | | item.put("salesContractNo", metric.salesContractNo()); |
| | | item.put("customerName", metric.customerName()); |
| | | item.put("projectName", metric.projectName()); |
| | | item.put("entryDate", formatDate(metric.entryDate())); |
| | | item.put("deliveryDate", formatDate(metric.deliveryDate())); |
| | | item.put("revenue", metric.revenue()); |
| | | item.put("materialCost", metric.materialCost()); |
| | | item.put("laborCost", metric.laborCost()); |
| | | item.put("depreciationCost", metric.depreciationCost()); |
| | | item.put("scrapCost", metric.scrapCost()); |
| | | item.put("totalCost", metric.totalCost()); |
| | | item.put("profit", metric.profit()); |
| | | item.put("profitRate", toPercent(metric.profitRate())); |
| | | item.put("riskLevel", metric.riskLevel()); |
| | | item.put("reasons", metric.reasons()); |
| | | item.put("suggestion", metric.suggestion()); |
| | | return item; |
| | | } |
| | | |
| | | private Map<String, Object> toRiskOrderItem(OrderProfitMetric metric) { |
| | | Map<String, Object> map = toOrderCostItem(metric); |
| | | map.put("priority", "high".equals(metric.riskLevel()) ? "high" : ("medium".equals(metric.riskLevel()) ? "medium" : "low")); |
| | | return map; |
| | | } |
| | | |
| | | private List<Map<String, Object>> buildCustomerProfitTop(List<OrderProfitMetric> metrics, int topN) { |
| | | Map<String, BigDecimal> customerProfitMap = new HashMap<>(); |
| | | Map<String, BigDecimal> customerRevenueMap = new HashMap<>(); |
| | | for (OrderProfitMetric metric : metrics) { |
| | | customerProfitMap.merge(metric.customerName(), metric.profit(), BigDecimal::add); |
| | | customerRevenueMap.merge(metric.customerName(), metric.revenue(), BigDecimal::add); |
| | | } |
| | | return customerProfitMap.entrySet().stream() |
| | | .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed()) |
| | | .limit(topN) |
| | | .map(entry -> { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | BigDecimal revenue = customerRevenueMap.getOrDefault(entry.getKey(), BigDecimal.ZERO); |
| | | map.put("customerName", entry.getKey()); |
| | | map.put("profit", entry.getValue()); |
| | | map.put("revenue", revenue); |
| | | map.put("profitRate", toPercent(rate(entry.getValue(), revenue))); |
| | | return map; |
| | | }) |
| | | .toList(); |
| | | } |
| | | |
| | | private Map<String, Object> toInventoryItem(InventoryMetric metric) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("productModelId", metric.modelId()); |
| | | map.put("productName", metric.productName()); |
| | | map.put("model", metric.modelName()); |
| | | map.put("quantity", metric.quantity()); |
| | | map.put("lockedQuantity", metric.lockedQuantity()); |
| | | map.put("avgUnitCost", metric.avgUnitCost()); |
| | | map.put("inventoryValue", metric.inventoryValue()); |
| | | map.put("outboundQuantity", metric.outboundQuantity()); |
| | | map.put("stagnantDays", metric.stagnantDays()); |
| | | map.put("overstock", metric.overstock()); |
| | | map.put("riskLevel", metric.stagnantDays() >= 90 ? "high" : (metric.stagnantDays() >= 30 ? "medium" : "low")); |
| | | return map; |
| | | } |
| | | |
| | | private boolean matchInventoryKeyword(InventoryMetric metric, String keyword) { |
| | | if (!StringUtils.hasText(keyword)) { |
| | | return true; |
| | | } |
| | | return metric.productName().contains(keyword.trim()) || metric.modelName().contains(keyword.trim()); |
| | | } |
| | | |
| | | private Map<String, Object> toMonthlyCashFlowItem(MonthlyCashFlow flow) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("month", flow.month()); |
| | | map.put("income", flow.income()); |
| | | map.put("expense", flow.expense()); |
| | | map.put("netFlow", flow.netFlow()); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toStatementRiskItem(StatementMetric metric, Map<String, String> nameMap, String type) { |
| | | BigDecimal actualRate = rate(metric.actualAmount(), metric.planAmount()); |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put(type + "Id", metric.entityId()); |
| | | map.put(type + "Name", safe(nameMap.get(metric.entityId()))); |
| | | map.put("statementMonth", metric.statementMonth()); |
| | | map.put("closingBalance", metric.closingBalance()); |
| | | map.put("planAmount", metric.planAmount()); |
| | | map.put("actualAmount", metric.actualAmount()); |
| | | map.put("actualRate", toPercent(actualRate)); |
| | | map.put("riskLevel", metric.closingBalance().compareTo(new BigDecimal("1000000")) > 0 || actualRate.compareTo(new BigDecimal("0.50")) < 0 ? "high" : "medium"); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> anomalyItem(String level, String type, String message, Map<String, Object> detail) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("riskLevel", level); |
| | | map.put("type", type); |
| | | map.put("message", message); |
| | | map.put("detail", detail); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> riskSuggestion(String type, String level, String suggestion) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("type", type); |
| | | map.put("level", level); |
| | | map.put("suggestion", suggestion); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> buildCostCompositionPie(BigDecimal material, BigDecimal labor, BigDecimal depreciation, BigDecimal scrap) { |
| | | List<Map<String, Object>> data = List.of( |
| | | Map.of("name", "ææææ¬", "value", material), |
| | | Map.of("name", "äººå·¥ææ¬", "value", labor), |
| | | Map.of("name", "ææ§ææ¬", "value", depreciation), |
| | | Map.of("name", "æèææ¬", "value", scrap) |
| | | ); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "ææ¬ææ", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "item")); |
| | | option.put("series", List.of(Map.of("name", "ææ¬ææ", "type", "pie", "radius", "60%", "data", data))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildOrderProfitBar(List<OrderProfitMetric> metrics) { |
| | | List<OrderProfitMetric> top = metrics.stream() |
| | | .sorted(Comparator.comparing(OrderProfitMetric::profit)) |
| | | .limit(10) |
| | | .toList(); |
| | | List<String> xData = top.stream().map(OrderProfitMetric::salesContractNo).toList(); |
| | | List<BigDecimal> yData = top.stream().map(OrderProfitMetric::profit).toList(); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "订å婿¶¦åå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", xData)); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "婿¶¦", "type", "bar", "data", yData))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildProcessCostBar(Map<String, BigDecimal> processCosts) { |
| | | List<String> xData = new ArrayList<>(processCosts.keySet()); |
| | | List<BigDecimal> yData = new ArrayList<>(processCosts.values()); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "å·¥åºææ¬æå", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", xData)); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "ææ¬", "type", "bar", "data", yData))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildProfitDistributionBar(List<OrderProfitMetric> metrics) { |
| | | List<OrderProfitMetric> sorted = metrics.stream() |
| | | .sorted(Comparator.comparing(OrderProfitMetric::profitRate)) |
| | | .limit(15) |
| | | .toList(); |
| | | List<String> xData = sorted.stream().map(OrderProfitMetric::salesContractNo).toList(); |
| | | List<BigDecimal> yData = sorted.stream().map(metric -> metric.profitRate().multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP)).toList(); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "订å婿¶¦çåå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", xData)); |
| | | option.put("yAxis", Map.of("type", "value", "name", "%")); |
| | | option.put("series", List.of(Map.of("name", "婿¶¦ç", "type", "bar", "data", yData))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildLossOrderTrendLine(List<OrderProfitMetric> metrics) { |
| | | Map<String, Long> lossByDate = new LinkedHashMap<>(); |
| | | List<OrderProfitMetric> sorted = metrics.stream() |
| | | .filter(metric -> metric.entryDate() != null) |
| | | .sorted(Comparator.comparing(OrderProfitMetric::entryDate)) |
| | | .toList(); |
| | | for (OrderProfitMetric metric : sorted) { |
| | | String day = formatDate(metric.entryDate()); |
| | | long inc = metric.profit().compareTo(BigDecimal.ZERO) < 0 ? 1L : 0L; |
| | | lossByDate.merge(day, inc, Long::sum); |
| | | } |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "äºæè®¢åè¶å¿", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(lossByDate.keySet()))); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "äºæè®¢åæ°", "type", "line", "smooth", true, "data", new ArrayList<>(lossByDate.values())))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildCustomerProfitBar(Map<String, BigDecimal> customerProfitMap) { |
| | | List<Map.Entry<String, BigDecimal>> top = customerProfitMap.entrySet().stream() |
| | | .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed()) |
| | | .limit(10) |
| | | .toList(); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "客æ·å©æ¶¦è´¡ç®TOP10", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", top.stream().map(Map.Entry::getKey).toList())); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "婿¶¦", "type", "bar", "data", top.stream().map(Map.Entry::getValue).toList()))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildInventoryTopBar(List<InventoryMetric> metrics) { |
| | | List<InventoryMetric> top = metrics.stream() |
| | | .sorted(Comparator.comparing(InventoryMetric::inventoryValue).reversed()) |
| | | .limit(10) |
| | | .toList(); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "åºåèµéå ç¨TOP10", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", top.stream().map(item -> item.productName() + "/" + item.modelName()).toList())); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "èµéå ç¨", "type", "bar", "data", top.stream().map(InventoryMetric::inventoryValue).toList()))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildInventoryAgingPie(List<InventoryMetric> metrics) { |
| | | long normal = metrics.stream().filter(item -> item.stagnantDays() < 30).count(); |
| | | long slow = metrics.stream().filter(item -> item.stagnantDays() >= 30 && item.stagnantDays() < 90).count(); |
| | | long stagnant = metrics.stream().filter(item -> item.stagnantDays() >= 90).count(); |
| | | List<Map<String, Object>> data = List.of( |
| | | Map.of("name", "æ£å¸¸", "value", normal), |
| | | Map.of("name", "ç¼æ
¢", "value", slow), |
| | | Map.of("name", "åæ»", "value", stagnant) |
| | | ); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "åºååºé¾åå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "item")); |
| | | option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", data))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildTurnoverGauge(BigDecimal turnoverDays) { |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "åºåå¨è½¬å¤©æ°", "left", "center")); |
| | | option.put("series", List.of(Map.of( |
| | | "type", "gauge", |
| | | "min", 0, |
| | | "max", 180, |
| | | "detail", Map.of("formatter", "{value}天"), |
| | | "data", List.of(Map.of("value", turnoverDays, "name", "å¨è½¬å¤©æ°")) |
| | | ))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildCashflowTrend(List<MonthlyCashFlow> actual, List<MonthlyCashFlow> forecast) { |
| | | List<String> labels = new ArrayList<>(); |
| | | List<BigDecimal> netActual = new ArrayList<>(); |
| | | List<BigDecimal> netForecast = new ArrayList<>(); |
| | | for (MonthlyCashFlow point : actual) { |
| | | labels.add(point.month()); |
| | | netActual.add(point.netFlow()); |
| | | netForecast.add(null); |
| | | } |
| | | for (MonthlyCashFlow point : forecast) { |
| | | labels.add(point.month()); |
| | | netActual.add(null); |
| | | netForecast.add(point.netFlow()); |
| | | } |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "ç°éæµè¶å¿ï¼å®é
+颿µï¼", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", labels)); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of( |
| | | Map.of("name", "å®é
åç°éæµ", "type", "line", "smooth", true, "data", netActual), |
| | | Map.of("name", "颿µåç°éæµ", "type", "line", "smooth", true, "data", netForecast) |
| | | )); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildReceivablePayableBar(BigDecimal receivable, BigDecimal payable) { |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "åºæ¶åºä»ä½é¢å¯¹æ¯", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", List.of("åºæ¶", "åºä»"))); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "ä½é¢", "type", "bar", "data", List.of(receivable, payable)))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildFundGapGauge(BigDecimal fundGap) { |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "èµé缺å£", "left", "center")); |
| | | option.put("series", List.of(Map.of( |
| | | "type", "gauge", |
| | | "min", 0, |
| | | "max", 10000000, |
| | | "detail", Map.of("formatter", "{value}"), |
| | | "data", List.of(Map.of("value", fundGap, "name", "èµé缺å£")) |
| | | ))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildAnomalyLevelPie(List<Map<String, Object>> anomalies) { |
| | | long high = anomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count(); |
| | | long medium = anomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count(); |
| | | long low = anomalies.stream().filter(item -> "low".equals(item.get("riskLevel"))).count(); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "å¼å¸¸ç级åå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "item")); |
| | | option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", List.of( |
| | | Map.of("name", "é«é£é©", "value", high), |
| | | Map.of("name", "ä¸é£é©", "value", medium), |
| | | Map.of("name", "ä½é£é©", "value", low) |
| | | )))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildAnomalyTypeBar(List<Map<String, Object>> anomalies) { |
| | | Map<String, Long> countByType = new LinkedHashMap<>(); |
| | | for (Map<String, Object> anomaly : anomalies) { |
| | | countByType.merge(String.valueOf(anomaly.get("type")), 1L, Long::sum); |
| | | } |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "å¼å¸¸ç±»ååå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(countByType.keySet()))); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "å¼å¸¸æ°", "type", "bar", "data", new ArrayList<>(countByType.values())))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildInventoryProfitGauge(BigDecimal inventoryValue, BigDecimal profit) { |
| | | BigDecimal ratio = inventoryValue.compareTo(BigDecimal.ZERO) <= 0 |
| | | ? BigDecimal.ZERO |
| | | : profit.divide(inventoryValue, 4, RoundingMode.HALF_UP).multiply(ONE_HUNDRED); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "婿¶¦/åºåèµéæ¯", "left", "center")); |
| | | option.put("series", List.of(Map.of( |
| | | "type", "gauge", |
| | | "min", -100, |
| | | "max", 100, |
| | | "detail", Map.of("formatter", "{value}%"), |
| | | "data", List.of(Map.of("value", ratio.setScale(2, RoundingMode.HALF_UP), "name", "婿¶¦èµéæ¯")) |
| | | ))); |
| | | return option; |
| | | } |
| | | |
| | | private int normalizeLimit(Integer limit) { |
| | | if (limit == null || limit <= 0) { |
| | | return DEFAULT_LIMIT; |
| | | } |
| | | return Math.min(limit, MAX_LIMIT); |
| | | } |
| | | |
| | | private DateRange resolveDateRange(String startDate, String endDate, String timeRange, String defaultLabel) { |
| | | LocalDate today = LocalDate.now(); |
| | | LocalDate explicitStart = parseLocalDate(startDate); |
| | | LocalDate explicitEnd = parseLocalDate(endDate); |
| | | if (explicitStart != null || explicitEnd != null) { |
| | | LocalDate start = explicitStart != null ? explicitStart : explicitEnd; |
| | | LocalDate end = explicitEnd != null ? explicitEnd : explicitStart; |
| | | if (start.isAfter(end)) { |
| | | LocalDate temp = start; |
| | | start = end; |
| | | end = temp; |
| | | } |
| | | return new DateRange(start, end, start + "è³" + end); |
| | | } |
| | | |
| | | if (!StringUtils.hasText(timeRange)) { |
| | | return new DateRange(null, null, "å
¨é¨"); |
| | | } |
| | | |
| | | String text = timeRange.trim(); |
| | | if (text.contains("ä»å¤©")) { |
| | | return new DateRange(today, today, "ä»å¤©"); |
| | | } |
| | | if (text.contains("æ¨å¤©") || text.contains("æ¨æ¥")) { |
| | | LocalDate day = today.minusDays(1); |
| | | return new DateRange(day, day, "æ¨å¤©"); |
| | | } |
| | | if (text.contains("æ¬å¨")) { |
| | | LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(start, today, "æ¬å¨"); |
| | | } |
| | | if (text.contains("ä¸å¨")) { |
| | | LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | LocalDate start = thisWeekStart.minusWeeks(1); |
| | | LocalDate end = thisWeekStart.minusDays(1); |
| | | return new DateRange(start, end, "ä¸å¨"); |
| | | } |
| | | if (text.contains("æ¬æ")) { |
| | | return new DateRange(today.withDayOfMonth(1), today, "æ¬æ"); |
| | | } |
| | | if (text.contains("䏿")) { |
| | | YearMonth lastMonth = YearMonth.from(today).minusMonths(1); |
| | | return new DateRange(lastMonth.atDay(1), lastMonth.atEndOfMonth(), "䏿"); |
| | | } |
| | | if (text.contains("ä»å¹´") || text.contains("æ¬å¹´")) { |
| | | return new DateRange(today.withDayOfYear(1), today, "ä»å¹´"); |
| | | } |
| | | Matcher relativeMatcher = RELATIVE_PATTERN.matcher(text); |
| | | if (relativeMatcher.find()) { |
| | | int amount = Integer.parseInt(relativeMatcher.group(2)); |
| | | String unit = relativeMatcher.group(3); |
| | | LocalDate start = switch (unit) { |
| | | case "天" -> today.minusDays(Math.max(amount - 1L, 0)); |
| | | case "å¨" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1); |
| | | case "个æ", "æ" -> today.minusMonths(Math.max(amount, 1)).plusDays(1); |
| | | case "å¹´" -> today.minusYears(Math.max(amount, 1)).plusDays(1); |
| | | default -> today.minusDays(29); |
| | | }; |
| | | return new DateRange(start, today, "è¿" + amount + unit); |
| | | } |
| | | Matcher dateMatcher = DATE_PATTERN.matcher(text); |
| | | if (dateMatcher.find()) { |
| | | LocalDate start = parseLocalDate(dateMatcher.group(1)); |
| | | LocalDate end = dateMatcher.find() ? parseLocalDate(dateMatcher.group(1)) : start; |
| | | if (start != null && end != null) { |
| | | if (start.isAfter(end)) { |
| | | LocalDate temp = start; |
| | | start = end; |
| | | end = temp; |
| | | } |
| | | return new DateRange(start, end, start + "è³" + end); |
| | | } |
| | | } |
| | | return new DateRange(null, null, "å
¨é¨"); |
| | | } |
| | | |
| | | private LocalDate parseLocalDate(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return LocalDate.parse(text.trim(), DATE_FMT); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Date toDate(LocalDate localDate) { |
| | | return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } |
| | | |
| | | private Date toExclusiveEndDate(LocalDate localDate) { |
| | | return Date.from(localDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } |
| | | |
| | | private LocalDate toLocalDate(Date date) { |
| | | if (date == null) { |
| | | return null; |
| | | } |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? "" : date.format(DATE_FMT); |
| | | } |
| | | |
| | | private String displayDate(LocalDate date) { |
| | | return date == null ? "" : date.format(DATE_FMT); |
| | | } |
| | | |
| | | private long daysBetween(LocalDate start, LocalDate end) { |
| | | if (start == null || end == null || start.isAfter(end)) { |
| | | return 0; |
| | | } |
| | | return end.toEpochDay() - start.toEpochDay(); |
| | | } |
| | | |
| | | private BigDecimal defaultDecimal(BigDecimal value) { |
| | | return value == null ? BigDecimal.ZERO : value; |
| | | } |
| | | |
| | | private BigDecimal maxZero(BigDecimal value) { |
| | | return value == null || value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value; |
| | | } |
| | | |
| | | private BigDecimal rate(BigDecimal numerator, BigDecimal denominator) { |
| | | if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return defaultDecimal(numerator).divide(denominator, 6, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private String toPercent(BigDecimal decimal) { |
| | | if (decimal == null) { |
| | | return "0.00%"; |
| | | } |
| | | BigDecimal rate = decimal.multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP); |
| | | return rate.toPlainString() + "%"; |
| | | } |
| | | |
| | | private BigDecimal avgRate(List<OrderProfitMetric> metrics) { |
| | | if (metrics == null || metrics.isEmpty()) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | BigDecimal sum = metrics.stream().map(OrderProfitMetric::profitRate).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | return sum.divide(new BigDecimal(metrics.size()), 6, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private BigDecimal estimateLaborCost(ProductionAccount account, Map<String, BigDecimal> salaryQuotaByOperation) { |
| | | BigDecimal salaryQuota = salaryQuotaByOperation.getOrDefault(safe(account.getTechnologyOperationName()), BigDecimal.ZERO); |
| | | BigDecimal finishedNum = defaultDecimal(account.getFinishedNum()); |
| | | BigDecimal workHours = defaultDecimal(account.getWorkHours()); |
| | | if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && finishedNum.compareTo(BigDecimal.ZERO) > 0) { |
| | | return finishedNum.multiply(salaryQuota); |
| | | } |
| | | if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && workHours.compareTo(BigDecimal.ZERO) > 0) { |
| | | return workHours.multiply(salaryQuota); |
| | | } |
| | | if (workHours.compareTo(BigDecimal.ZERO) > 0) { |
| | | return workHours; |
| | | } |
| | | return finishedNum; |
| | | } |
| | | |
| | | private List<Long> parseIdList(String raw) { |
| | | if (!StringUtils.hasText(raw)) { |
| | | return List.of(); |
| | | } |
| | | String text = raw.replace("[", "").replace("]", "").replace(" ", ""); |
| | | if (!StringUtils.hasText(text)) { |
| | | return List.of(); |
| | | } |
| | | List<Long> result = new ArrayList<>(); |
| | | for (String part : text.split(",")) { |
| | | if (!StringUtils.hasText(part)) { |
| | | continue; |
| | | } |
| | | try { |
| | | result.add(Long.parseLong(part.trim())); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private int keywordHitCount(List<String> keywords, String question) { |
| | | if (!StringUtils.hasText(question) || keywords == null) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | for (String keyword : keywords) { |
| | | if (question.contains(keyword)) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace("ï¼", "") |
| | | .replace(",", "") |
| | | .replace("ã", "") |
| | | .replace(".", "") |
| | | .replace("ï¼", "") |
| | | .replace("!", "") |
| | | .replace("ï¼", "") |
| | | .replace("?", "") |
| | | .replace("ï¼", "") |
| | | .replace(":", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private String safe(Object value) { |
| | | return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ').trim(); |
| | | } |
| | | |
| | | private LoginUser currentLoginUser(String memoryId) { |
| | | LoginUser loginUser = aiSessionUserContext.get(memoryId); |
| | | if (loginUser != null) { |
| | | return loginUser; |
| | | } |
| | | return SecurityUtils.getLoginUser(); |
| | | } |
| | | |
| | | private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) { |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", displayDate(range.start())); |
| | | summary.put("endDate", displayDate(range.end())); |
| | | summary.put("count", count); |
| | | summary.put("keyword", safe(keyword)); |
| | | return summary; |
| | | } |
| | | |
| | | private Long toLongOrNull(String value) { |
| | | if (!StringUtils.hasText(value)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return Long.valueOf(value.trim()); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private <T> List<T> defaultList(List<T> list) { |
| | | return list == null ? List.of() : list; |
| | | } |
| | | |
| | | private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) { |
| | | if (tenantId != null) { |
| | | wrapper.eq(field, tenantId); |
| | | } |
| | | } |
| | | |
| | | private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) { |
| | | if (deptId != null) { |
| | | wrapper.eq(field, deptId); |
| | | } |
| | | } |
| | | |
| | | private List<KnowledgeDoc> financeKnowledgeBase() { |
| | | return List.of( |
| | | new KnowledgeDoc( |
| | | "婿¶¦ä¸éåææ¡æ¶", |
| | | List.of("婿¶¦ä¸é", "äºæè®¢å", "æ¯å©ç", "åå©ç"), |
| | | "å
çæ¶å
¥ç«¯ï¼è®¢åç»æãåä»·ã交ä»å»¶è¿ï¼ï¼åçææ¬ç«¯ï¼ææãäººå·¥ãææ§ãæèï¼ï¼æåçç°é端ï¼å款ãè´¦æãåè´¦é£é©ï¼ã", |
| | | List.of("sales_ledger", "sales_ledger_product", "production_account", "device_ledger", "account_statement"), |
| | | List.of("为ä»ä¹æ¬æå©æ¶¦ä¸éï¼", "åªäºè®¢åäºææä¸¥éï¼", "ææ¬ä¸åæ¥èªåªä¸ªå·¥åºï¼") |
| | | ), |
| | | new KnowledgeDoc( |
| | | "åºåèµéå ç¨è¯æ", |
| | | List.of("åºå积å", "åæ»åºå", "å¨è½¬ç", "èµéå ç¨"), |
| | | "åºåèµéè¯æéç¹çï¼åºåä»·å¼ãè¿30天åºåºææ¬ã忻天æ°ãè¶
卿¯ä¾ï¼å½¢æå»åºåä¸éè´èå¥èå¨çç¥ã", |
| | | List.of("stock_inventory", "procurement_record_storage", "procurement_record_out"), |
| | | List.of("åªäºç©æèµéå ç¨æé«ï¼", "åªäºåºåè¶
è¿90天æªå¨è½¬ï¼", "åºåå¨è½¬å¤©æ°æ¯å¦å¼å¸¸ï¼") |
| | | ), |
| | | new KnowledgeDoc( |
| | | "ç°éæµä¸è´¦æ¬¾é£é©", |
| | | List.of("ç°éæµ", "åºæ¶", "åºä»", "忬¾", "èµé缺å£"), |
| | | "ç°éæµå¤æè¦ç»åæ¶æ¬¾ã仿¬¾ãåºæ¶åºä»ä½é¢ä¸é¢æµåæµéï¼éç¹å
³æ³¨é«ä½é¢å®¢æ·åé«éä¸ä»æ¬¾ä¾åºåã", |
| | | List.of("account_sales_collection", "account_purchase_payment", "account_statement"), |
| | | List.of("æªæ¥ä¸ä¸ªææ¯å¦æèµé缺å£ï¼", "åªä¸ªå®¢æ·å款é£é©æé«ï¼", "仿¬¾ååæå¤§çæ¯åªäºä¾åºåï¼") |
| | | ), |
| | | new KnowledgeDoc( |
| | | "ä¸è´¢ä¸ä½åå£å¾", |
| | | List.of("ä¸è´¢èå", "ä¸è´¢èå¨", "å£å¾", "驾驶è±"), |
| | | "订å婿¶¦å£å¾=é宿¶å
¥-ææææ¬-äººå·¥ææ¬-è®¾å¤ææ§-æèææ¬ï¼ç»è¥é©¾é©¶è±èå¨è®¢åãç产ãåºåã设å¤ãè´¦æ¬¾æ°æ®ã", |
| | | List.of("sales_ledger", "production_operation_task", "production_product_main", "device_ledger", "stock_inventory", "account_statement"), |
| | | List.of("订å婿¶¦çå¦ä½è®¡ç®ï¼", "ç»è¥é©¾é©¶è±æ ¸å¿ææ æåªäºï¼") |
| | | ) |
| | | ); |
| | | } |
| | | |
| | | private String jsonResponse(boolean success, |
| | | String type, |
| | | String description, |
| | | Map<String, Object> summary, |
| | | Map<String, Object> data, |
| | | Map<String, Object> charts) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("success", success); |
| | | result.put("type", type); |
| | | result.put("description", description); |
| | | result.put("summary", summary == null ? Map.of() : summary); |
| | | result.put("data", data == null ? Map.of() : data); |
| | | result.put("charts", charts == null ? Map.of() : charts); |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | private record DateRange(LocalDate start, LocalDate end, String label) { |
| | | private boolean hasDateFilter() { |
| | | return start != null && end != null; |
| | | } |
| | | } |
| | | |
| | | private record OrderProfitMetric(Long ledgerId, |
| | | String salesContractNo, |
| | | String customerName, |
| | | String projectName, |
| | | LocalDate entryDate, |
| | | LocalDate deliveryDate, |
| | | BigDecimal revenue, |
| | | BigDecimal materialCost, |
| | | BigDecimal laborCost, |
| | | BigDecimal depreciationCost, |
| | | BigDecimal scrapCost, |
| | | BigDecimal totalCost, |
| | | BigDecimal profit, |
| | | BigDecimal profitRate, |
| | | String riskLevel, |
| | | List<String> reasons, |
| | | String suggestion) { |
| | | } |
| | | |
| | | private record AnalysisBundle(List<OrderProfitMetric> orderMetrics, |
| | | Map<String, BigDecimal> processCostRanking, |
| | | BigDecimal totalRevenue, |
| | | BigDecimal totalMaterialCost, |
| | | BigDecimal totalLaborCost, |
| | | BigDecimal totalDepreciationCost, |
| | | BigDecimal totalScrapCost, |
| | | BigDecimal totalCost, |
| | | BigDecimal totalProfit) { |
| | | private static AnalysisBundle empty() { |
| | | return new AnalysisBundle(List.of(), Map.of(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, |
| | | BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); |
| | | } |
| | | } |
| | | |
| | | private record MaterialCostResult(Map<Long, BigDecimal> materialCostByLedgerId, |
| | | Map<Long, BigDecimal> avgUnitCostByModelId) { |
| | | } |
| | | |
| | | private record ProductionCostContext(Map<Long, BigDecimal> laborCostByLedgerId, |
| | | Map<Long, BigDecimal> scrapCostByLedgerId, |
| | | Map<String, BigDecimal> processCostRanking) { |
| | | private static ProductionCostContext empty() { |
| | | return new ProductionCostContext(Map.of(), Map.of(), Map.of()); |
| | | } |
| | | } |
| | | |
| | | private record InventoryMetric(Long modelId, |
| | | String productName, |
| | | String modelName, |
| | | BigDecimal quantity, |
| | | BigDecimal lockedQuantity, |
| | | BigDecimal avgUnitCost, |
| | | BigDecimal inventoryValue, |
| | | BigDecimal outboundQuantity, |
| | | long stagnantDays, |
| | | boolean overstock) { |
| | | } |
| | | |
| | | private static class InventoryMetricBuilder { |
| | | private final Long modelId; |
| | | private BigDecimal quantity = BigDecimal.ZERO; |
| | | private BigDecimal lockedQuantity = BigDecimal.ZERO; |
| | | private BigDecimal warnNum = BigDecimal.ZERO; |
| | | private LocalDateTime firstInTime; |
| | | |
| | | private InventoryMetricBuilder(Long modelId) { |
| | | this.modelId = modelId; |
| | | } |
| | | |
| | | private void addQuantity(BigDecimal quantity) { |
| | | this.quantity = this.quantity.add(quantity); |
| | | } |
| | | |
| | | private void addLockedQuantity(BigDecimal lockedQuantity) { |
| | | this.lockedQuantity = this.lockedQuantity.add(lockedQuantity); |
| | | } |
| | | |
| | | private void addWarnNum(BigDecimal warnNum) { |
| | | this.warnNum = this.warnNum.max(warnNum); |
| | | } |
| | | |
| | | private void updateFirstInTime(LocalDateTime createTime) { |
| | | if (this.firstInTime == null || createTime.isBefore(this.firstInTime)) { |
| | | this.firstInTime = createTime; |
| | | } |
| | | } |
| | | |
| | | private Long modelId() { |
| | | return modelId; |
| | | } |
| | | |
| | | private BigDecimal quantity() { |
| | | return quantity; |
| | | } |
| | | |
| | | private BigDecimal lockedQuantity() { |
| | | return lockedQuantity; |
| | | } |
| | | |
| | | private BigDecimal warnNum() { |
| | | return warnNum; |
| | | } |
| | | |
| | | private LocalDateTime firstInTime() { |
| | | return firstInTime; |
| | | } |
| | | } |
| | | |
| | | private record OutboundStats(Map<Long, BigDecimal> outboundQtyByModel, |
| | | Map<Long, LocalDateTime> lastOutboundTimeByModel, |
| | | BigDecimal totalOutboundCost) { |
| | | private static OutboundStats empty() { |
| | | return new OutboundStats(Map.of(), Map.of(), BigDecimal.ZERO); |
| | | } |
| | | } |
| | | |
| | | private record MonthlyCashFlow(String month, BigDecimal income, BigDecimal expense, BigDecimal netFlow) { |
| | | } |
| | | |
| | | private record StatementMetric(String entityId, |
| | | BigDecimal closingBalance, |
| | | BigDecimal planAmount, |
| | | BigDecimal actualAmount, |
| | | String statementMonth) { |
| | | } |
| | | |
| | | private record StatementSnapshot(BigDecimal receivableTotal, |
| | | BigDecimal payableTotal, |
| | | List<StatementMetric> receivableTop, |
| | | List<StatementMetric> payableTop) { |
| | | private static StatementSnapshot empty() { |
| | | return new StatementSnapshot(BigDecimal.ZERO, BigDecimal.ZERO, List.of(), List.of()); |
| | | } |
| | | } |
| | | |
| | | private record KnowledgeDoc(String topic, |
| | | List<String> keywords, |
| | | String knowledge, |
| | | List<String> relatedTables, |
| | | List<String> suggestedQuestions) { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.tools; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.device.mapper.DeviceDefectRecordMapper; |
| | | import com.ruoyi.device.mapper.DeviceLedgerMapper; |
| | | import com.ruoyi.device.mapper.DeviceRepairMapper; |
| | | import com.ruoyi.device.pojo.DeviceDefectRecord; |
| | | import com.ruoyi.device.pojo.DeviceLedger; |
| | | import com.ruoyi.device.pojo.DeviceRepair; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.procurementrecord.mapper.ProcurementExceptionRecordMapper; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementExceptionRecord; |
| | | import com.ruoyi.production.mapper.ProductionOperationTaskMapper; |
| | | import com.ruoyi.production.mapper.ProductionOrderMapper; |
| | | import com.ruoyi.production.mapper.ProductionPlanMapper; |
| | | import com.ruoyi.production.mapper.ProductionProductMainMapper; |
| | | import com.ruoyi.production.pojo.ProductionOperationTask; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionPlan; |
| | | import com.ruoyi.production.pojo.ProductionProductMain; |
| | | import com.ruoyi.quality.mapper.QualityInspectMapper; |
| | | import com.ruoyi.quality.mapper.QualityUnqualifiedMapper; |
| | | import com.ruoyi.quality.pojo.QualityInspect; |
| | | import com.ruoyi.quality.pojo.QualityUnqualified; |
| | | import com.ruoyi.stock.mapper.StockInventoryMapper; |
| | | import com.ruoyi.stock.pojo.StockInventory; |
| | | import dev.langchain4j.agent.tool.P; |
| | | import dev.langchain4j.agent.tool.Tool; |
| | | import dev.langchain4j.agent.tool.ToolMemoryId; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Component |
| | | public class ManufacturingAgentTools { |
| | | |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final int DEFAULT_LIMIT = 10; |
| | | private static final int MAX_LIMIT = 30; |
| | | private static final int DEVICE_REPAIR_STATUS_PENDING = 0; |
| | | |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionProductMainMapper productionProductMainMapper; |
| | | private final DeviceLedgerMapper deviceLedgerMapper; |
| | | private final DeviceRepairMapper deviceRepairMapper; |
| | | private final DeviceDefectRecordMapper deviceDefectRecordMapper; |
| | | private final QualityInspectMapper qualityInspectMapper; |
| | | private final QualityUnqualifiedMapper qualityUnqualifiedMapper; |
| | | private final StockInventoryMapper stockInventoryMapper; |
| | | private final ProcurementExceptionRecordMapper procurementExceptionRecordMapper; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | |
| | | public ManufacturingAgentTools(ProductionPlanMapper productionPlanMapper, |
| | | ProductionOrderMapper productionOrderMapper, |
| | | ProductionOperationTaskMapper productionOperationTaskMapper, |
| | | ProductionProductMainMapper productionProductMainMapper, |
| | | DeviceLedgerMapper deviceLedgerMapper, |
| | | DeviceRepairMapper deviceRepairMapper, |
| | | DeviceDefectRecordMapper deviceDefectRecordMapper, |
| | | QualityInspectMapper qualityInspectMapper, |
| | | QualityUnqualifiedMapper qualityUnqualifiedMapper, |
| | | StockInventoryMapper stockInventoryMapper, |
| | | ProcurementExceptionRecordMapper procurementExceptionRecordMapper, |
| | | AiSessionUserContext aiSessionUserContext) { |
| | | this.productionPlanMapper = productionPlanMapper; |
| | | this.productionOrderMapper = productionOrderMapper; |
| | | this.productionOperationTaskMapper = productionOperationTaskMapper; |
| | | this.productionProductMainMapper = productionProductMainMapper; |
| | | this.deviceLedgerMapper = deviceLedgerMapper; |
| | | this.deviceRepairMapper = deviceRepairMapper; |
| | | this.deviceDefectRecordMapper = deviceDefectRecordMapper; |
| | | this.qualityInspectMapper = qualityInspectMapper; |
| | | this.qualityUnqualifiedMapper = qualityUnqualifiedMapper; |
| | | this.stockInventoryMapper = stockInventoryMapper; |
| | | this.procurementExceptionRecordMapper = procurementExceptionRecordMapper; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | } |
| | | |
| | | @Tool(name = "æ¥è¯¢å¶é ä¸å¡åæ°æ®", value = "æä¸å¡åæ¥è¯¢ç产ç°åºã计åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤çç¸å
³æ°æ®ã") |
| | | public String queryDomain(@ToolMemoryId String memoryId, |
| | | @P(value = "ä¸å¡åï¼site/plan/workorder/device/quality/material/exception") String domain, |
| | | @P(value = "å
³é®åï¼å¯ä¸ä¼ ", required = false) String keyword, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§30", required = false) Integer limit, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦ä»å¹´ãæ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | int finalLimit = normalizeLimit(limit); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange); |
| | | boolean hasTimeConstraint = hasTimeConstraint(startDate, endDate, timeRange); |
| | | String normalizedDomain = normalizeDomain(domain); |
| | | |
| | | return switch (normalizedDomain) { |
| | | case "site" -> siteSnapshot(loginUser, range); |
| | | case "plan" -> listProductionPlans(loginUser, keyword, finalLimit, range); |
| | | case "workorder" -> listWorkOrders(loginUser, keyword, finalLimit, range); |
| | | case "device" -> isRepairIntent(keyword, timeRange) |
| | | ? listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint) |
| | | : listDevices(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit); |
| | | case "repair" -> listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint); |
| | | case "quality" -> listQualityIssues(loginUser, keyword, finalLimit, range); |
| | | case "material" -> listMaterialInventory(loginUser, keyword, finalLimit); |
| | | case "exception" -> listExceptions(loginUser, keyword, finalLimit, range); |
| | | default -> jsonResponse(false, "manufacturing_query", "䏿¯æçä¸å¡å: " + safe(domain), Map.of(), Map.of(), Map.of()); |
| | | }; |
| | | } |
| | | |
| | | @Tool(name = "å¶é é¢è¦çæ¿", value = "计ç®è®¡åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤ççé¢è¦ä¿¡æ¯ã") |
| | | public String getWarningBoard(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦ä»å¤©ãæ¬å¨ãæ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange); |
| | | LocalDate today = LocalDate.now(); |
| | | |
| | | long overduePlanCount = countOverduePlans(loginUser, today); |
| | | long overdueWorkOrderCount = countOverdueWorkOrders(loginUser, today); |
| | | long pendingRepairCount = countPendingRepairs(loginUser); |
| | | long qualityOpenCount = countOpenQualityIssues(loginUser, range); |
| | | long lowStockCount = countLowStock(loginUser); |
| | | long exceptionCount = countExceptionRecords(loginUser, range); |
| | | |
| | | List<Map<String, Object>> warningItems = new ArrayList<>(); |
| | | if (overduePlanCount > 0) { |
| | | warningItems.add(warningItem("high", "计å龿", overduePlanCount, "æç产计åè¶
è¿éæ±æ¥æä»æªå®æ")); |
| | | } |
| | | if (overdueWorkOrderCount > 0) { |
| | | warningItems.add(warningItem("high", "å·¥å龿", overdueWorkOrderCount, "æå·¥å计åç»ææ¥æå·²è¿ä»æªå®å·¥")); |
| | | } |
| | | if (pendingRepairCount > 0) { |
| | | warningItems.add(warningItem("medium", "设å¤å¾
ç»´ä¿®", pendingRepairCount, "åå¨å¾
ç»´ä¿®/ç»´ä¿®ä¸ç设å¤")); |
| | | } |
| | | if (qualityOpenCount > 0) { |
| | | warningItems.add(warningItem("high", "è´¨éæªéç¯", qualityOpenCount, "å卿ªå¤ç宿çä¸åæ ¼è®°å½")); |
| | | } |
| | | if (lowStockCount > 0) { |
| | | warningItems.add(warningItem("medium", "ç©æä½åºå", lowStockCount, "åºåæ°éä½äºæçäºé¢è¦éå¼")); |
| | | } |
| | | if (exceptionCount > 0) { |
| | | warningItems.add(warningItem("medium", "å¼å¸¸è®°å½", exceptionCount, "æ¶é´èå´å
åå¨å¼å¸¸å¤çè®°å½")); |
| | | } |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("warningCount", warningItems.size()); |
| | | summary.put("overduePlanCount", overduePlanCount); |
| | | summary.put("overdueWorkOrderCount", overdueWorkOrderCount); |
| | | summary.put("pendingRepairCount", pendingRepairCount); |
| | | summary.put("qualityOpenCount", qualityOpenCount); |
| | | summary.put("lowStockCount", lowStockCount); |
| | | summary.put("exceptionCount", exceptionCount); |
| | | |
| | | return jsonResponse(true, "manufacturing_warning", "å·²è¿åå¶é é¢è¦çæ¿ã", summary, |
| | | Map.of("items", warningItems), Map.of()); |
| | | } |
| | | |
| | | @Tool(name = "å¶é ç»è¥åæ", value = "ææ¶é´èå´è¾åºå¶é å
³é®ææ ï¼æ¯ææ¥ãé®ãåæåºæ¯ã") |
| | | public String analyzeFactory(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦æ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange); |
| | | |
| | | long planTotal = countPlans(loginUser, range); |
| | | long planCompleted = countPlansByStatus(loginUser, range, 2); |
| | | long workOrderTotal = countWorkOrders(loginUser, range); |
| | | long workOrderCompleted = countWorkOrdersByStatus(loginUser, range, 2); |
| | | long workOrderInProgress = countWorkOrdersByStatus(loginUser, range, 1); |
| | | |
| | | long outputCount = countOutputs(loginUser, range); |
| | | long deviceTotal = countDevices(loginUser); |
| | | long pendingRepairCount = countPendingRepairs(loginUser); |
| | | long qualityInspectTotal = countQualityInspect(loginUser, range); |
| | | long qualityNgCount = countOpenQualityIssues(loginUser, range); |
| | | long materialSkuCount = countInventorySku(loginUser); |
| | | long lowStockCount = countLowStock(loginUser); |
| | | long exceptionCount = countExceptionRecords(loginUser, range); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("planTotal", planTotal); |
| | | summary.put("planCompleted", planCompleted); |
| | | summary.put("planCompletionRate", toRate(planCompleted, planTotal)); |
| | | summary.put("workOrderTotal", workOrderTotal); |
| | | summary.put("workOrderCompleted", workOrderCompleted); |
| | | summary.put("workOrderInProgress", workOrderInProgress); |
| | | summary.put("workOrderCompletionRate", toRate(workOrderCompleted, workOrderTotal)); |
| | | summary.put("outputCount", outputCount); |
| | | summary.put("deviceTotal", deviceTotal); |
| | | summary.put("pendingRepairCount", pendingRepairCount); |
| | | summary.put("qualityInspectTotal", qualityInspectTotal); |
| | | summary.put("qualityNgCount", qualityNgCount); |
| | | summary.put("qualityIssueRate", toRate(qualityNgCount, qualityInspectTotal)); |
| | | summary.put("materialSkuCount", materialSkuCount); |
| | | summary.put("lowStockCount", lowStockCount); |
| | | summary.put("exceptionCount", exceptionCount); |
| | | |
| | | List<Map<String, Object>> coreMetrics = List.of( |
| | | metric("计å宿ç", toRate(planCompleted, planTotal)), |
| | | metric("å·¥å宿ç", toRate(workOrderCompleted, workOrderTotal)), |
| | | metric("è´¨éå¼å¸¸ç", toRate(qualityNgCount, qualityInspectTotal)), |
| | | metric("ä½åºåå æ¯", toRate(lowStockCount, materialSkuCount)) |
| | | ); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("domainBarOption", buildDomainBarOption(summary)); |
| | | charts.put("qualityPieOption", buildQualityPieOption(qualityInspectTotal, qualityNgCount)); |
| | | |
| | | return jsonResponse(true, "manufacturing_analysis", "å·²è¿åå¶é åæç»æã", summary, |
| | | Map.of("coreMetrics", coreMetrics), charts); |
| | | } |
| | | |
| | | @Tool(name = "çæå¶é åç建议", value = "æ ¹æ®ç¨æ·é®é¢è¾åºå¯æ§è¡çåçå¨ä½å»ºè®®ï¼å
æ¬ç®æ ä¸å¡æ¥å£ãå¿
å¡«åæ®µå示ä¾ã") |
| | | public String planActions(@ToolMemoryId String memoryId, |
| | | @P("ç¨æ·è¯æ±åæ") String userQuery) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | List<Map<String, Object>> actionCards = new ArrayList<>(); |
| | | |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "å·¥å", "派工", "ä½ä¸")) { |
| | | actionCards.add(actionCard( |
| | | "workorder_assign", |
| | | "工忴¾å·¥", |
| | | "POST", |
| | | "/productionOperationTask/assign", |
| | | List.of("id", "userIds"), |
| | | Map.of("id", 10001, "userIds", "12,13"), |
| | | "å°å·¥ååé
ç»æå®äººåï¼éç¨äºç°åºè°åº¦ã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "设å¤", "ç»´ä¿®", "æ
é")) { |
| | | actionCards.add(actionCard( |
| | | "device_repair_create", |
| | | "å建设å¤ç»´ä¿®å", |
| | | "POST", |
| | | "/device/repair", |
| | | List.of("deviceLedgerId", "deviceName", "repairName", "remark"), |
| | | Map.of("deviceLedgerId", 1001, "deviceName", "ç©ºåæºA-01", "repairName", "å¼ ä¸", "remark", "å¼å并伴鿏©å"), |
| | | "æ°å»ºç»´ä¿®åï¼è¿å
¥è®¾å¤å¼å¸¸å¤çéç¯ã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "è´¨é", "ä¸åæ ¼", "éç¯")) { |
| | | actionCards.add(actionCard( |
| | | "quality_unqualified_deal", |
| | | "å¤çä¸åæ ¼å", |
| | | "POST", |
| | | "/quality/qualityUnqualified/deal", |
| | | List.of("id", "dealResult", "dealName"), |
| | | Map.of("id", 3001, "dealResult", "è¿å·¥å夿£", "dealName", "æå"), |
| | | "对ä¸åæ ¼è®°å½æ§è¡å¤ç½®å¹¶éç¯ã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "ç©æ", "åºå", "è¡¥æ")) { |
| | | actionCards.add(actionCard( |
| | | "material_inbound", |
| | | "è¡¥å
åºå", |
| | | "POST", |
| | | "/stockInventory/addstockInventory", |
| | | List.of("productModelId", "batchNo", "qualitity"), |
| | | Map.of("productModelId", 5001, "batchNo", "B2026051601", "qualitity", 120), |
| | | "å½ä½åºåé¢è¦è§¦åæ¶ï¼å¢å åºåæ°éã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "å¼å¸¸", "éè´å¼å¸¸", "æ¥æå¼å¸¸")) { |
| | | actionCards.add(actionCard( |
| | | "procurement_exception_add", |
| | | "ç»è®°å¼å¸¸è®°å½", |
| | | "POST", |
| | | "/procurementExceptionRecord/add", |
| | | List.of("purchaseLedgerId", "exceptionReason", "exceptionNum"), |
| | | Map.of("purchaseLedgerId", 888, "exceptionReason", "å°æç缺", "exceptionNum", 24), |
| | | "ç»è®°éè´/æ¥æå¼å¸¸ï¼ä¾¿äºåç»è¿½è¸ªååæã")); |
| | | } |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("actionCount", actionCards.size()); |
| | | summary.put("userId", loginUser.getUserId()); |
| | | summary.put("tenantId", loginUser.getTenantId()); |
| | | |
| | | return jsonResponse(true, "manufacturing_action_plan", "å·²çæåç建议ï¼è¯·å端å¼å¯¼ç¨æ·ç¡®è®¤åè°ç¨ç®æ ä¸å¡æ¥å£ã", |
| | | summary, Map.of("actionCards", actionCards), Map.of()); |
| | | } |
| | | |
| | | private String siteSnapshot(LoginUser loginUser, DateRange range) { |
| | | long planTotal = countPlans(loginUser, range); |
| | | long workOrderTotal = countWorkOrders(loginUser, range); |
| | | long outputCount = countOutputs(loginUser, range); |
| | | long deviceTotal = countDevices(loginUser); |
| | | long pendingRepairCount = countPendingRepairs(loginUser); |
| | | long qualityOpenCount = countOpenQualityIssues(loginUser, range); |
| | | long lowStockCount = countLowStock(loginUser); |
| | | long exceptionCount = countExceptionRecords(loginUser, range); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("planTotal", planTotal); |
| | | summary.put("workOrderTotal", workOrderTotal); |
| | | summary.put("outputCount", outputCount); |
| | | summary.put("deviceTotal", deviceTotal); |
| | | summary.put("pendingRepairCount", pendingRepairCount); |
| | | summary.put("qualityOpenCount", qualityOpenCount); |
| | | summary.put("lowStockCount", lowStockCount); |
| | | summary.put("exceptionCount", exceptionCount); |
| | | |
| | | return jsonResponse(true, "manufacturing_site_snapshot", "å·²è¿åç产ç°åºæ¦è§ã", summary, Map.of(), Map.of()); |
| | | } |
| | | |
| | | private String listProductionPlans(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end()); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(ProductionPlan::getMpsNo, keyword) |
| | | .or().like(ProductionPlan::getRemark, keyword) |
| | | .or().like(ProductionPlan::getSource, keyword)); |
| | | } |
| | | wrapper.orderByDesc(ProductionPlan::getRequiredDate, ProductionPlan::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(productionPlanMapper.selectList(wrapper)).stream() |
| | | .map(this::toPlanItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_plan_list", "å·²è¿åç产计åå表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listWorkOrders(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start()) |
| | | .le(ProductionOperationTask::getPlanEndTime, range.end()); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(ProductionOperationTask::getWorkOrderNo, keyword) |
| | | .or().like(ProductionOperationTask::getUserIds, keyword)); |
| | | } |
| | | wrapper.orderByDesc(ProductionOperationTask::getPlanEndTime, ProductionOperationTask::getId) |
| | | .last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(productionOperationTaskMapper.selectList(wrapper)).stream() |
| | | .map(this::toWorkOrderItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_workorder_list", "å·²è¿åå·¥åå表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listDevices(LoginUser loginUser, String keyword, int limit) { |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword) |
| | | .or().like(DeviceLedger::getDeviceModel, keyword) |
| | | .or().like(DeviceLedger::getDeviceBrand, keyword)); |
| | | } |
| | | wrapper.orderByDesc(DeviceLedger::getId).last("limit " + limit); |
| | | |
| | | Map<Long, Long> pendingRepairMap = pendingRepairCountByDevice(loginUser); |
| | | List<Map<String, Object>> items = defaultList(deviceLedgerMapper.selectList(wrapper)).stream() |
| | | .map(item -> toDeviceItem(item, pendingRepairMap.getOrDefault(item.getId(), 0L))) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_device_list", "å·²è¿å设å¤å表ã", |
| | | Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listDeviceRepairs(LoginUser loginUser, String keyword, int limit, DateRange range, boolean hasTimeConstraint) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | Long currentDeptId = loginUser.getCurrentDeptId(); |
| | | if (currentDeptId != null) { |
| | | wrapper.and(w -> w.eq(DeviceRepair::getDeptId, currentDeptId).or().isNull(DeviceRepair::getDeptId)); |
| | | } |
| | | if (hasTimeConstraint) { |
| | | wrapper.ge(DeviceRepair::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(DeviceRepair::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | if (StringUtils.hasText(keyword)) { |
| | | List<Long> matchedDeviceIds = findDeviceLedgerIdsByKeyword(loginUser, keyword); |
| | | wrapper.and(w -> { |
| | | w.like(DeviceRepair::getDeviceName, keyword) |
| | | .or().like(DeviceRepair::getDeviceModel, keyword) |
| | | .or().like(DeviceRepair::getRemark, keyword) |
| | | .or().like(DeviceRepair::getRepairName, keyword) |
| | | .or().like(DeviceRepair::getMaintenanceName, keyword); |
| | | if (!matchedDeviceIds.isEmpty()) { |
| | | w.or().in(DeviceRepair::getDeviceLedgerId, matchedDeviceIds); |
| | | } |
| | | }); |
| | | } |
| | | wrapper.orderByDesc(DeviceRepair::getCreateTime, DeviceRepair::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(deviceRepairMapper.selectList(wrapper)).stream() |
| | | .map(this::toDeviceRepairItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_device_repair_list", "å·²è¿å设å¤ç»´ä¿®è®°å½ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listQualityIssues(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId); |
| | | wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start())) |
| | | .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end())); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(QualityUnqualified::getProductName, keyword) |
| | | .or().like(QualityUnqualified::getDefectivePhenomena, keyword) |
| | | .or().like(QualityUnqualified::getDealResult, keyword)); |
| | | } |
| | | wrapper.orderByDesc(QualityUnqualified::getCheckTime, QualityUnqualified::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(qualityUnqualifiedMapper.selectList(wrapper)).stream() |
| | | .map(this::toQualityItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_quality_list", "å·²è¿åè´¨éå¼å¸¸å表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listMaterialInventory(LoginUser loginUser, String keyword, int limit) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(StockInventory::getBatchNo, keyword) |
| | | .or().like(StockInventory::getProductModelId, keyword)); |
| | | } |
| | | wrapper.orderByDesc(StockInventory::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(stockInventoryMapper.selectList(wrapper)).stream() |
| | | .map(this::toMaterialItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_material_list", "å·²è¿åç©æåºåå表ã", |
| | | Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listExceptions(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId); |
| | | wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.like(ProcurementExceptionRecord::getExceptionReason, keyword); |
| | | } |
| | | wrapper.orderByDesc(ProcurementExceptionRecord::getCreateTime, ProcurementExceptionRecord::getId) |
| | | .last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(procurementExceptionRecordMapper.selectList(wrapper)).stream() |
| | | .map(this::toExceptionItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_exception_list", "å·²è¿åå¼å¸¸å¤çå表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private long countPlans(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end()); |
| | | return productionPlanMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countPlansByStatus(LoginUser loginUser, DateRange range, int status) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.ge(ProductionPlan::getRequiredDate, range.start()) |
| | | .le(ProductionPlan::getRequiredDate, range.end()) |
| | | .eq(ProductionPlan::getStatus, status); |
| | | return productionPlanMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countWorkOrders(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start()) |
| | | .le(ProductionOperationTask::getPlanEndTime, range.end()); |
| | | return productionOperationTaskMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countWorkOrdersByStatus(LoginUser loginUser, DateRange range, int status) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start()) |
| | | .le(ProductionOperationTask::getPlanEndTime, range.end()) |
| | | .eq(ProductionOperationTask::getStatus, status); |
| | | return productionOperationTaskMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOutputs(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProductionProductMain> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId); |
| | | wrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | return productionProductMainMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countDevices(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId); |
| | | return deviceLedgerMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countPendingRepairs(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId); |
| | | wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING); |
| | | return deviceRepairMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countQualityInspect(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<QualityInspect> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), QualityInspect::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityInspect::getDeptId); |
| | | wrapper.ge(QualityInspect::getCheckTime, toDate(range.start())) |
| | | .lt(QualityInspect::getCheckTime, toExclusiveEndDate(range.end())); |
| | | return qualityInspectMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOpenQualityIssues(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId); |
| | | wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start())) |
| | | .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end())) |
| | | .ne(QualityUnqualified::getInspectState, 2); |
| | | return qualityUnqualifiedMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countInventorySku(LoginUser loginUser) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | return stockInventoryMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countLowStock(LoginUser loginUser) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | wrapper.isNotNull(StockInventory::getWarnNum); |
| | | List<StockInventory> stocks = defaultList(stockInventoryMapper.selectList(wrapper)); |
| | | return stocks.stream() |
| | | .filter(this::isLowStock) |
| | | .count(); |
| | | } |
| | | |
| | | private long countExceptionRecords(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId); |
| | | wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | return procurementExceptionRecordMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOverduePlans(LoginUser loginUser, LocalDate today) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.lt(ProductionPlan::getRequiredDate, today).ne(ProductionPlan::getStatus, 2); |
| | | return productionPlanMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOverdueWorkOrders(LoginUser loginUser, LocalDate today) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.lt(ProductionOperationTask::getPlanEndTime, today).ne(ProductionOperationTask::getStatus, 2); |
| | | return productionOperationTaskMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private Map<Long, Long> pendingRepairCountByDevice(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId); |
| | | wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING); |
| | | return defaultList(deviceRepairMapper.selectList(wrapper)).stream() |
| | | .filter(item -> item.getDeviceLedgerId() != null) |
| | | .collect(Collectors.groupingBy(DeviceRepair::getDeviceLedgerId, Collectors.counting())); |
| | | } |
| | | |
| | | private Map<String, Object> toPlanItem(ProductionPlan item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("mpsNo", safe(item.getMpsNo())); |
| | | map.put("requiredDate", formatDate(item.getRequiredDate())); |
| | | map.put("promisedDeliveryDate", formatDate(item.getPromisedDeliveryDate())); |
| | | map.put("qtyRequired", item.getQtyRequired()); |
| | | map.put("quantityIssued", item.getQuantityIssued()); |
| | | map.put("status", item.getStatus()); |
| | | map.put("source", safe(item.getSource())); |
| | | map.put("remark", safe(item.getRemark())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toWorkOrderItem(ProductionOperationTask item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("workOrderNo", safe(item.getWorkOrderNo())); |
| | | map.put("productionOrderId", item.getProductionOrderId()); |
| | | map.put("planStartTime", formatDate(item.getPlanStartTime())); |
| | | map.put("planEndTime", formatDate(item.getPlanEndTime())); |
| | | map.put("actualStartTime", formatDate(item.getActualStartTime())); |
| | | map.put("actualEndTime", formatDate(item.getActualEndTime())); |
| | | map.put("planQuantity", item.getPlanQuantity()); |
| | | map.put("completeQuantity", item.getCompleteQuantity()); |
| | | map.put("status", item.getStatus()); |
| | | map.put("userIds", safe(item.getUserIds())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toDeviceItem(DeviceLedger item, long pendingRepairCount) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("deviceName", safe(item.getDeviceName())); |
| | | map.put("deviceModel", safe(item.getDeviceModel())); |
| | | map.put("deviceBrand", safe(item.getDeviceBrand())); |
| | | map.put("status", safe(item.getStatus())); |
| | | map.put("storageLocation", safe(item.getStorageLocation())); |
| | | map.put("supplierName", safe(item.getSupplierName())); |
| | | map.put("pendingRepairCount", pendingRepairCount); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toDeviceRepairItem(DeviceRepair item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("deviceLedgerId", item.getDeviceLedgerId()); |
| | | map.put("deviceName", safe(item.getDeviceName())); |
| | | map.put("deviceModel", safe(item.getDeviceModel())); |
| | | map.put("repairTime", formatDate(item.getRepairTime())); |
| | | map.put("repairName", safe(item.getRepairName())); |
| | | map.put("maintenanceName", safe(item.getMaintenanceName())); |
| | | map.put("maintenanceTime", formatDateTime(item.getMaintenanceTime())); |
| | | map.put("maintenanceResult", safe(item.getMaintenanceResult())); |
| | | map.put("acceptanceName", safe(item.getAcceptanceName())); |
| | | map.put("acceptanceTime", formatDateTime(item.getAcceptanceTime())); |
| | | map.put("status", item.getStatus()); |
| | | map.put("remark", safe(item.getRemark())); |
| | | map.put("createTime", formatDateTime(item.getCreateTime())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toQualityItem(QualityUnqualified item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("checkTime", formatDate(item.getCheckTime())); |
| | | map.put("inspectState", item.getInspectState()); |
| | | map.put("productId", item.getProductId()); |
| | | map.put("productName", safe(item.getProductName())); |
| | | map.put("model", safe(item.getModel())); |
| | | map.put("quantity", item.getQuantity()); |
| | | map.put("defectivePhenomena", safe(item.getDefectivePhenomena())); |
| | | map.put("dealResult", safe(item.getDealResult())); |
| | | map.put("dealName", safe(item.getDealName())); |
| | | map.put("dealTime", formatDate(item.getDealTime())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toMaterialItem(StockInventory item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("productModelId", item.getProductModelId()); |
| | | map.put("batchNo", safe(item.getBatchNo())); |
| | | map.put("qualitity", item.getQualitity()); |
| | | map.put("lockedQuantity", item.getLockedQuantity()); |
| | | map.put("warnNum", item.getWarnNum()); |
| | | map.put("lowStock", isLowStock(item)); |
| | | map.put("remark", safe(item.getRemark())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toExceptionItem(ProcurementExceptionRecord item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("purchaseLedgerId", item.getPurchaseLedgerId()); |
| | | map.put("exceptionReason", safe(item.getExceptionReason())); |
| | | map.put("exceptionNum", item.getExceptionNum()); |
| | | map.put("createTime", formatDateTime(item.getCreateTime())); |
| | | return map; |
| | | } |
| | | |
| | | private boolean isLowStock(StockInventory item) { |
| | | BigDecimal quantity = item.getQualitity(); |
| | | BigDecimal warnNum = item.getWarnNum(); |
| | | if (quantity == null || warnNum == null) { |
| | | return false; |
| | | } |
| | | return quantity.compareTo(warnNum) <= 0; |
| | | } |
| | | |
| | | private Map<String, Object> warningItem(String level, String title, long count, String detail) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("level", level); |
| | | map.put("title", title); |
| | | map.put("count", count); |
| | | map.put("detail", detail); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> actionCard(String code, |
| | | String name, |
| | | String method, |
| | | String targetApi, |
| | | List<String> requiredFields, |
| | | Map<String, Object> examplePayload, |
| | | String description) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("code", code); |
| | | map.put("name", name); |
| | | map.put("method", method); |
| | | map.put("targetApi", targetApi); |
| | | map.put("requiredFields", requiredFields); |
| | | map.put("examplePayload", examplePayload); |
| | | map.put("description", description); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> metric(String label, String value) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("label", label); |
| | | map.put("value", value); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) { |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("count", count); |
| | | summary.put("keyword", safe(keyword)); |
| | | return summary; |
| | | } |
| | | |
| | | private Map<String, Object> buildDomainBarOption(Map<String, Object> summary) { |
| | | List<String> xData = List.of("计å", "å·¥å", "设å¤", "è´¨é", "ç©æ", "å¼å¸¸"); |
| | | List<Number> yData = List.of( |
| | | numberValue(summary.get("planTotal")), |
| | | numberValue(summary.get("workOrderTotal")), |
| | | numberValue(summary.get("deviceTotal")), |
| | | numberValue(summary.get("qualityNgCount")), |
| | | numberValue(summary.get("lowStockCount")), |
| | | numberValue(summary.get("exceptionCount")) |
| | | ); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "å¶é åå
³é®æ°é", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", xData)); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "æ°é", "type", "bar", "data", yData))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildQualityPieOption(long inspectTotal, long ngCount) { |
| | | long passCount = Math.max(inspectTotal - ngCount, 0); |
| | | List<Map<String, Object>> data = List.of( |
| | | Map.of("name", "ä¸åæ ¼", "value", ngCount), |
| | | Map.of("name", "éä¸åæ ¼", "value", passCount) |
| | | ); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "è´¨éç»æåå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "item")); |
| | | option.put("series", List.of(Map.of("name", "è´¨é", "type", "pie", "radius", "60%", "data", data))); |
| | | return option; |
| | | } |
| | | |
| | | private int numberValue(Object value) { |
| | | if (value instanceof Number number) { |
| | | return number.intValue(); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | private String toRate(long numerator, long denominator) { |
| | | if (denominator <= 0) { |
| | | return "0.00%"; |
| | | } |
| | | BigDecimal rate = new BigDecimal(numerator) |
| | | .multiply(new BigDecimal("100")) |
| | | .divide(new BigDecimal(denominator), 2, RoundingMode.HALF_UP); |
| | | return rate.toPlainString() + "%"; |
| | | } |
| | | |
| | | private String normalizeDomain(String domain) { |
| | | if (!StringUtils.hasText(domain)) { |
| | | return ""; |
| | | } |
| | | String value = domain.trim().toLowerCase(); |
| | | return switch (value) { |
| | | case "ç产ç°åº", "site", "factory", "workshop" -> "site"; |
| | | case "计å", "plan", "schedule" -> "plan"; |
| | | case "å·¥å", "workorder", "work_order", "task" -> "workorder"; |
| | | case "设å¤", "device", "equipment" -> "device"; |
| | | case "ç»´ä¿®", "repair", "maintenance" -> "repair"; |
| | | case "è´¨é", "quality", "qc" -> "quality"; |
| | | case "ç©æ", "material", "inventory", "stock" -> "material"; |
| | | case "å¼å¸¸", "exception", "abnormal" -> "exception"; |
| | | default -> value; |
| | | }; |
| | | } |
| | | |
| | | private boolean isRepairIntent(String keyword, String userQuery) { |
| | | String query = safe(userQuery); |
| | | return containsAny(safe(keyword), "ç»´ä¿®", "æ¥ä¿®", "æ£ä¿®", "ç»´æ¤") |
| | | || containsAny(query, "ç»´ä¿®", "æ¥ä¿®", "æ£ä¿®", "ç»´æ¤"); |
| | | } |
| | | |
| | | private String normalizeDeviceQueryKeyword(String keyword, String userQuery) { |
| | | String source = StringUtils.hasText(keyword) ? keyword : userQuery; |
| | | if (!StringUtils.hasText(source)) { |
| | | return null; |
| | | } |
| | | String cleaned = source |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("帮æ", "") |
| | | .replace("请", "") |
| | | .replace("æ¥", "") |
| | | .replace("设å¤", "") |
| | | .replace("维修记å½", "") |
| | | .replace("ç»´ä¿®æ
åµ", "") |
| | | .replace("æ¥ä¿®è®°å½", "") |
| | | .replace("æ¥ä¿®æ
åµ", "") |
| | | .replace("ç»´ä¿®", "") |
| | | .replace("æ¥ä¿®", "") |
| | | .replace("æ
åµ", "") |
| | | .replace("è®°å½", "") |
| | | .replace("ä¿¡æ¯", "") |
| | | .replace("ç", "") |
| | | .replace("ä¸ä¸", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private List<Long> findDeviceLedgerIdsByKeyword(LoginUser loginUser, String keyword) { |
| | | if (!StringUtils.hasText(keyword)) { |
| | | return List.of(); |
| | | } |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | Long currentDeptId = loginUser.getCurrentDeptId(); |
| | | if (currentDeptId != null) { |
| | | wrapper.and(w -> w.eq(DeviceLedger::getDeptId, currentDeptId).or().isNull(DeviceLedger::getDeptId)); |
| | | } |
| | | wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword) |
| | | .or().like(DeviceLedger::getDeviceModel, keyword) |
| | | .or().like(DeviceLedger::getDeviceBrand, keyword)); |
| | | wrapper.orderByDesc(DeviceLedger::getId).last("limit 200"); |
| | | return defaultList(deviceLedgerMapper.selectList(wrapper)).stream() |
| | | .map(DeviceLedger::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | private boolean hasTimeConstraint(String startDate, String endDate, String userQuery) { |
| | | if (StringUtils.hasText(startDate) || StringUtils.hasText(endDate)) { |
| | | return true; |
| | | } |
| | | if (!StringUtils.hasText(userQuery)) { |
| | | return false; |
| | | } |
| | | String text = userQuery.trim(); |
| | | return containsAny(text, "ä»å¤©", "æ¨å¤©", "æ¬å¨", "ä¸å¨", "æ¬æ", "䏿", "ä»å¹´", "å»å¹´", "è¿", "æè¿"); |
| | | } |
| | | |
| | | private DateRange resolveDateRange(String startDate, String endDate, String timeRange) { |
| | | LocalDate today = LocalDate.now(); |
| | | LocalDate start = parseLocalDate(startDate); |
| | | LocalDate end = parseLocalDate(endDate); |
| | | if (start != null || end != null) { |
| | | LocalDate s = start != null ? start : end; |
| | | LocalDate e = end != null ? end : start; |
| | | if (s.isAfter(e)) { |
| | | LocalDate temp = s; |
| | | s = e; |
| | | e = temp; |
| | | } |
| | | return new DateRange(s, e, s + "è³" + e); |
| | | } |
| | | if (!StringUtils.hasText(timeRange)) { |
| | | return new DateRange(today.minusDays(29), today, "è¿30天"); |
| | | } |
| | | String text = timeRange.trim(); |
| | | if (text.contains("ä»å¤©")) { |
| | | return new DateRange(today, today, "ä»å¤©"); |
| | | } |
| | | if (text.contains("æ¬å¨")) { |
| | | LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(startOfWeek, today, "æ¬å¨"); |
| | | } |
| | | if (text.contains("æ¬æ")) { |
| | | return new DateRange(today.withDayOfMonth(1), today, "æ¬æ"); |
| | | } |
| | | if (text.contains("æ¬å¹´") || text.contains("ä»å¹´")) { |
| | | return new DateRange(today.withDayOfYear(1), today, "ä»å¹´"); |
| | | } |
| | | if (text.contains("å»å¹´")) { |
| | | LocalDate firstDay = today.minusYears(1).withDayOfYear(1); |
| | | LocalDate lastDay = today.minusYears(1).withMonth(12).withDayOfMonth(31); |
| | | return new DateRange(firstDay, lastDay, "å»å¹´"); |
| | | } |
| | | if (text.contains("䏿")) { |
| | | LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1); |
| | | return new DateRange(startOfLastMonth, startOfLastMonth.withDayOfMonth(startOfLastMonth.lengthOfMonth()), "䏿"); |
| | | } |
| | | java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(è¿|æè¿)(\\d+)(天|å¨|个æ|æ|å¹´)").matcher(text); |
| | | if (matcher.find()) { |
| | | int amount = Integer.parseInt(matcher.group(2)); |
| | | String unit = matcher.group(3); |
| | | LocalDate relativeStart = switch (unit) { |
| | | case "天" -> today.minusDays(Math.max(amount - 1L, 0)); |
| | | case "å¨" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1); |
| | | case "个æ", "æ" -> today.minusMonths(Math.max(amount, 1)).plusDays(1); |
| | | case "å¹´" -> today.minusYears(Math.max(amount, 1)).plusDays(1); |
| | | default -> today.minusDays(29); |
| | | }; |
| | | return new DateRange(relativeStart, today, "è¿" + amount + unit); |
| | | } |
| | | return new DateRange(today.minusDays(29), today, "è¿30天"); |
| | | } |
| | | |
| | | private LocalDate parseLocalDate(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | return LocalDate.parse(text.trim(), DATE_FMT); |
| | | } |
| | | |
| | | private Date toDate(LocalDate date) { |
| | | return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } |
| | | |
| | | private Date toExclusiveEndDate(LocalDate date) { |
| | | return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? "" : DATE_FMT.format(date); |
| | | } |
| | | |
| | | private String formatDate(Date date) { |
| | | if (date == null) { |
| | | return ""; |
| | | } |
| | | return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); |
| | | } |
| | | |
| | | private String formatDateTime(LocalDateTime time) { |
| | | if (time == null) { |
| | | return ""; |
| | | } |
| | | return time.truncatedTo(ChronoUnit.SECONDS).toString().replace('T', ' '); |
| | | } |
| | | |
| | | private int normalizeLimit(Integer limit) { |
| | | if (limit == null || limit <= 0) { |
| | | return DEFAULT_LIMIT; |
| | | } |
| | | return Math.min(limit, MAX_LIMIT); |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... values) { |
| | | for (String value : values) { |
| | | if (text.contains(value)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) { |
| | | if (tenantId != null) { |
| | | wrapper.eq(field, tenantId); |
| | | } |
| | | } |
| | | |
| | | private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) { |
| | | if (deptId != null) { |
| | | wrapper.eq(field, deptId); |
| | | } |
| | | } |
| | | |
| | | private LoginUser currentLoginUser(String memoryId) { |
| | | LoginUser loginUser = aiSessionUserContext.get(memoryId); |
| | | if (loginUser != null) { |
| | | return loginUser; |
| | | } |
| | | return SecurityUtils.getLoginUser(); |
| | | } |
| | | |
| | | private String safe(Object value) { |
| | | return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' '); |
| | | } |
| | | |
| | | private <T> List<T> defaultList(List<T> list) { |
| | | return list == null ? List.of() : list; |
| | | } |
| | | |
| | | private String jsonResponse(boolean success, |
| | | String type, |
| | | String description, |
| | | Map<String, Object> summary, |
| | | Map<String, Object> data, |
| | | Map<String, Object> charts) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("success", success); |
| | | result.put("type", type); |
| | | result.put("description", description); |
| | | result.put("summary", summary == null ? Map.of() : summary); |
| | | result.put("data", data == null ? Map.of() : data); |
| | | result.put("charts", charts == null ? Map.of() : charts); |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | private record DateRange(LocalDate start, LocalDate end, String label) { |
| | | } |
| | | } |
| src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java
src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java
src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java
src/main/java/com/ruoyi/approve/bean/vo/ApproveGetAndUpdateVo.java
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java
src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java
src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java
src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java
src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java
src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java
src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java
src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java
src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java
src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
src/main/java/com/ruoyi/approve/dto/KnowledgeBaseVectorVO.java
src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java
src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java
src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java
src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java
src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java
src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java
src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java
src/main/java/com/ruoyi/approve/mapper/KnowledgeBaseVectorMapper.java
src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java
src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java
src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java
src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java
src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java
src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java
src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java
src/main/java/com/ruoyi/approve/pojo/KnowledgeBaseVector.java
src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java
src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java
src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java
src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java
src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java
src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java
src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java
src/main/java/com/ruoyi/approve/service/FinReimbursementService.java
src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java
src/main/java/com/ruoyi/approve/service/KnowledgeBaseVectorService.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java
src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseVectorServiceImpl.java
src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java
src/main/java/com/ruoyi/basic/controller/CustomerController.java
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java
src/main/java/com/ruoyi/basic/dto/StorageBlobVO.java
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java
src/main/java/com/ruoyi/basic/mapper/SupplierManageMapper.java
src/main/java/com/ruoyi/basic/pojo/ProductModel.java
src/main/java/com/ruoyi/basic/service/ICustomerService.java
src/main/java/com/ruoyi/basic/service/ISupplierService.java
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.java
src/main/java/com/ruoyi/basic/utils/FileUtil.java
src/main/java/com/ruoyi/basic/vo/CustomerVo.java
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java
src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java
src/main/java/com/ruoyi/collaborativeApproval/dto/SealApplicationManagementDTO.java
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java
src/main/java/com/ruoyi/collaborativeApproval/service/impl/MeetingServiceImpl.java
src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java
src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java
src/main/java/com/ruoyi/collaborativeApproval/vo/SearchMeetingUseVo.java
src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java
src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java
src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java
src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java (已删除)
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java (已删除)
src/main/java/com/ruoyi/common/enums/TypeEnums.java
src/main/java/com/ruoyi/common/utils/DateUtils.java
src/main/java/com/ruoyi/common/utils/OrderUtils.java
src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
src/main/java/com/ruoyi/device/controller/DeviceLedgerController.java
src/main/java/com/ruoyi/device/controller/DeviceRepairController.java
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java
src/main/java/com/ruoyi/device/mapper/DeviceLedgerMapper.java
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
src/main/java/com/ruoyi/device/service/IDeviceLedgerService.java
src/main/java/com/ruoyi/device/service/IDeviceRepairService.java
src/main/java/com/ruoyi/device/service/impl/DeviceLedgerServiceImpl.java
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
src/main/java/com/ruoyi/framework/config/ApplicationConfig.java
src/main/java/com/ruoyi/framework/security/LoginUser.java
src/main/java/com/ruoyi/home/controller/HomeController.java
src/main/java/com/ruoyi/home/dto/StatisticsReceivablePayableDto.java
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
src/main/java/com/ruoyi/mock/controller/DataCheckController.java
src/main/java/com/ruoyi/mock/dto/DataCheckRequest.java
src/main/java/com/ruoyi/mock/dto/DataGenerateRequest.java
src/main/java/com/ruoyi/mock/prompt/MockDataPrompt.java
src/main/java/com/ruoyi/mock/service/DataCheckService.java
src/main/java/com/ruoyi/mock/service/DataGenerateService.java
src/main/java/com/ruoyi/mock/service/impl/DataCheckServiceImpl.java
src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java
src/main/java/com/ruoyi/mock/vo/DataCheckResult.java
src/main/java/com/ruoyi/mock/vo/DataGenerateResult.java
src/main/java/com/ruoyi/oA/controller/OaProjectController.java (已删除)
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseController.java (已删除)
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseTaskController.java (已删除)
src/main/java/com/ruoyi/oA/dto/OaProjectDto.java (已删除)
src/main/java/com/ruoyi/oA/dto/OaProjectPhaseDto.java (已删除)
src/main/java/com/ruoyi/oA/mapper/OaProjectMapper.java (已删除)
src/main/java/com/ruoyi/oA/mapper/OaProjectPhaseMapper.java (已删除)
src/main/java/com/ruoyi/oA/mapper/OaProjectPhaseTaskMapper.java (已删除)
src/main/java/com/ruoyi/oA/pojo/OaProject.java (已删除)
src/main/java/com/ruoyi/oA/pojo/OaProjectPhase.java (已删除)
src/main/java/com/ruoyi/oA/pojo/OaProjectPhaseTask.java (已删除)
src/main/java/com/ruoyi/oA/service/OaProjectPhaseService.java (已删除)
src/main/java/com/ruoyi/oA/service/OaProjectPhaseTaskService.java (已删除)
src/main/java/com/ruoyi/oA/service/OaProjectService.java (已删除)
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseServiceImpl.java (已删除)
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseTaskServiceImpl.java (已删除)
src/main/java/com/ruoyi/oA/service/impl/OaProjectServiceImpl.java (已删除)
src/main/java/com/ruoyi/procurementrecord/bean/dto/Details.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/InventoryInformationDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementAddDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementManagementUpdateDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementPageDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementPageDtoCopy.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementRecordOutAdd.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementRecordOutPageDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementUpdateDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnManagementDto.java
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingInfoVo.java
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementExceptionRecordController.java
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java
src/main/java/com/ruoyi/procurementrecord/dto/Details.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/InventoryInformationDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementAddDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementManagementUpdateDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDtoCopy.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutAdd.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutPageDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementUpdateDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ReturnManagementDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/dto/ReturnSaleProductDto.java (已删除)
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnSaleProductMapper.java
src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java
src/main/java/com/ruoyi/procurementrecord/service/ReturnManagementService.java
src/main/java/com/ruoyi/procurementrecord/service/ReturnSaleProductService.java
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java
src/main/java/com/ruoyi/production/bean/dto/ProductionOperationTaskDto.java
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanImportDto.java
src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java
src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java
src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java
src/main/java/com/ruoyi/production/controller/ProductionPlanController.java
src/main/java/com/ruoyi/production/enums/ProductOrderStatusEnum.java
src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java
src/main/java/com/ruoyi/production/pojo/ProductionAccount.java
src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java
src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java
src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java
src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java
src/main/java/com/ruoyi/production/service/ProductionPlanService.java
src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java
src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java
src/main/java/com/ruoyi/production/util/TaskPlanQuantityUtil.java
src/main/java/com/ruoyi/project/common/CaptchaController.java
src/main/java/com/ruoyi/project/monitor/controller/CacheController.java
src/main/java/com/ruoyi/project/monitor/controller/ServerController.java
src/main/java/com/ruoyi/project/monitor/controller/SysJobController.java
src/main/java/com/ruoyi/project/monitor/controller/SysJobLogController.java
src/main/java/com/ruoyi/project/monitor/controller/SysLogininforController.java
src/main/java/com/ruoyi/project/monitor/controller/SysOperlogController.java
src/main/java/com/ruoyi/project/monitor/controller/SysUserOnlineController.java
src/main/java/com/ruoyi/project/system/controller/SysConfigController.java
src/main/java/com/ruoyi/project/system/controller/SysDeptController.java
src/main/java/com/ruoyi/project/system/controller/SysDictDataController.java
src/main/java/com/ruoyi/project/system/controller/SysDictTypeController.java
src/main/java/com/ruoyi/project/system/controller/SysLoginController.java
src/main/java/com/ruoyi/project/system/controller/SysMenuController.java
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java
src/main/java/com/ruoyi/project/system/controller/SysPostController.java
src/main/java/com/ruoyi/project/system/controller/SysProfileController.java
src/main/java/com/ruoyi/project/system/controller/SysRegisterController.java
src/main/java/com/ruoyi/project/system/controller/SysRoleController.java
src/main/java/com/ruoyi/project/system/controller/SysUserController.java
src/main/java/com/ruoyi/project/system/domain/SysUser.java
src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java
src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java
src/main/java/com/ruoyi/project/system/service/ISysUserService.java
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
src/main/java/com/ruoyi/project/system/service/impl/SysUserServiceImpl.java
src/main/java/com/ruoyi/project/tool/gen/controller/GenController.java
src/main/java/com/ruoyi/projectManagement/controller/RolesController.java
src/main/java/com/ruoyi/projectManagement/pojo/Roles.java
src/main/java/com/ruoyi/projectManagement/service/impl/PlanServiceImpl.java
src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoStageHandleService.java
src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java
src/main/java/com/ruoyi/purchase/controller/InvoicePurchaseController.java (已删除)
src/main/java/com/ruoyi/purchase/controller/PaymentRegistrationController.java (已删除)
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java (已删除)
src/main/java/com/ruoyi/purchase/dto/InvoicePurchaseDto.java (已删除)
src/main/java/com/ruoyi/purchase/dto/InvoicePurchaseReportDto.java (已删除)
src/main/java/com/ruoyi/purchase/dto/PaymentRegistrationDto.java (已删除)
src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java (已删除)
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderProductsDto.java
src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java
src/main/java/com/ruoyi/purchase/dto/TicketRegistrationDto.java (已删除)
src/main/java/com/ruoyi/purchase/dto/VatDto.java
src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java (已删除)
src/main/java/com/ruoyi/purchase/mapper/PaymentRegistrationMapper.java (已删除)
src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java (已删除)
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java
src/main/java/com/ruoyi/purchase/mapper/TicketRegistrationMapper.java (已删除)
src/main/java/com/ruoyi/purchase/pojo/InvoicePurchase.java (已删除)
src/main/java/com/ruoyi/purchase/pojo/PaymentRegistration.java (已删除)
src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java (已删除)
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrderProducts.java
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java
src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java
src/main/java/com/ruoyi/purchase/pojo/TicketRegistration.java (已删除)
src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java (已删除)
src/main/java/com/ruoyi/purchase/service/IPaymentRegistrationService.java (已删除)
src/main/java/com/ruoyi/purchase/service/IProductRecordService.java (已删除)
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
src/main/java/com/ruoyi/purchase/service/ITicketRegistrationService.java (已删除)
src/main/java/com/ruoyi/purchase/service/PurchaseReportService.java
src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java
src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java (已删除)
src/main/java/com/ruoyi/purchase/service/impl/PaymentRegistrationServiceImpl.java (已删除)
src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java (已删除)
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReportServiceImpl.java
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java
src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java (已删除)
src/main/java/com/ruoyi/purchase/vo/PurchaseReportVo.java
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderProductsDetailVo.java
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java (已删除)
src/main/java/com/ruoyi/purchase/vo/PurchaseStockInProductVo.java
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java
src/main/java/com/ruoyi/quality/controller/QualityInspectParamController.java
src/main/java/com/ruoyi/quality/controller/QualityReportController.java
src/main/java/com/ruoyi/quality/controller/QualityTestStandardBindingController.java
src/main/java/com/ruoyi/quality/controller/QualityTestStandardController.java
src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java
src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java
src/main/java/com/ruoyi/sales/controller/InvoiceLedgerController.java (已删除)
src/main/java/com/ruoyi/sales/controller/InvoiceRegistrationController.java (已删除)
src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java
src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java
src/main/java/com/ruoyi/sales/controller/ReceiptPaymentController.java (已删除)
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java (已删除)
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
src/main/java/com/ruoyi/sales/dto/InvoiceLedgerDto.java (已删除)
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationDto.java (已删除)
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationProductDto.java (已删除)
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentDto.java (已删除)
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentExeclDto.java (已删除)
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentRecordDto.java (已删除)
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
src/main/java/com/ruoyi/sales/dto/SalesLedgerImportDto.java
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductImportDto.java
src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java
src/main/java/com/ruoyi/sales/dto/ShippingApproveDto.java
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
src/main/java/com/ruoyi/sales/excel/InvoiceLedgerExcelDto.java
src/main/java/com/ruoyi/sales/excel/InvoiceRegisAndProductExcelDto.java (已删除)
src/main/java/com/ruoyi/sales/mapper/InvoiceLedgerFileMapper.java (已删除)
src/main/java/com/ruoyi/sales/mapper/InvoiceLedgerMapper.java (已删除)
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationMapper.java (已删除)
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationProductMapper.java (已删除)
src/main/java/com/ruoyi/sales/mapper/ReceiptPaymentMapper.java (已删除)
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
src/main/java/com/ruoyi/sales/mapper/ShipmentApprovalMapper.java (已删除)
src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java
src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java
src/main/java/com/ruoyi/sales/pojo/InvoiceLedger.java (已删除)
src/main/java/com/ruoyi/sales/pojo/InvoiceLedgerFile.java (已删除)
src/main/java/com/ruoyi/sales/pojo/InvoiceRegistration.java (已删除)
src/main/java/com/ruoyi/sales/pojo/InvoiceRegistrationProduct.java (已删除)
src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java
src/main/java/com/ruoyi/sales/pojo/ReceiptPayment.java (已删除)
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java
src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java
src/main/java/com/ruoyi/sales/pojo/ShipmentApproval.java (已删除)
src/main/java/com/ruoyi/sales/service/ISalesLedgerProductService.java
src/main/java/com/ruoyi/sales/service/InvoiceLedgerService.java (已删除)
src/main/java/com/ruoyi/sales/service/InvoiceRegistrationService.java (已删除)
src/main/java/com/ruoyi/sales/service/ReceiptPaymentService.java (已删除)
src/main/java/com/ruoyi/sales/service/ShipmentApprovalService.java (已删除)
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
src/main/java/com/ruoyi/sales/service/impl/InvoiceLedgerServiceImpl.java (已删除)
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java (已删除)
src/main/java/com/ruoyi/sales/service/impl/ReceiptPaymentServiceImpl.java (已删除)
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
src/main/java/com/ruoyi/sales/service/impl/ShipmentApprovalServiceImpl.java (已删除)
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
src/main/java/com/ruoyi/staff/controller/PersonalShiftController.java
src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
src/main/java/com/ruoyi/staff/controller/StaffSalaryMainController.java
src/main/java/com/ruoyi/staff/dto/StaffLeaveDto.java
src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java
src/main/java/com/ruoyi/staff/dto/StaffSalaryCalculateDto.java
src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java
src/main/java/com/ruoyi/staff/init/DataInitializer.java
src/main/java/com/ruoyi/staff/mapper/SchemeApplicableStaffMapper.java
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
src/main/java/com/ruoyi/staff/mapper/StaffSalaryMainMapper.java
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java
src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java
src/main/java/com/ruoyi/staff/service/StaffSalaryMainService.java
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java
src/main/java/com/ruoyi/stock/execl/StockInRecordExportData.java
src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java
src/main/java/com/ruoyi/stock/execl/StockOutRecordExportData.java
src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java
src/main/java/com/ruoyi/stock/mapper/StockInRecordMapper.java
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
src/main/java/com/ruoyi/stock/service/StockInRecordService.java
src/main/java/com/ruoyi/stock/service/StockInventoryService.java
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java
src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java
src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java
src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java
src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java
src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java
src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java
src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java
src/main/java/com/ruoyi/technology/service/TechnologyBomService.java
src/main/java/com/ruoyi/technology/service/TechnologyOperationService.java
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java
src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
src/main/java/com/ruoyi/warehouse/controller/DocumentationFileController.java
src/main/java/com/ruoyi/warehouse/mapper/DocumentationFileMapper.java
src/main/java/com/ruoyi/warehouse/mapper/DocumentationMapper.java
src/main/java/com/ruoyi/warehouse/service/DocumentationFileService.java
src/main/resources/application-btw.yml
src/main/resources/application-ckgm.yml
src/main/resources/application-dev.yml
src/main/resources/application-hqjc.yml
src/main/resources/application-hthb.yml
src/main/resources/application-jfhy.yml
src/main/resources/application-khyy.yml
src/main/resources/application-lsy.yml
src/main/resources/application-myhb-pro.yml
src/main/resources/application-nhxcl.yml
src/main/resources/application-qfsw.yml
src/main/resources/application-qlmc-pro.yml
src/main/resources/application-rfsy.yml
src/main/resources/application-xhks.yml
src/main/resources/application-xlgs.yml
src/main/resources/application-yss.yml
src/main/resources/application-zdjc.yml
src/main/resources/application.yml
src/main/resources/approve-todo-agent-prompt.txt
src/main/resources/financial-agent-prompt.txt
src/main/resources/logback.xml
src/main/resources/manufacturing-agent-prompt.txt
src/main/resources/mapper/account/AccountExpenseMapper.xml (已删除)
src/main/resources/mapper/account/AccountFileMapper.xml (已删除)
src/main/resources/mapper/account/AccountIncomeMapper.xml (已删除)
src/main/resources/mapper/account/AccountStatementMapper.xml
src/main/resources/mapper/account/BorrowInfoMapper.xml (已删除)
src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml
src/main/resources/mapper/account/financial/AccountSubjectMapper.xml
src/main/resources/mapper/account/financial/FinVoucherEntryMapper.xml
src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml
src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml
src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml
src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml
src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml
src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml
src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml
src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml
src/main/resources/mapper/approve/ApprovalInstanceMapper.xml
src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml
src/main/resources/mapper/approve/ApprovalRecordMapper.xml
src/main/resources/mapper/approve/ApprovalTaskMapper.xml
src/main/resources/mapper/approve/ApprovalTemplateMapper.xml
src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml
src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml
src/main/resources/mapper/approve/ApproveProcessMapper.xml
src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml
src/main/resources/mapper/approve/FinReimbursementMapper.xml
src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml
src/main/resources/mapper/approve/KnowledgeBaseMapper.xml
src/main/resources/mapper/basic/CustomerMapper.xml
src/main/resources/mapper/basic/ProductModelMapper.xml
src/main/resources/mapper/basic/SupplierManageMapper.xml
src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.xml
src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml
src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml
src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml
src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml
src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
src/main/resources/mapper/device/DeviceLedgerMapper.xml
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
src/main/resources/mapper/device/DeviceRepairMapper.xml
src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml
src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml
src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml
src/main/resources/mapper/oA/OaProjectMapper.xml (已删除)
src/main/resources/mapper/oA/OaProjectPhaseMapper.xml (已删除)
src/main/resources/mapper/oA/OaProjectPhaseTaskMapper.xml (已删除)
src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml
src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
src/main/resources/mapper/production/ProductionAccountMapper.xml
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
src/main/resources/mapper/production/ProductionOrderMapper.xml
src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml
src/main/resources/mapper/production/ProductionPlanMapper.xml
src/main/resources/mapper/production/ProductionProductMainMapper.xml
src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml (已删除)
src/main/resources/mapper/purchase/PaymentRegistrationMapper.xml (已删除)
src/main/resources/mapper/purchase/ProductRecordMapper.xml (已删除)
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
src/main/resources/mapper/quality/QualityInspectMapper.xml
src/main/resources/mapper/quality/QualityTestStandardMapper.xml
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
src/main/resources/mapper/sales/InvoiceLedgerMapper.xml (已删除)
src/main/resources/mapper/sales/InvoiceRegistrationMapper.xml (已删除)
src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml (已删除)
src/main/resources/mapper/sales/ReceiptPaymentMapper.xml (已删除)
src/main/resources/mapper/sales/SalesLedgerMapper.xml
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
src/main/resources/mapper/sales/SalesQuotationMapper.xml
src/main/resources/mapper/sales/ShipmentApprovalMapper.xml (已删除)
src/main/resources/mapper/sales/ShippingInfoMapper.xml
src/main/resources/mapper/sales/ShippingProductDetailMapper.xml
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
src/main/resources/mapper/staff/PersonalShiftMapper.xml
src/main/resources/mapper/staff/SchemeApplicableStaffMapper.xml
src/main/resources/mapper/staff/StaffLeaveMapper.xml
src/main/resources/mapper/staff/StaffOnJobMapper.xml
src/main/resources/mapper/staff/StaffSalaryMainMapper.xml
src/main/resources/mapper/stock/StockInRecordMapper.xml
src/main/resources/mapper/stock/StockInventoryMapper.xml
src/main/resources/mapper/stock/StockOutRecordMapper.xml
src/main/resources/mapper/system/SysDeptMapper.xml
src/main/resources/mapper/system/SysDictTypeMapper.xml
src/main/resources/mapper/system/SysNoticeMapper.xml
src/main/resources/mapper/system/SysPostMapper.xml
src/main/resources/mapper/system/SysRoleMapper.xml
src/main/resources/mapper/system/SysUserMapper.xml
src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml
src/main/resources/mapper/technology/TechnologyOperationMapper.xml
src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml
src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml
src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml
src/main/resources/mapper/warehouse/DocumentationMapper.xml
src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml
src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml
src/main/resources/purchase-agent-prompt.txt
src/main/resources/sales-agent-prompt.txt
src/main/resources/static/销售台账导入模板.xlsx |