From 1af5b3cbd3d1090541706d643d5937714b553c27 Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Wed, 24 Jun 2026 09:13:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=93=E9=80=9A=E5=87=BA=E5=BA=93?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + AGENTS.md | 32 + .../2026-06-23-stock-master-detail-api-log.md | 857 ++++ pda/api/warehouse/inbound.js | 46 +- pda/api/warehouse/outbound.js | 8 +- pda/manifest.json | 4 +- pda/pages/warehouse/inbound/bindTray.vue | 694 +++- pda/pages/warehouse/inbound/index.vue | 55 +- ruoyi-admin/pom.xml | 9 +- .../com/ruoyi/api/aspect/ApiLogAspect.java | 175 + .../ruoyi/api/client/BaseExternalClient.java | 27 +- .../java/com/ruoyi/api/client/WcsClient.java | 59 + .../api/controller/ExternalApiController.java | 115 + .../java/com/ruoyi/api/domain/ApiLog.java | 12 + .../api/domain/WcsInboundCompleteRequest.java | 62 + .../api/domain/WcsInboundLocationRequest.java | 61 + .../domain/WcsOutboundCompleteRequest.java | 51 + .../com/ruoyi/api/mapper/ApiLogMapper.java | 13 + .../com/ruoyi/api/service/ApiLogService.java | 33 +- .../com/ruoyi/api/task/ApiLogCleanupTask.java | 27 + .../controller/BasBarcodeDataController.java | 12 + .../data/mapper/BasBarcodeDataMapper.java | 17 + .../data/service/IBasBarcodeDataService.java | 8 + .../impl/BasBarcodeDataServiceImpl.java | 105 + .../ruoyi/bas/product/domain/BasProduct.java | 513 ++- .../controller/BasStationPointController.java | 105 + .../stationpoint/domain/BasStationPoint.java | 153 + .../mapper/BasStationPointMapper.java | 32 + .../service/IBasStationPointService.java | 32 + .../impl/BasStationPointServiceImpl.java | 79 + .../stock/controller/BasStockController.java | 56 + .../com/ruoyi/bas/stock/domain/BasStock.java | 30 + .../bas/stock/domain/BasStockDetail.java | 281 ++ .../stock/mapper/BasStockDetailMapper.java | 26 + .../bas/stock/mapper/BasStockMapper.java | 8 + .../bas/stock/service/IBasStockService.java | 41 + .../service/impl/BasStockServiceImpl.java | 191 +- .../controller/BasTrayRuleController.java | 105 + .../bas/trayrule/domain/BasTrayRule.java | 163 + .../trayrule/mapper/BasTrayRuleMapper.java | 61 + .../trayrule/service/IBasTrayRuleService.java | 61 + .../service/impl/BasTrayRuleServiceImpl.java | 82 + .../ruoyi/in/job/mapper/MydoJobMapper.java | 56 + .../ruoyi/in/job/service/IMydoJobService.java | 78 + .../job/service/impl/MydoJobServiceImpl.java | 227 +- .../sotckindetail/domain/InStockinDetail.java | 165 + .../ruoyi/in/stockin/domain/InStockin.java | 334 ++ .../in/stockin/mapper/InStockinMapper.java | 20 + .../kpi/service/impl/KpiServiceImpl.java | 12 +- .../job/controller/OutMydoJobController.java | 11 + .../out/stockout/domain/OutStockout.java | 340 +- .../service/impl/OutStockoutServiceImpl.java | 47 +- .../domain/OutStockoutDetail.java | 193 + .../ruoyi/pda/controller/PdaController.java | 126 +- .../com/ruoyi/pda/domain/AddInJobRequest.java | 22 + .../com/ruoyi/pda/domain/BindTrayRequest.java | 12 + .../java/com/ruoyi/pda/mapper/PdaMapper.java | 51 + .../com/ruoyi/pda/service/PdaService.java | 85 + .../pda/service/impl/PdaServiceImpl.java | 396 +- .../src/main/resources/application-druid.yml | 2 +- .../src/main/resources/application.yml | 6 +- .../resources/mapper/api/ApiLogMapper.xml | 22 + .../mapper/bas/BasBarcodeDataMapper.xml | 22 +- .../resources/mapper/bas/BasProductMapper.xml | 146 +- .../mapper/bas/BasStationPointMapper.xml | 127 + .../mapper/bas/BasStockDetailMapper.xml | 148 + .../resources/mapper/bas/BasStockMapper.xml | 81 +- .../mapper/bas/BasTrayRuleMapper.xml | 111 + .../mapper/in/InStockinDetailMapper.xml | 130 +- .../resources/mapper/in/InStockinMapper.xml | 146 +- .../mapper/in/MydoJobDetailMapper.xml | 21 +- .../resources/mapper/in/MydoJobMapper.xml | 82 +- .../mapper/out/OutStockoutDetailMapper.xml | 99 +- .../mapper/out/OutStockoutMapper.xml | 142 +- .../main/resources/mapper/pda/PdaMapper.xml | 134 +- .../com/ruoyi/api/client/WcsClientTest.java | 70 + .../service/impl/MydoJobServiceImplTest.java | 94 + .../com/ruoyi/common/annotation/ApiLog.java | 36 + .../framework/config/SecurityConfig.java | 2 +- ruoyi-ui/package.json | 1 + ruoyi-ui/src/api/bas/barcodeData.js | 9 + ruoyi-ui/src/api/bas/stationpoint.js | 44 + ruoyi-ui/src/api/bas/stock.js | 43 + ruoyi-ui/src/api/bas/trayRule.js | 44 + ruoyi-ui/src/api/out/job.js | 8 + ruoyi-ui/src/views/bas/barcodeData/index.vue | 277 +- .../src/views/bas/barcodeFormat/index.vue | 48 +- ruoyi-ui/src/views/bas/product/index.vue | 336 +- ruoyi-ui/src/views/bas/stationpoint/index.vue | 361 ++ ruoyi-ui/src/views/bas/stock/index.vue | 316 +- ruoyi-ui/src/views/bas/trayRule/index.vue | 252 ++ ruoyi-ui/src/views/in/add/index.vue | 94 +- ruoyi-ui/src/views/in/job/index.vue | 4 +- ruoyi-ui/src/views/in/stockin/index.vue | 164 +- ruoyi-ui/src/views/out/create/index.vue | 340 +- ruoyi-ui/src/views/out/job/index.vue | 23 +- ruoyi-ui/src/views/out/stockout/index.vue | 16 +- ruoyi-ui/src/views/pd/create/index.vue | 15 +- scripts/apply-sql-file.ps1 | 75 + scripts/update_bill_schema.js | 122 + sql/bas_api_log.sql | 182 + sql/bas_config_menus.sql | 81 + sql/bas_station_point.sql | 77 + sql/bas_stock_master_detail.sql | 255 ++ sql/bas_tray_rule.sql | 71 + sql/out_stockout_flow.sql | 904 +++++ sql/proc_add_in_job_trace.sql | 126 + sql/proc_bind_tray_msg_trace.sql | 164 + sql/proc_hand_delete_in_job.sql | 85 + tools/mcp/sqlserver-mcp-server/README.md | 39 + .../mcp/sqlserver-mcp-server/dist/config.d.ts | 17 + .../sqlserver-mcp-server/dist/config.d.ts.map | 1 + tools/mcp/sqlserver-mcp-server/dist/config.js | 70 + .../sqlserver-mcp-server/dist/config.js.map | 1 + tools/mcp/sqlserver-mcp-server/dist/db.d.ts | 21 + .../mcp/sqlserver-mcp-server/dist/db.d.ts.map | 1 + tools/mcp/sqlserver-mcp-server/dist/db.js | 81 + tools/mcp/sqlserver-mcp-server/dist/db.js.map | 1 + .../mcp/sqlserver-mcp-server/dist/index.d.ts | 3 + .../sqlserver-mcp-server/dist/index.d.ts.map | 1 + tools/mcp/sqlserver-mcp-server/dist/index.js | 52 + .../sqlserver-mcp-server/dist/index.js.map | 1 + tools/mcp/sqlserver-mcp-server/dist/sql.d.ts | 15 + .../sqlserver-mcp-server/dist/sql.d.ts.map | 1 + tools/mcp/sqlserver-mcp-server/dist/sql.js | 86 + .../mcp/sqlserver-mcp-server/dist/sql.js.map | 1 + .../mcp/sqlserver-mcp-server/dist/tools.d.ts | 4 + .../sqlserver-mcp-server/dist/tools.d.ts.map | 1 + tools/mcp/sqlserver-mcp-server/dist/tools.js | 533 +++ .../sqlserver-mcp-server/dist/tools.js.map | 1 + .../sqlserver-mcp-server/package-lock.json | 3434 +++++++++++++++++ tools/mcp/sqlserver-mcp-server/package.json | 26 + tools/mcp/sqlserver-mcp-server/src/config.ts | 95 + tools/mcp/sqlserver-mcp-server/src/db.ts | 104 + tools/mcp/sqlserver-mcp-server/src/index.ts | 59 + tools/mcp/sqlserver-mcp-server/src/sql.ts | 121 + tools/mcp/sqlserver-mcp-server/src/tools.ts | 661 ++++ tools/mcp/sqlserver-mcp-server/start.ps1 | 42 + .../sqlserver-mcp-server/tests/sql.test.ts | 65 + tools/mcp/sqlserver-mcp-server/tsconfig.json | 20 + .../mcp/sqlserver-mcp-server/vitest.config.ts | 8 + 141 files changed, 17337 insertions(+), 1065 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/superpowers/plans/2026-06-23-stock-master-detail-api-log.md create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/api/aspect/ApiLogAspect.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsInboundCompleteRequest.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsInboundLocationRequest.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsOutboundCompleteRequest.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/api/mapper/ApiLogMapper.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/api/task/ApiLogCleanupTask.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stationpoint/controller/BasStationPointController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stationpoint/domain/BasStationPoint.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stationpoint/mapper/BasStationPointMapper.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stationpoint/service/IBasStationPointService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stationpoint/service/impl/BasStationPointServiceImpl.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stock/domain/BasStockDetail.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/stock/mapper/BasStockDetailMapper.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/trayrule/controller/BasTrayRuleController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/trayrule/domain/BasTrayRule.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/trayrule/mapper/BasTrayRuleMapper.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/trayrule/service/IBasTrayRuleService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/bas/trayrule/service/impl/BasTrayRuleServiceImpl.java create mode 100644 ruoyi-admin/src/main/resources/mapper/api/ApiLogMapper.xml create mode 100644 ruoyi-admin/src/main/resources/mapper/bas/BasStationPointMapper.xml create mode 100644 ruoyi-admin/src/main/resources/mapper/bas/BasStockDetailMapper.xml create mode 100644 ruoyi-admin/src/main/resources/mapper/bas/BasTrayRuleMapper.xml create mode 100644 ruoyi-admin/src/test/java/com/ruoyi/api/client/WcsClientTest.java create mode 100644 ruoyi-admin/src/test/java/com/ruoyi/in/job/service/impl/MydoJobServiceImplTest.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/ApiLog.java create mode 100644 ruoyi-ui/src/api/bas/stationpoint.js create mode 100644 ruoyi-ui/src/api/bas/trayRule.js create mode 100644 ruoyi-ui/src/views/bas/stationpoint/index.vue create mode 100644 ruoyi-ui/src/views/bas/trayRule/index.vue create mode 100644 scripts/apply-sql-file.ps1 create mode 100644 scripts/update_bill_schema.js create mode 100644 sql/bas_api_log.sql create mode 100644 sql/bas_config_menus.sql create mode 100644 sql/bas_station_point.sql create mode 100644 sql/bas_stock_master_detail.sql create mode 100644 sql/bas_tray_rule.sql create mode 100644 sql/out_stockout_flow.sql create mode 100644 sql/proc_add_in_job_trace.sql create mode 100644 sql/proc_bind_tray_msg_trace.sql create mode 100644 sql/proc_hand_delete_in_job.sql create mode 100644 tools/mcp/sqlserver-mcp-server/README.md create mode 100644 tools/mcp/sqlserver-mcp-server/dist/config.d.ts create mode 100644 tools/mcp/sqlserver-mcp-server/dist/config.d.ts.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/config.js create mode 100644 tools/mcp/sqlserver-mcp-server/dist/config.js.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/db.d.ts create mode 100644 tools/mcp/sqlserver-mcp-server/dist/db.d.ts.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/db.js create mode 100644 tools/mcp/sqlserver-mcp-server/dist/db.js.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/index.d.ts create mode 100644 tools/mcp/sqlserver-mcp-server/dist/index.d.ts.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/index.js create mode 100644 tools/mcp/sqlserver-mcp-server/dist/index.js.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/sql.d.ts create mode 100644 tools/mcp/sqlserver-mcp-server/dist/sql.d.ts.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/sql.js create mode 100644 tools/mcp/sqlserver-mcp-server/dist/sql.js.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/tools.d.ts create mode 100644 tools/mcp/sqlserver-mcp-server/dist/tools.d.ts.map create mode 100644 tools/mcp/sqlserver-mcp-server/dist/tools.js create mode 100644 tools/mcp/sqlserver-mcp-server/dist/tools.js.map create mode 100644 tools/mcp/sqlserver-mcp-server/package-lock.json create mode 100644 tools/mcp/sqlserver-mcp-server/package.json create mode 100644 tools/mcp/sqlserver-mcp-server/src/config.ts create mode 100644 tools/mcp/sqlserver-mcp-server/src/db.ts create mode 100644 tools/mcp/sqlserver-mcp-server/src/index.ts create mode 100644 tools/mcp/sqlserver-mcp-server/src/sql.ts create mode 100644 tools/mcp/sqlserver-mcp-server/src/tools.ts create mode 100644 tools/mcp/sqlserver-mcp-server/start.ps1 create mode 100644 tools/mcp/sqlserver-mcp-server/tests/sql.test.ts create mode 100644 tools/mcp/sqlserver-mcp-server/tsconfig.json create mode 100644 tools/mcp/sqlserver-mcp-server/vitest.config.ts diff --git a/.gitignore b/.gitignore index c887fa9..5ce76b6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,7 @@ bin-release/ ruoyi-ui/build/** ruoyi-ui/dist/** ruoyi-ui/node_modules/** +tools/mcp/sqlserver-mcp-server/node_modules/** +.cursorrules-mcp.json /ruoyi-ui/dist.zip /RuoYi-Vue-master.zip diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..845155d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +This repository is a RuoYi-based WMS application. The Java backend is a Maven multi-module project rooted at `pom.xml`: `ruoyi-admin` is the Spring Boot entry point; `ruoyi-framework`, `ruoyi-system`, `ruoyi-quartz`, `ruoyi-generator`, and `ruoyi-common` hold framework, system, scheduled-job, generator, and utility code. Resources, MyBatis XML, and configuration live under each module's `src/main/resources`. + +The Vue 2 admin web app is in `ruoyi-ui`. PDA/mobile uni-app code is in `pda`, with `pages`, `api`, `store`, `utils`, and `static`. Database setup and migration helpers are in `sql` and `scripts`. + +## Build, Test, and Development Commands + +- `mvn clean package`: build all backend modules with Java 17. +- `mvn spring-boot:run -pl ruoyi-admin`: run the backend locally. +- `cd ruoyi-ui && npm install`: install dependencies from `package-lock.json`. +- `cd ruoyi-ui && npm run dev`: start the Vue admin UI, normally at `http://localhost:80`. +- `cd ruoyi-ui && npm run build:prod`: create the production UI build. +- `./ry.sh` or `ry.bat`: use bundled RuoYi startup scripts for upstream-style deployment. + +## Coding Style & Naming Conventions + +Use UTF-8. The admin UI `.editorconfig` sets two-space indentation, LF endings, final newlines, and trimmed trailing whitespace; follow that style for JavaScript, Vue, SCSS, JSON, and Markdown. Java packages stay under `com.ruoyi`; keep the RuoYi layering pattern: `controller`, `domain`, `mapper`, `service`, and `service.impl`. Name Java classes by role, for example `BasWarehouseController`, `IBasWarehouseService`, and `BasWarehouseMapper`. Keep MyBatis XML names aligned with mapper interfaces. + +## Testing Guidelines + +No project-owned test suite is currently present. For backend changes, add JUnit tests under the affected module's `src/test/java` and run `mvn test` or `mvn -pl test`. For UI changes, at minimum run `npm run build:prod`; add component or integration tests if introducing test tooling. For SQL changes, verify scripts against a disposable schema before review. + +## Commit & Pull Request Guidelines + +Git history currently contains only the initial commit (`项目初始化`), so keep future commits short, imperative, and scoped, for example `fix: validate tray binding status` or `feat: add stock detail export`. Pull requests should describe the change, list backend/UI/SQL impact, include verification commands, link issues when available, and attach screenshots for visible UI or PDA changes. + +## Security & Configuration Tips + +Do not commit real credentials, tokens, or production endpoints. Keep values in `ruoyi-admin/src/main/resources/application*.yml`, `ruoyi-ui/.env.*`, and `pda/config.js` sanitized before sharing. Treat SQL scripts as deployable artifacts: make them repeatable when possible and clearly name destructive operations. diff --git a/docs/superpowers/plans/2026-06-23-stock-master-detail-api-log.md b/docs/superpowers/plans/2026-06-23-stock-master-detail-api-log.md new file mode 100644 index 0000000..611f1a3 --- /dev/null +++ b/docs/superpowers/plans/2026-06-23-stock-master-detail-api-log.md @@ -0,0 +1,857 @@ +# Stock Master Detail And API Log Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Split inventory into master/detail storage, fix completed inbound job child-detail lookup, and persist inbound/outbound external API logs in `bas_api_log` with 30-day retention. + +**Architecture:** Use a compatibility migration for inventory: keep `bas_stock` as the master/current compatibility table during this phase, add `bas_stock_detail` for material lines, and update new backend/frontend screens to read master/detail. Stored procedure stock posting writes both master and detail so existing outbound, stocktake, and dashboard readers do not break while the UI moves to the new structure. API logs use an `@ApiLog` annotation for inbound external callbacks and the existing `BaseExternalClient` choke point for outbound calls, both writing to the same table. + +**Tech Stack:** SQL Server, Spring Boot, MyBatis XML, RuoYi controllers/services, Vue2 + Element UI, UniApp PDA where relevant. + +--- + +## File Structure + +**Database scripts** +- Create: `sql/bas_stock_master_detail.sql` — creates `bas_stock_detail`, backfills existing stock material lines, adds indexes. +- Create: `sql/proc_complete_in_job_stock_detail.sql` — updates `proc_complete_in_job` stock insertion section so inbound completion writes `bas_stock` and `bas_stock_detail`. +- Create: `sql/bas_api_log.sql` — creates `bas_api_log`, indexes, and cleanup procedure `proc_clean_bas_api_log`. + +**Stock backend** +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/domain/BasStockDetail.java` — material-line inventory detail entity. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/mapper/BasStockDetailMapper.java` — detail mapper contract. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/service/IBasStockDetailService.java` — detail service contract. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/service/impl/BasStockDetailServiceImpl.java` — detail service implementation. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/controller/BasStockDetailController.java` — `/bas/stockdetail/list` endpoint for detail rows under a master `stockId`. +- Create: `ruoyi-admin/src/main/resources/mapper/bas/BasStockDetailMapper.xml` — detail SQL mapping. +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/bas/stock/domain/BasStock.java` — add `List details` for master/detail responses while keeping legacy query/export fields for compatibility. +- Modify: `ruoyi-admin/src/main/resources/mapper/bas/BasStockMapper.xml` — make main list return one master stock row per `stock_id`; stop using product fields as master columns in the main list. +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/bas/stock/service/impl/BasStockServiceImpl.java` — insert/update/delete master and detail consistently for manual stock edits. + +**Stock frontend** +- Create: `ruoyi-ui/src/api/bas/stockDetail.js` — stock detail API calls. +- Modify: `ruoyi-ui/src/views/bas/stock/index.vue` — show master inventory in the top table and a material-detail table below the selected master row. + +**Completed inbound job detail fix** +- Modify: `ruoyi-admin/src/main/resources/mapper/in/MydoJobDetailMapper.xml` — make `selectMydoJobDetailFinishList` filter by `job_id`, mirroring the active detail query. +- Modify: `ruoyi-ui/src/views/in/job/index.vue` — pass selected row status to detail query instead of relying on the global query status. + +**API logging** +- Create: `ruoyi-common/src/main/java/com/ruoyi/common/annotation/ApiLog.java` — `@Log`-style annotation. +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/domain/ApiLog.java` — add direction, request method, source IP, trace ID fields. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/api/mapper/ApiLogMapper.java` — persistence contract. +- Create: `ruoyi-admin/src/main/resources/mapper/api/ApiLogMapper.xml` — insert and retention SQL. +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/service/ApiLogService.java` — persist to mapper instead of only printing local logs. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/api/aspect/ApiLogAspect.java` — annotation aspect for inbound external callbacks. +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/controller/ExternalApiController.java` — annotate WCS/ERP/MES external endpoints that are called by outside systems. +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/client/BaseExternalClient.java` — ensure outbound logs include `direction=OUTBOUND` and write to `bas_api_log`. +- Create: `ruoyi-admin/src/main/java/com/ruoyi/api/task/ApiLogCleanupTask.java` — daily retention cleanup for logs older than 30 days. + +--- + +### Task 1: Create Stock Detail Table And Backfill Data + +**Files:** +- Create: `sql/bas_stock_master_detail.sql` + +- [ ] **Step 1: Add migration SQL** + +Create `sql/bas_stock_master_detail.sql` with this content: + +```sql +IF OBJECT_ID(N'dbo.bas_stock_detail', N'U') IS NULL +BEGIN + CREATE TABLE dbo.bas_stock_detail ( + stock_detail_id varchar(80) NOT NULL, + stock_id nvarchar(32) NOT NULL, + product_status nvarchar(2) NULL, + lot_no nvarchar(80) NULL, + product_id nvarchar(80) NULL, + product_name nvarchar(255) NULL, + quantity decimal(18, 0) NULL, + out_quantity decimal(18, 0) NULL, + job_detail_id nvarchar(255) NULL, + stockin_detail_id nvarchar(80) NULL, + create_time datetime2(7) NULL CONSTRAINT DF_bas_stock_detail_create_time DEFAULT GETDATE(), + update_time datetime2(7) NULL, + CONSTRAINT PK_bas_stock_detail PRIMARY KEY CLUSTERED (stock_detail_id) + ); +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'dbo.bas_stock_detail') AND name = N'IX_bas_stock_detail_stock_id') + CREATE INDEX IX_bas_stock_detail_stock_id ON dbo.bas_stock_detail(stock_id); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'dbo.bas_stock_detail') AND name = N'IX_bas_stock_detail_product') + CREATE INDEX IX_bas_stock_detail_product ON dbo.bas_stock_detail(product_id, lot_no) INCLUDE(stock_id, quantity, out_quantity); +GO + +INSERT INTO dbo.bas_stock_detail ( + stock_detail_id, stock_id, product_status, lot_no, product_id, product_name, + quantity, out_quantity, job_detail_id, stockin_detail_id, create_time +) +SELECT + CAST(bs.stock_id AS varchar(80)) + '-1', + bs.stock_id, + bs.product_status, + bs.lot_no, + bs.product_id, + bs.product_name, + bs.quantity, + bs.out_quantity, + bs.job_detail_id, + bs.stockin_detail_id, + ISNULL(bs.in_date, GETDATE()) +FROM dbo.bas_stock bs +WHERE NOT EXISTS ( + SELECT 1 FROM dbo.bas_stock_detail d WHERE d.stock_id = bs.stock_id +) + AND (bs.product_id IS NOT NULL OR bs.product_name IS NOT NULL OR bs.quantity IS NOT NULL); +GO +``` + +- [ ] **Step 2: Apply migration to `FTC25009-WMS`** + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File ".\scripts\apply-sql-file.ps1" -SqlFile ".\sql\bas_stock_master_detail.sql" +``` + +Expected output: + +```text +Applied SQL file to FTC25009-WMS +``` + +- [ ] **Step 3: Verify backfill** + +Run through MCP `read_data`: + +```sql +SELECT TOP 10 stock_id, COUNT(*) AS detail_count +FROM dbo.bas_stock_detail +GROUP BY stock_id +ORDER BY stock_id; +``` + +Expected: existing `bas_stock.stock_id` values have one `bas_stock_detail` row each. + +--- + +### Task 2: Add Stock Detail Backend CRUD Read Path + +**Files:** +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/domain/BasStockDetail.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/mapper/BasStockDetailMapper.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/service/IBasStockDetailService.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/service/impl/BasStockDetailServiceImpl.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/bas/stockdetail/controller/BasStockDetailController.java` +- Create: `ruoyi-admin/src/main/resources/mapper/bas/BasStockDetailMapper.xml` + +- [ ] **Step 1: Create `BasStockDetail` domain** + +```java +package com.ruoyi.bas.stockdetail.domain; + +import java.math.BigDecimal; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +public class BasStockDetail extends BaseEntity { + private static final long serialVersionUID = 1L; + + private String stockDetailId; + private String stockId; + @Excel(name = "物料状态") + private String productStatus; + @Excel(name = "批次号") + private String lotNo; + @Excel(name = "物料编码") + private String productId; + @Excel(name = "物料名称") + private String productName; + @Excel(name = "库存数量") + private BigDecimal quantity; + @Excel(name = "出库数量") + private BigDecimal outQuantity; + @Excel(name = "子作业id") + private String jobDetailId; + @Excel(name = "入库子单id") + private String stockinDetailId; + + public String getStockDetailId() { return stockDetailId; } + public void setStockDetailId(String stockDetailId) { this.stockDetailId = stockDetailId; } + public String getStockId() { return stockId; } + public void setStockId(String stockId) { this.stockId = stockId; } + public String getProductStatus() { return productStatus; } + public void setProductStatus(String productStatus) { this.productStatus = productStatus; } + public String getLotNo() { return lotNo; } + public void setLotNo(String lotNo) { this.lotNo = lotNo; } + public String getProductId() { return productId; } + public void setProductId(String productId) { this.productId = productId; } + public String getProductName() { return productName; } + public void setProductName(String productName) { this.productName = productName; } + public BigDecimal getQuantity() { return quantity; } + public void setQuantity(BigDecimal quantity) { this.quantity = quantity; } + public BigDecimal getOutQuantity() { return outQuantity; } + public void setOutQuantity(BigDecimal outQuantity) { this.outQuantity = outQuantity; } + public String getJobDetailId() { return jobDetailId; } + public void setJobDetailId(String jobDetailId) { this.jobDetailId = jobDetailId; } + public String getStockinDetailId() { return stockinDetailId; } + public void setStockinDetailId(String stockinDetailId) { this.stockinDetailId = stockinDetailId; } +} +``` + +- [ ] **Step 2: Create mapper interface** + +```java +package com.ruoyi.bas.stockdetail.mapper; + +import java.util.List; +import com.ruoyi.bas.stockdetail.domain.BasStockDetail; + +public interface BasStockDetailMapper { + List selectBasStockDetailList(BasStockDetail basStockDetail); + BasStockDetail selectBasStockDetailByStockDetailId(String stockDetailId); + int insertBasStockDetail(BasStockDetail basStockDetail); + int updateBasStockDetail(BasStockDetail basStockDetail); + int deleteBasStockDetailByStockDetailId(String stockDetailId); + int deleteBasStockDetailByStockIds(String[] stockIds); +} +``` + +- [ ] **Step 3: Create mapper XML** + +```xml + + + + + + + + + + + + + + + + + + + + + + insert into bas_stock_detail(stock_detail_id, stock_id, product_status, lot_no, product_id, product_name, quantity, out_quantity, job_detail_id, stockin_detail_id) + values(#{stockDetailId}, #{stockId}, #{productStatus}, #{lotNo}, #{productId}, #{productName}, #{quantity}, #{outQuantity}, #{jobDetailId}, #{stockinDetailId}) + + + + update bas_stock_detail + + product_status = #{productStatus}, + lot_no = #{lotNo}, + product_id = #{productId}, + product_name = #{productName}, + quantity = #{quantity}, + out_quantity = #{outQuantity}, + job_detail_id = #{jobDetailId}, + stockin_detail_id = #{stockinDetailId}, + update_time = getdate(), + + where stock_detail_id = #{stockDetailId} + + + + delete from bas_stock_detail where stock_detail_id = #{stockDetailId} + + + + delete from bas_stock_detail where stock_id in + + #{stockId} + + + +``` + +- [ ] **Step 4: Create service and controller using existing RuoYi pattern** + +Controller endpoint must be: + +```java +@PreAuthorize("@ss.hasPermi('bas:stock:list')") +@GetMapping("/list") +public TableDataInfo list(BasStockDetail basStockDetail) { + startPage(); + List list = basStockDetailService.selectBasStockDetailList(basStockDetail); + return getDataTable(list); +} +``` + +- [ ] **Step 5: Run IDE lints** + +Run lints on the new package and fix imports/package names until there are no diagnostics. + +--- + +### Task 3: Convert Stock UI To Master/Detail + +**Files:** +- Create: `ruoyi-ui/src/api/bas/stockDetail.js` +- Modify: `ruoyi-ui/src/views/bas/stock/index.vue` + +- [ ] **Step 1: Add detail API** + +```js +import request from '@/utils/request' + +export function listStockDetail(query) { + return request({ + url: '/bas/stockdetail/list', + method: 'get', + params: query + }) +} +``` + +- [ ] **Step 2: Update stock page row-click behavior** + +In `ruoyi-ui/src/views/bas/stock/index.vue`, import: + +```js +import { listStockDetail } from '@/api/bas/stockDetail' +``` + +Add state: + +```js +currentStock: null, +stockDetailList: [], +stockDetailLoading: false, +stockDetailTotal: 0, +stockDetailQueryParams: { + pageNum: 1, + pageSize: 10, + stockId: null +} +``` + +Add methods: + +```js +handleStockRowClick(row) { + this.currentStock = row + this.stockDetailQueryParams.pageNum = 1 + this.stockDetailQueryParams.stockId = row.stockId + this.getStockDetailList() +}, +getStockDetailList() { + if (!this.stockDetailQueryParams.stockId) { + this.stockDetailList = [] + this.stockDetailTotal = 0 + return + } + this.stockDetailLoading = true + listStockDetail(this.stockDetailQueryParams).then(response => { + this.stockDetailList = response.rows || [] + this.stockDetailTotal = response.total || 0 + this.stockDetailLoading = false + }).catch(() => { + this.stockDetailList = [] + this.stockDetailTotal = 0 + this.stockDetailLoading = false + }) +} +``` + +- [ ] **Step 3: Adjust top table to show only master fields** + +Top table columns should keep: +- `stockId` +- `cargoSpaceId` +- `stockStatus` +- `trayCode` +- `stockinId` +- `inType` +- `warehouseName` +- `userName` +- `deptName` +- `suplierName` + +Move material columns to the lower detail table: +- `productId` +- `productName` +- `lotNo` +- `quantity` +- `outQuantity` +- `jobDetailId` +- `stockinDetailId` + +- [ ] **Step 4: Verify manually** + +Open inventory page, click a master stock row, and confirm detail rows load from `/bas/stockdetail/list?stockId=...`. + +--- + +### Task 4: Make Inbound Completion Write Stock Master And Detail + +**Files:** +- Create: `sql/proc_complete_in_job_stock_detail.sql` + +- [ ] **Step 1: Replace stock insertion block inside `proc_complete_in_job`** + +Use the current procedure definition from `FTC25009-WMS`. In the cursor loop, replace the existing single `INSERT INTO bas_stock (...) VALUES (...)` block with this block: + +```sql +-- 插入库存主表。主表只保存托盘、货位、仓库、入库来源等通用信息。 +IF NOT EXISTS (SELECT 1 FROM dbo.bas_stock WHERE stock_id = @stock_id) +BEGIN + INSERT INTO dbo.bas_stock ( + stock_id, cargo_space_id, stock_status, in_date, + job_id, tray_code, stockin_id, in_type, warehouse_id + ) + VALUES ( + @stock_id, @cargo_space_id, '1', CONVERT(VARCHAR, GETDATE(), 120), + @job_id, @tray_code, @stockin_id, NULL, @warehouse_id + ); +END; + +-- 插入库存子表。子表保存物料、批次、数量和来源子单。 +INSERT INTO dbo.bas_stock_detail ( + stock_detail_id, stock_id, product_status, lot_no, product_id, product_name, + quantity, out_quantity, job_detail_id, stockin_detail_id, create_time +) +VALUES ( + @stock_id + '-' + CAST(ABS(CHECKSUM(@job_detail_id)) AS varchar(20)), + @stock_id, + '1', + @lot_no, + @product_id, + @product_name, + @in_quantity, + 0, + @job_detail_id, + @stockin_detail_id, + GETDATE() +); +``` + +- [ ] **Step 2: Keep legacy fields during this phase** + +Do not drop old material columns from `bas_stock` in this task. Existing outbound allocation, stocktake, dashboard, and reports still contain direct references to `bas_stock.product_id`, `bas_stock.quantity`, and related fields. Remove those only after all readers are moved to `bas_stock_detail`. + +- [ ] **Step 3: Apply the procedure script** + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File ".\scripts\apply-sql-file.ps1" -SqlFile ".\sql\proc_complete_in_job_stock_detail.sql" +``` + +- [ ] **Step 4: Verify with a controlled inbound completion** + +After completing one inbound job, run: + +```sql +SELECT TOP 5 s.stock_id, s.cargo_space_id, s.tray_code, s.warehouse_id, + d.product_id, d.product_name, d.quantity, d.job_detail_id, d.stockin_detail_id +FROM dbo.bas_stock s +INNER JOIN dbo.bas_stock_detail d ON d.stock_id = s.stock_id +ORDER BY d.create_time DESC; +``` + +Expected: one master stock row with one or more detail material rows. + +--- + +### Task 5: Fix Completed Inbound Job Detail Lookup + +**Files:** +- Modify: `ruoyi-admin/src/main/resources/mapper/in/MydoJobDetailMapper.xml` +- Modify: `ruoyi-ui/src/views/in/job/index.vue` + +- [ ] **Step 1: Fix finished detail SQL** + +Replace `selectMydoJobDetailFinishList` with: + +```xml + +``` + +- [ ] **Step 2: Pass selected row status to child query** + +In `ruoyi-ui/src/views/in/job/index.vue`, update `handleRowClick(row)` so it sets: + +```js +this.detailQueryParams.jobId = row.jobId +this.detailQueryParams.status = row.status +``` + +Do not use `this.queryParams.status` for child query routing because the selected row is the source of truth. + +- [ ] **Step 3: Verify active and completed jobs** + +Use the 入库作业管理 page: +- query active job, click row, child details should load from `mydo_job_detail`. +- query status `4`, click row, child details should load from `mydo_job_detail_finish`. + +--- + +### Task 6: Create `bas_api_log` Table And Retention Procedure + +**Files:** +- Create: `sql/bas_api_log.sql` + +- [ ] **Step 1: Add API log table SQL** + +```sql +IF OBJECT_ID(N'dbo.bas_api_log', N'U') IS NULL +BEGIN + CREATE TABLE dbo.bas_api_log ( + id bigint IDENTITY(1,1) NOT NULL, + direction varchar(16) NOT NULL, + sys_code varchar(50) NULL, + interface_name nvarchar(100) NULL, + request_url nvarchar(500) NULL, + request_method varchar(20) NULL, + request_body nvarchar(max) NULL, + response_body nvarchar(max) NULL, + msg_type varchar(20) NULL, + http_status int NULL, + cost_ms bigint NULL, + status varchar(30) NULL, + error_msg nvarchar(2000) NULL, + source_ip varchar(80) NULL, + trace_id varchar(80) NULL, + create_time datetime2(7) NOT NULL CONSTRAINT DF_bas_api_log_create_time DEFAULT GETDATE(), + CONSTRAINT PK_bas_api_log PRIMARY KEY CLUSTERED(id) + ); +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'dbo.bas_api_log') AND name = N'IX_bas_api_log_create_time') + CREATE INDEX IX_bas_api_log_create_time ON dbo.bas_api_log(create_time); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'dbo.bas_api_log') AND name = N'IX_bas_api_log_direction_sys') + CREATE INDEX IX_bas_api_log_direction_sys ON dbo.bas_api_log(direction, sys_code, create_time DESC); +GO + +CREATE OR ALTER PROCEDURE dbo.proc_clean_bas_api_log +AS +BEGIN + SET NOCOUNT ON; + DELETE FROM dbo.bas_api_log + WHERE create_time < DATEADD(DAY, -30, GETDATE()); +END +GO +``` + +- [ ] **Step 2: Apply SQL** + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File ".\scripts\apply-sql-file.ps1" -SqlFile ".\sql\bas_api_log.sql" +``` + +Expected output: + +```text +Applied SQL file to FTC25009-WMS +``` + +--- + +### Task 7: Persist API Logs + +**Files:** +- Create: `ruoyi-common/src/main/java/com/ruoyi/common/annotation/ApiLog.java` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/domain/ApiLog.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/api/mapper/ApiLogMapper.java` +- Create: `ruoyi-admin/src/main/resources/mapper/api/ApiLogMapper.xml` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/service/ApiLogService.java` + +- [ ] **Step 1: Add annotation** + +```java +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ApiLog { + String interfaceName() default ""; + String sysCode() default ""; + String direction() default "INBOUND"; + boolean isSaveRequestData() default true; + boolean isSaveResponseData() default true; + String[] excludeParamNames() default {}; +} +``` + +- [ ] **Step 2: Add mapper** + +```java +package com.ruoyi.api.mapper; + +import com.ruoyi.api.domain.ApiLog; + +public interface ApiLogMapper { + int insertApiLog(ApiLog apiLog); + int deleteExpiredApiLog(); +} +``` + +- [ ] **Step 3: Add mapper XML** + +```xml + + + + + insert into bas_api_log(direction, sys_code, interface_name, request_url, request_method, + request_body, response_body, msg_type, http_status, cost_ms, status, error_msg, + source_ip, trace_id, create_time) + values(#{direction}, #{sysCode}, #{interfaceName}, #{requestUrl}, #{requestMethod}, + #{requestBody}, #{responseBody}, #{msgType}, #{httpStatus}, #{costMs}, #{status}, #{errorMsg}, + #{sourceIp}, #{traceId}, getdate()) + + + + delete from bas_api_log where create_time < dateadd(day, -30, getdate()) + + +``` + +- [ ] **Step 4: Change service to persist** + +Inject `ApiLogMapper` into `ApiLogService`, truncate request/response/error to safe sizes, and call `apiLogMapper.insertApiLog(apiLog)` in `record(ApiLog apiLog)`. + +Concrete core implementation: + +```java +@Autowired +private ApiLogMapper apiLogMapper; + +public void record(ApiLog apiLog) { + if (apiLog == null) { + return; + } + apiLog.setRequestBody(limit(apiLog.getRequestBody(), MAX_BODY_LEN)); + apiLog.setResponseBody(limit(apiLog.getResponseBody(), MAX_BODY_LEN)); + apiLog.setErrorMsg(limit(apiLog.getErrorMsg(), 2000)); + apiLog.setCreateTime(LocalDateTime.now()); + apiLogMapper.insertApiLog(apiLog); +} + +public int cleanExpired() { + return apiLogMapper.deleteExpiredApiLog(); +} + +private String limit(String value, int maxLength) { + if (value == null || value.length() <= maxLength) { + return value; + } + return value.substring(0, maxLength) + "...[truncated]"; +} +``` + +--- + +### Task 8: Add API Log Aspect And Retention Task + +**Files:** +- Create: `ruoyi-admin/src/main/java/com/ruoyi/api/aspect/ApiLogAspect.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/api/task/ApiLogCleanupTask.java` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/controller/ExternalApiController.java` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/client/BaseExternalClient.java` + +- [ ] **Step 1: Add aspect for inbound external API methods** + +Implement `ApiLogAspect` in `ruoyi-admin` rather than `ruoyi-framework`, because the aspect needs compile-time access to `com.ruoyi.api.service.ApiLogService`. + +The aspect must: +- start timer in `@Before("@annotation(apiLog)")` +- on return, save `status=SUCCESS` +- on exception, save `status=FAIL` +- fill `direction`, `sysCode`, `interfaceName`, URL, method, source IP, request JSON, response JSON, error, cost. + +- [ ] **Step 2: Annotate inbound WCS callbacks** + +In `ExternalApiController`, annotate at least: + +```java +@ApiLog(interfaceName = "WCS-请求入库货位", sysCode = "WCS", direction = "INBOUND") +@PostMapping("/wcs/inbound/location") +public AjaxResult inboundLocation(@RequestBody WcsInboundLocationRequest request) { ... } + +@ApiLog(interfaceName = "WCS-完成入库作业", sysCode = "WCS", direction = "INBOUND") +@PostMapping("/wcs/inbound/complete") +public AjaxResult completeInbound(@RequestBody WcsInboundCompleteRequest request) { ... } +``` + +- [ ] **Step 3: Ensure outbound logs write to `bas_api_log`** + +In `BaseExternalClient.post(...)`, keep the existing log construction but set: + +```java +apiLog.setDirection("OUTBOUND"); +apiLog.setRequestMethod("POST"); +apiLog.setSourceIp(null); +apiLogService.record(apiLog); +``` + +- [ ] **Step 4: Add cleanup task** + +```java +package com.ruoyi.api.task; + +import com.ruoyi.api.service.ApiLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class ApiLogCleanupTask { + @Autowired + private ApiLogService apiLogService; + + @Scheduled(cron = "0 30 2 * * ?") + public void cleanExpiredApiLogs() { + apiLogService.cleanExpired(); + } +} +``` + +If this project does not enable Spring scheduling, register a RuoYi Quartz job that invokes `apiLogCleanupTask.cleanExpiredApiLogs` daily at 02:30. + +--- + +### Task 9: Verification + +**Files:** +- No code files, verification only. + +- [ ] **Step 1: Verify database schema** + +Run: + +```sql +SELECT TABLE_NAME +FROM INFORMATION_SCHEMA.TABLES +WHERE TABLE_NAME IN ('bas_stock', 'bas_stock_detail', 'bas_api_log'); +``` + +Expected: all three names returned. + +- [ ] **Step 2: Verify completed job detail lookup** + +Run: + +```sql +SELECT TOP 1 job_id FROM dbo.mydo_job_finish ORDER BY finish_time DESC; +``` + +Then call: + +```text +GET /in/jobdetail/list?jobId=&status=4 +``` + +Expected: rows from `mydo_job_detail_finish` for that `job_id`. + +- [ ] **Step 3: Verify API log insert** + +Call WCS inbound location or complete endpoint, then run: + +```sql +SELECT TOP 10 direction, sys_code, interface_name, status, create_time +FROM dbo.bas_api_log +ORDER BY id DESC; +``` + +Expected: `INBOUND/WCS` rows for WCS callbacks and `OUTBOUND/WCS` rows for WMS calling WCS. + +- [ ] **Step 4: Run backend build** + +Run: + +```powershell +mvn -pl ruoyi-admin -am -DskipTests compile +``` + +Expected: PASS after Maven is upgraded to at least `3.6.3`. Current local environment previously failed before Java compilation because `maven-compiler-plugin:3.13.0` requires Maven `3.6.3`. + +- [ ] **Step 5: Run frontend build** + +Run: + +```powershell +npm run build:prod +``` + +from `ruoyi-ui`. + +Expected: production bundle builds successfully. + +--- + +## Self-Review + +**Spec coverage:** +- Inventory master/detail database, backend, and frontend are covered in Tasks 1-4. +- Completed inbound job detail lookup is covered in Task 5. +- API log annotation, persistence to `bas_api_log`, outbound/inbound coverage, and 30-day retention are covered in Tasks 6-8. + +**Known implementation guardrail:** +- Do not drop material columns from `bas_stock` in this iteration. Existing outbound allocation, stocktake, dashboard, and report SQL still read the old flat shape. This plan moves the UI/backend to master/detail while preserving compatibility; a later cleanup can remove old columns after all readers are migrated. + +**Type consistency:** +- Java uses `BasStockDetail.stockDetailId`, SQL uses `stock_detail_id`. +- API log Java uses camelCase fields mapping to snake_case columns in MyBatis. diff --git a/pda/api/warehouse/inbound.js b/pda/api/warehouse/inbound.js index ac44a0d..faabcac 100644 --- a/pda/api/warehouse/inbound.js +++ b/pda/api/warehouse/inbound.js @@ -9,6 +9,41 @@ export function getInboundTaskList(query) { }) } +// 获取入库待处理主单列表 +export function getInboundStockinList(query) { + return request({ + url: '/pda/inboundStockinList', + method: 'get', + params: query + }) +} + +// 获取入库主单下待绑盘子单列表 +export function getInboundDetailList(query) { + return request({ + url: '/pda/inboundDetailList', + method: 'get', + params: query + }) +} + +// 获取 PDA 仓库下拉选项:显示仓库名称,提交仓库编码 +export function getWarehouseOptions() { + return request({ + url: '/pda/warehouseOptions', + method: 'get' + }) +} + +// 获取 PDA 入库点位下拉选项:按仓库、托盘类型过滤,只返回启用且空闲的入库/入出库点位 +export function getInboundStationPointOptions(warehouseId, trayCode) { + return request({ + url: '/pda/inboundStationPointOptions', + method: 'get', + params: { warehouseId, trayCode } + }) +} + // 根据入库明细ID查询物料信息(入库绑盘用) export function getMaterialByDetailId(detailId) { return request({ @@ -22,7 +57,7 @@ export function getMaterialByDetailId(detailId) { * 提交入库绑盘(支持一托多料) * @param {Object} data * @param {string} data.trayCode 托盘号 - * @param {string} data.warehouseId 仓库编码(可选) + * @param {string} data.warehouseId 仓库编码 * @param {Array} data.items 物料列表 * 每项: { stockinDetailId, productId, quantity } */ @@ -34,6 +69,15 @@ export function submitBindTray(data) { }) } +// 确认绑盘并立即生成入库作业 +export function submitBindTrayAndAddInJob(data) { + return request({ + url: '/pda/bindTrayAndAddInJob', + method: 'post', + data: data + }) +} + // 提交补料入库 export function submitSupplyInbound(data) { return request({ diff --git a/pda/api/warehouse/outbound.js b/pda/api/warehouse/outbound.js index 24b1982..7b1c34f 100644 --- a/pda/api/warehouse/outbound.js +++ b/pda/api/warehouse/outbound.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 获取出库待处理任务列表 export function getOutboundTaskList(query) { return request({ - url: '/wms/outbound/task/list', + url: '/pda/outboundTaskList', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function getOutboundTaskList(query) { // 获取出库任务详情 export function getOutboundDetail(query) { return request({ - url: '/wms/outbound/detail', + url: '/pda/outboundDetail', method: 'get', params: query }) @@ -21,7 +21,7 @@ export function getOutboundDetail(query) { // 确认出库 export function confirmOutbound(data) { return request({ - url: '/wms/outbound/confirm', + url: '/pda/outboundConfirm', method: 'post', data: data }) @@ -30,7 +30,7 @@ export function confirmOutbound(data) { // 提交出库异常确认 export function submitOutboundException(data) { return request({ - url: '/wms/outbound/exception', + url: '/pda/outboundException', method: 'post', data: data }) diff --git a/pda/manifest.json b/pda/manifest.json index 03ee107..1613d6b 100644 --- a/pda/manifest.json +++ b/pda/manifest.json @@ -1,6 +1,6 @@ { - "name" : "若依移动端", - "appid" : "__UNI__25A9D80", + "name" : "工厂PDA", + "appid" : "__UNI__ECE92C0", "description" : "", "versionName" : "1.2.0", "versionCode" : "100", diff --git a/pda/pages/warehouse/inbound/bindTray.vue b/pda/pages/warehouse/inbound/bindTray.vue index 315e1aa..db06a1b 100644 --- a/pda/pages/warehouse/inbound/bindTray.vue +++ b/pda/pages/warehouse/inbound/bindTray.vue @@ -1,18 +1,42 @@ diff --git a/ruoyi-ui/src/views/bas/stock/index.vue b/ruoyi-ui/src/views/bas/stock/index.vue index 5ec212b..4ec61f5 100644 --- a/ruoyi-ui/src/views/bas/stock/index.vue +++ b/ruoyi-ui/src/views/bas/stock/index.vue @@ -210,7 +210,7 @@ - + @@ -219,18 +219,12 @@ - - - - - - - - + + + + @@ -272,7 +316,7 @@ import { saveWithDetails } from '@/api/out/stockout' import { listWarehouse } from '@/api/bas/warehouse' import { listCustomer } from '@/api/bas/customer' -import { listStock } from '@/api/bas/stock' +import { listStockDetail } from '@/api/bas/stock' export default { name: 'CreateStockout', @@ -280,40 +324,44 @@ export default { data() { return { saving: false, - - // ---------- 主单表单 ---------- mainForm: { outType: null, stockSource: null, erpBill: null, + documentN: null, + documentDat: null, + projectNam: null, + counterpart: null, + handle: null, + creato: null, + logisticsCompany: null, + logisticsN: null, + reviewe: null, + receive: null, + receiverA: null, + receiverPhon: null, warehouseId: null, warehouseName: null, customerId: null, customerName: null, - status: '0' // 默认待出库 + status: '0' }, mainRules: { - warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }], - customerId: [{ required: true, message: '请选择客户', trigger: 'change' }], - outType: [{ required: true, message: '请选择出库类型', trigger: 'change' }], - stockSource: [{ required: true, message: '请选择单据来源', trigger: 'change' }] + warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }], + customerId: [{ required: true, message: '请选择客户', trigger: 'change' }], + outType: [{ required: true, message: '请选择出库类型', trigger: 'change' }], + stockSource: [{ required: true, message: '请选择单据来源', trigger: 'change' }] }, - - // ---------- 下拉数据 ---------- warehouseOptions: [], customerOptions: [], - - // ---------- 明细行 ---------- detailLines: [], - - // ---------- 库存弹框 ---------- stockDialogVisible: false, stockLoading: false, stockList: [], stockTotal: 0, stockQuery: { pageNum: 1, - pageSize: 8, + pageSize: 10, warehouseId: null, productId: null, productName: null, @@ -322,22 +370,18 @@ export default { selectedStocks: [] } }, - computed: { totalQuantity() { - return this.detailLines.reduce((sum, l) => sum + (Number(l.outQuantity) || 0), 0) + return this.detailLines.reduce((sum, line) => sum + (Number(line.outQuantity) || 0), 0) } }, - created() { this.fetchWarehouseOptions() this.fetchCustomerOptions() }, - methods: { - // ===== 基础数据加载 ===== fetchWarehouseOptions() { - listWarehouse({ pageSize: 200,warehouseType: 1 }).then(res => { + listWarehouse({ pageSize: 200 }).then(res => { this.warehouseOptions = res.rows || [] }) }, @@ -346,24 +390,17 @@ export default { this.customerOptions = res.rows || [] }) }, - - // ===== 仓库/客户联动 ===== onWarehouseChange(val) { - const w = this.warehouseOptions.find(o => o.warehouseId === val) - this.mainForm.warehouseName = w ? w.warehouseName : null + const warehouse = this.warehouseOptions.find(item => item.warehouseId === val) + this.mainForm.warehouseName = warehouse ? warehouse.warehouseName : null this.stockQuery.warehouseId = val || null + this.detailLines = [] }, onCustomerChange(val) { - const c = this.customerOptions.find(o => o.customerId === val) - this.mainForm.customerName = c ? c.customerName : null + const customer = this.customerOptions.find(item => item.customerId === val) + this.mainForm.customerName = customer ? customer.customerName : null + this.mainForm.counterpart = customer ? customer.customerName : null }, - - // ===== 出库数量变化 ===== - onOutQtyChange(val, index) { - // 可在此扩展超限提示等逻辑 - }, - - // ===== 库存弹框 ===== openStockDialog() { this.stockDialogVisible = true }, @@ -384,89 +421,104 @@ export default { }, fetchStockList() { this.stockLoading = true - listStock(this.stockQuery).then(res => { + listStockDetail(this.stockQuery).then(res => { this.stockList = res.rows || [] this.stockTotal = res.total || 0 this.stockLoading = false - // 回显已选中行 - this.$nextTick(() => { - if (this.$refs.stockTable) { - this.stockList.forEach(row => { - const already = this.detailLines.some(l => l.stockId === row.stockId) - if (already) { - this.$refs.stockTable.toggleRowSelection(row, true) - } - }) - } - }) - }).catch(() => { this.stockLoading = false }) + }).catch(() => { + this.stockLoading = false + }) }, onStockSelectionChange(selection) { this.selectedStocks = selection }, - // 已在明细行中的库存记录置为不可重复勾选 isStockSelectable(row) { - return !this.detailLines.some(l => l.stockId === row.stockId) + return !this.detailLines.some(line => line.detailId === row.detailId) }, confirmStocks() { - this.selectedStocks.forEach(s => { - if (!this.detailLines.some(l => l.stockId === s.stockId)) { + this.selectedStocks.forEach(stock => { + if (!this.detailLines.some(line => line.detailId === stock.detailId)) { + const stockQuantity = Number(stock.availableQuantity != null ? stock.availableQuantity : stock.quantity) || 0 this.detailLines.push({ - stockId: s.stockId, // 库存ID - productId: s.productId, // 物料编码 - productName: s.productName, // 物料名称 - lotNo: s.lotNo || '', // 批次 - cargoSpaceId: s.cargoSpaceId || '', // 货位 - trayCode: s.trayCode || '', // 托盘条码 - stockQuantity: s.quantity, // 可用库存(原始) - outQuantity: 1, // 出库数量默认1 - remark: '' + detailId: stock.detailId, + stockId: stock.stockId, + productId: stock.productId, + productName: stock.productName, + itemCod: stock.productId, + itemNam: stock.productName, + lotNo: stock.lotNo || '', + stockQuantity: stockQuantity, + outQuantity: 1, + cargoSpaceId: stock.cargoSpaceId || '', + trayCode: stock.trayCode || '', + specMode: stock.spec || '', + uni: stock.unit || '', + warehous: this.mainForm.warehouseName || '', + locatio: stock.cargoSpaceId || '', + inventoryStatus: stock.status || '', + sourceDocN: this.mainForm.erpBill || '', + remar: '', + remark: '' }) } }) this.stockDialogVisible = false + this.selectedStocks = [] }, - - // ===== 明细行操作 ===== removeLine(index) { this.detailLines.splice(index, 1) }, - - // ===== 保存 ===== handleSave() { - // 1. 主单校验 this.$refs.mainForm.validate(valid => { if (!valid) return - // 2. 明细非空校验 if (this.detailLines.length === 0) { - this.$modal.msgWarning('请至少添加一条库存明细') + this.$modal.msgWarning('请至少选择一条库存明细') return } - // 3. 数量校验 - const invalidLine = this.detailLines.find(l => !l.outQuantity || l.outQuantity <= 0) + const invalidLine = this.detailLines.find(line => !line.outQuantity || line.outQuantity <= 0) if (invalidLine) { this.$modal.msgWarning(`物料【${invalidLine.productName}】的出库数量必须大于 0`) return } + const exceedLine = this.detailLines.find(line => Number(line.outQuantity) > Number(line.stockQuantity || 0)) + if (exceedLine) { + this.$modal.msgWarning(`物料【${exceedLine.productName}】的出库数量不能大于可用库存`) + return + } this.doSave() }) }, - async doSave() { this.saving = true try { - // 构造保存数据(主单 + 明细) const saveData = { ...this.mainForm, - details: this.detailLines.map(line => ({ - stockId: line.stockId, - productId: line.productId, + counterpart: this.mainForm.counterpart || this.mainForm.customerName, + warehous: this.mainForm.warehouseName, + printCoun: 0, + tota: this.totalQuantity, + details: this.detailLines.map((line, index) => ({ + lineN: index + 1, + stockId: line.stockId, + stockDetailId: line.detailId, + productId: line.productId, productName: line.productName, - lotNo: line.lotNo || null, - cargoSpaceId: line.cargoSpaceId || null, - trayCode: line.trayCode || null, - quantity: line.outQuantity, // 出库数量 - remark: line.remark || null + itemCod: line.itemCod || line.productId, + itemNam: line.itemNam || line.productName, + lotNo: line.lotNo || null, + quantity: line.outQuantity, + allotNum: line.outQuantity, + assignNum: 0, + specMode: line.specMode || null, + uni: line.uni || null, + actualQt: null, + warehous: line.warehous || this.mainForm.warehouseName || null, + locatio: line.locatio || line.cargoSpaceId || null, + inventoryStatus: line.inventoryStatus || null, + sourceDocN: line.sourceDocN || this.mainForm.erpBill || null, + remar: line.remar || line.remark || null, + remark: line.remark || line.remar || null, + status: '0' })) } const res = await saveWithDetails(saveData) @@ -475,16 +527,14 @@ export default { return } this.$modal.msgSuccess('出库单据创建成功!') - // 跳回列表页 this.$router.push({ path: '/out/stockout' }) - } catch (e) { + } catch (error) { this.$modal.msgError('保存失败,请检查网络或联系管理员') - console.error(e) + console.error(error) } finally { this.saving = false } } - } } @@ -494,27 +544,28 @@ export default { padding: 20px; } -/* 页面标题栏 */ .page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; } + .page-title { font-size: 18px; font-weight: 600; color: #303133; } + .header-actions { display: flex; gap: 8px; } -/* 分区卡片 */ .section-card { border-radius: 4px; } + .card-header { display: flex; align-items: center; @@ -523,31 +574,24 @@ export default { font-weight: 600; color: #303133; } + .card-header i { margin-right: 6px; color: #409eff; } -/* 库存搜索栏 */ +.summary-bar { + margin-top: 12px; + padding: 10px 12px; + background: #f5f7fa; + border-radius: 4px; + color: #606266; +} + .stock-search { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; } - -/* 汇总栏 */ -.summary-bar { - margin-top: 10px; - padding: 8px 12px; - background: #f0f9ff; - border-radius: 4px; - font-size: 13px; - color: #606266; - border-left: 3px solid #409eff; -} -.summary-bar strong { - color: #409eff; - margin: 0 2px; -} diff --git a/ruoyi-ui/src/views/out/job/index.vue b/ruoyi-ui/src/views/out/job/index.vue index e9ad8fe..5ac0863 100644 --- a/ruoyi-ui/src/views/out/job/index.vue +++ b/ruoyi-ui/src/views/out/job/index.vue @@ -98,6 +98,17 @@ v-hasPermi="['out:job:complete']" >手动完成 + + 推送WCS + @@ -202,7 +213,7 @@