diff --git a/.trae/documents/修复打印两页问题并完善打印模板.md b/.trae/documents/修复打印两页问题并完善打印模板.md
new file mode 100644
index 0000000..e1b674f
--- /dev/null
+++ b/.trae/documents/修复打印两页问题并完善打印模板.md
@@ -0,0 +1,53 @@
+## 问题判断
+- 仅勾选一条子单仍打印两页,典型原因是分页规则对最后一页也生效或伪类匹配异常导致空白页。
+- `:last-child` 在存在空白文本节点/注释节点的情况下可能失效,导致最后一个实际页面元素仍应用分页。
+- 也可能存在重复数据(同一个 `stockinDetailId` 被选中两次),引发两页输出。
+
+## 修复与优化方案
+### 1. 明确分页控制(按索引条件渲染)
+- 将分页控制从 CSS 伪类改为模板条件,避免 `:last-child` 因节点影响而失效:
+- 模板改为:
+```vue
+
+
+
+```
+
+### 2. 去重与防重复打印
+- 在 `printDetailQRCodes` 中对 `detailSelections` 去重,确保同一子单只打印一次:
+```js
+const map = new Map()
+this.printData = []
+for (const row of this.detailSelections) {
+ const key = row.stockinDetailId
+ if (key && !map.has(key)) {
+ map.set(key, true)
+ this.printData.push(row)
+ }
+}
+```
+
+### 3. 保证单页不被拆分
+- 保留并强化:`.print-page { page-break-inside: avoid; }`,防止卡片高度导致跨页拆分。
+- 控制卡片外层高度与内边距不超过 A4 可打印区域(当前 720px 宽 + 常规内容高度安全)。
+
+### 4. 打印触发时机与副本数
+- 维持 `this.$nextTick(() => setTimeout(() => this.$refs.easyPrint2.print(), 100))`,避免多次触发。
+- 如仍出现两页,追加保护:打印前记录 `printData.length` 与内容,控制仅当 `length >= 1` 才触发一次。
+
+## 代码变更摘要
+- 更新模板:将分页规则改为基于索引的内联样式条件。
+- 更新方法:`printDetailQRCodes` 增加去重逻辑与空数组保护。
+- 保留样式:`page-break-inside: avoid`;移除或忽略 `:last-child` 分页伪类。
+
+## 验证步骤
+- 勾选 1 条子单打印,预览应仅 1 页。
+- 勾选 2 条子单打印,预览应为 2 页且无末尾空白页。
+- 检查不同浏览器(Chromium/Edge)表现一致性。
+
+请确认按此方案实施,我将立即更新代码并验证。
\ No newline at end of file
diff --git a/.trae/documents/创建并接入 in_stockin_detail_n 明细存储.md b/.trae/documents/创建并接入 in_stockin_detail_n 明细存储.md
new file mode 100644
index 0000000..b2c6fd3
--- /dev/null
+++ b/.trae/documents/创建并接入 in_stockin_detail_n 明细存储.md
@@ -0,0 +1,104 @@
+## 目标
+
+* 在 SQL Server 新增表 `in_stockin_detail_n`,用于存储 ERP 返回的入库产品明细(Cols/Rows)。
+
+* 以实体、Mapper、Service 的方式接入到现有 WMS 代码,并在 `ToErpServiceImpl#getInStock` 中解析 JSON 后落库。
+
+* 字段覆盖现有 ERP 索引映射,支持来源、创建/更新时间等审计信息。
+
+## 数据表设计(SQL Server)
+
+* 表名:`in_stockin_detail_n`
+
+* 关键字段:
+
+ * stockin\_detail\_id (入库单主键,插入时,主单对应有几条子单,在主单ID后拼接“-”子单条数)
+ * stockin\_id 主单ID 与主单ID保持一致
+ * `erp_detail_id`(ERP入库明细ID,对应 Rows\[0])
+ * `product_name` nvarchar(200)(Rows\[1])
+ * `product_model` nvarchar(200)(Rows\[2],型号)
+ * `unit` nvarchar(50)(Rows\[3])
+ * `unit_price` decimal(18,6)(Rows\[4])
+ * `aux_unit` nvarchar(50) 或 int(Rows\[5],与现有实现保持兼容;建议 nvarchar 以避免字典耦合)
+ * `attr1` nvarchar(100)(Rows\[6])
+ * `attr2` nvarchar(100)(Rows\[7])
+ * `quantity` decimal(18,6)(Rows\[9])
+ * `in_quantity` decimal(18,6)(Rows\[10])
+ * `aux_quantity` decimal(18,6)(Rows\[11])
+ * `total_price` decimal(18,6)(Rows\[12])
+ * `cost_unit_price` decimal(18,6)(Rows\[13])
+ * `cost_total_price` decimal(18,6)(Rows\[14])
+ * `accounting_cost_total` decimal(18,6)(Rows\[15])
+ * `accounting_quantity` decimal(18,6)(Rows\[16])
+ * `accounting_status` int(Rows\[17])
+ * `batch_no` nvarchar(100)(Rows\[18])
+ * `serial_no` nvarchar(100)(Rows\[19])
+ * `production_date` datetime2(3)(Rows\[20])
+ * `expiry_date` datetime2(3)(Rows\[21])
+ * `arrival_date` datetime2(3)(Rows\[22])
+ * `remark` nvarchar(255)(Rows\[23])
+ * `custom1`..`custom6` nvarchar(255)(Rows\[24]..Rows\[29])
+ * `stevedore` nvarchar(100)(Rows\[30],装卸工)
+ * `create_time` datetime2(3) 默认 `SYSDATETIME()`
+ * `update_time` datetime2(3) 可在更新时写入 `SYSDATETIME()`
+
+* 约束:`detail_id` 主键;如需防重复插入,可使用 `detail_id + production_date` 唯一索引(按您确认)。
+
+## Java 代码接入
+
+* 新增实体 `com.ruoyi.system.domain.InStockinDetailN`
+
+ * 字段与表结构一一对应,类型与上面一致;保留必要的 `@Excel` 注解,遵循现有风格。
+
+ * 继承 `BaseEntity`,复用 `createTime`、`updateTime` 字段。
+
+* 新增 Mapper 接口 `com.ruoyi.system.mapper.InStockinDetailNMapper`
+
+ * 方法:`insertInStockinDetailN`、`updateInStockinDetailN`、`selectByDetailId`、`batchInsert(List<...>)`、`selectByDetailIdAndDate`(防重复)。
+
+* 新增 XML `mapper/system/InStockinDetailNMapper.xml`
+
+ * 定义 `resultMap` 与列映射;提供 `insert`、`update`、`select`、`batchInsert`(`foreach`)语句,时间字段默认使用 `SYSDATETIME()`。
+
+* 新增 Service `IInStockinDetailNService` 与实现 `InStockinDetailNServiceImpl`
+
+ * 封装批量写入与防重复逻辑(按 `detail_id` 或当天 `create_time` 过滤)。
+
+## 解析与入库逻辑
+
+* 在 `ToErpServiceImpl#getInStock` 中:
+
+ * 从 `row.get(15)`(产品明细 JSON)解析出 `Rows` 列表。
+
+ * 按上面索引映射生成 `InStockinDetailN` 对象,填充 `order_src='ERP'`、`create_time=当前时间`。
+
+ * 通过 `selectByDetailIdAndDate(detail_id, today)` 判断是否已存在,未存在则插入,已存在忽略。
+
+ * 将 `stockin_guid` 关联为当前主单 `newStockin.getStockinGuid()`(若可获得)。
+
+## 索引映射校验
+
+* 结合已有 `ErpInStockinDetail` 的使用位置(`ToErpServiceImpl#AddStockinDetailByErpStockinDetail`),确保以下关键位一致:
+
+ * `id`→Rows\[0]、`spare1`(产品名称)→Rows\[1]、`spare2`(产品编号/型号)→Rows\[2]
+
+ * 数量→Rows\[9]、批号→Rows\[17]、序列号→Rows\[18]、生产/有效/到货日期→Rows\[19]/\[20]/\[21]
+
+* 如您确认 `Clos` 与 `Rows` 存在偏移或后续附加列,我们将按您提供的最终结构调整表与映射。
+
+## 验证与回归
+
+* 本地运行 `getInStock`,抓取一次 ERP 返回;在控制台输出解析计数与首条样例,以确认列位。
+
+* 手工查询:`SELECT TOP 10 * FROM in_stockin_detail_n ORDER BY create_time DESC;`
+
+* 回归检查:对 `in_stockin_n` 与新表之间的 `stockin_guid` 关联是否正确。
+
+## 提交约定
+
+* 在实现时,所有 Java 代码将补充详细中文注释,解释每一步改动原因与映射关系。
+
+* 不自动创建 `.md` 或 `.sql` 文件;如您需要,我们可生成对应 SQL 文件并在您确认后执行。
+
+* 仅在您确认后进行代码与数据库变更,并按您要求的 Git 提交规范执行提交。
+
diff --git a/.trae/documents/在入库单新增中隐藏打印、选择物料并编辑子单,生成单号与默认来源.md b/.trae/documents/在入库单新增中隐藏打印、选择物料并编辑子单,生成单号与默认来源.md
new file mode 100644
index 0000000..692abb3
--- /dev/null
+++ b/.trae/documents/在入库单新增中隐藏打印、选择物料并编辑子单,生成单号与默认来源.md
@@ -0,0 +1,61 @@
+## 目标
+- 在 `InStockIn` 页面满足 5 项需求:
+ 1. 点击“单据新增”时,打开弹框隐藏“打印入库条码”按钮
+ 2. 弹框内新增“选择物料”按钮,打开第二个弹框显示物料列表,选择后将物料加入子单明细
+ 3. 子单明细中的“计划数量”和“入库数量”可编辑
+ 4. 新增时自动生成 `stockinId`:`RK` + 当前毫秒时间戳
+ 5. 提交时 `orderSrc` 默认赋值为“自建单”
+
+## 技术实现
+- 页面文件:`ruoyi-ui/src/views/system/inStockIn/index.vue`
+- 参考实现:`ruoyi-ui/src/views/in/stockIn/add/index_new.vue` 的“添加物料”与“选择物料”弹框逻辑
+
+## 详细改动
+### 新增状态标识
+- 在 `data()` 中新增:`isAddMode: false`
+- 用于区分“新增”与“编辑/查看”,控制按钮显示与表格可编辑
+
+### 隐藏打印按钮
+- 对话框底部的打印按钮(`printDetailQRCodes`)改为:`v-if="!isAddMode"`
+- 点击“单据新增”时将 `isAddMode = true`,从而隐藏打印按钮
+
+### 选择物料入口
+- 在“子单信息”卡片标题区域新增一个按钮:`选择物料`
+- 按钮事件 `openProductSelector()` 打开二级弹框
+
+### 物料选择弹框
+- 复用 `index_new.vue` 的模式:
+ - 引入 API:`import { listBasProduct } from "@/api/bas/BasProduct"`
+ - 在 `data()` 中新增:`productSelectVisible, productOptions, selectedProducts, productTotal, queryProductParams, productLoading`
+ - 弹框内容包含搜索表单、物料表格(带选择)、分页与“确定/取消”
+ - 事件:`getProductList()、resetProductQuery()、handleProductSelectionChange()、confirmSelectProduct()`
+
+### 子单明细数据与可编辑列
+- 子单明细列表数据源仍使用 `inStockinDetailNList`
+- 在“新增”模式下,`confirmSelectProduct()` 将选中的物料映射并 `push` 到 `inStockinDetailNList`:
+ - 字段映射示例:`productName、productModel(来自 productId 或 spec)、unit、quantity(默认 1)、inQuantity(默认 0)`
+- 在子单表格中为 `quantity` 与 `inQuantity` 列增加模板:`` 与 ``,并用 `v-if="isAddMode"` 控制仅在新增时可编辑
+
+### 自动生成主单号
+- 在 `handleAdd()`:
+ - `this.reset()` 后设置:`this.form.stockinId = 'RK' + Date.now()`
+ - `this.isAddMode = true; this.open = true; this.isView = false; this.title = '添加入库单据管理'`
+- 由于 `watch('form.stockinId')` 会尝试拉取子单,新增生成的主单号在后端不存在,列表将为空,此时通过物料选择填充本地子单明细
+
+### 提交默认来源
+- 在 `submitForm()` 里,在发起 `addInStockIn(this.form)` 前设置:`this.form.orderSrc = '自建单'`
+- 编辑场景保持原逻辑不变;新增成功后 `isAddMode` 复位为 `false`
+
+## 校验与兼容
+- 行内“修改/删除”按钮逻辑保持现有条件控制;新增模式下仅本地操作子单明细
+- 不影响历史单据查询与打印
+- 若后端需要接收子单明细,一并在后续迭代中按后端接口扩展(当前仅前端本地编辑)
+
+## 代码触点
+- 模板:对话框底部按钮处、子单卡片标题区新增按钮、子单表格新增可编辑列
+- 脚本:新增 `isAddMode` 标识、导入 `listBasProduct`、新增弹框及方法、在 `handleAdd()` 和 `submitForm()` 中按要求赋值
+
+## 期望交互
+- 点击“新增”→ 弹框打开且不显示打印按钮;自动生成主单号
+- 点“选择物料”→ 打开物料选择弹框,勾选并确定→ 子单明细填充,可编辑数量
+- 点“确定”提交→ 主单 `orderSrc` 自动为“自建单”
\ No newline at end of file
diff --git a/.trae/documents/新增完成表 in_stockin_n_finish 与 in_stockin_detail_n_finish.md b/.trae/documents/新增完成表 in_stockin_n_finish 与 in_stockin_detail_n_finish.md
new file mode 100644
index 0000000..887d878
--- /dev/null
+++ b/.trae/documents/新增完成表 in_stockin_n_finish 与 in_stockin_detail_n_finish.md
@@ -0,0 +1,55 @@
+## 目标
+- 为 `in_stockin_n_finish` 和 `in_stockin_detail_n_finish` 两张“完成/归档表”新增后端代码(Mapper 接口 + MyBatis XML + Service 接口与实现)。
+- 字段结构与现有 `in_stockin_n`、`in_stockin_detail_n` 完全一致,仅更换表名与命名空间。
+
+## 范围
+- 复用现有领域模型:直接复用 `InStockinN` 与 `InStockinDetailN` 两个实体类,不新增冗余实体。
+- 新增 Mapper 接口与 XML:Finish 版本分别指向 `_finish` 表名。
+- 新增 Service 接口与实现:提供基本 CRUD 与“从原表批量归档到 finish 表”的能力。
+- 不改动现有 Controller(如需页面展示 Finish 列表,可后续扩展)。
+
+## 技术方案
+- MyBatis:在 `mapper/system` 目录新增两个 XML 文件,`resultMap` 的 `type` 仍然使用现有实体类;`select`/`insert`/`update`/`delete` 语句的 `from`/`into` 指向 `_finish` 表。
+- Mapper 接口:在 `com.ruoyi.system.mapper` 新增 `InStockinNFinishMapper`、`InStockinDetailNFinishMapper`,方法签名与现有 Mapper 保持一致(命名不同)。
+- Service:在 `com.ruoyi.system.service` 新增 `IInStockinNFinishService`、`IInStockinDetailNFinishService`;在 `com.ruoyi.system.service.impl` 新增对应实现类,注入 Finish Mapper 完成操作。
+
+## 文件清单
+- `ruoyi-admin/src/main/java/com/ruoyi/system/mapper/InStockinNFinishMapper.java`
+- `ruoyi-admin/src/main/java/com/ruoyi/system/mapper/InStockinDetailNFinishMapper.java`
+- `ruoyi-admin/src/main/resources/mapper/system/InStockinNFinishMapper.xml`
+- `ruoyi-admin/src/main/resources/mapper/system/InStockinDetailNFinishMapper.xml`
+- `ruoyi-admin/src/main/java/com/ruoyi/system/service/IInStockinNFinishService.java`
+- `ruoyi-admin/src/main/java/com/ruoyi/system/service/IInStockinDetailNFinishService.java`
+- `ruoyi-admin/src/main/java/com/ruoyi/system/service/impl/InStockinNFinishServiceImpl.java`
+- `ruoyi-admin/src/main/java/com/ruoyi/system/service/impl/InStockinDetailNFinishServiceImpl.java`
+
+## 方法签名(示例)
+- InStockinNFinishMapper / Service:
+ - `InStockinN selectByStockinGuid(String stockinGuid)`
+ - `List selectList(InStockinN query)`
+ - `int insert(InStockinN row)` / `int update(InStockinN row)` / `int deleteByStockinGuid(String stockinGuid)`
+ - `int batchArchiveFromSource(String date)`(可选:将当日数据从源表复制到 finish 表)
+- InStockinDetailNFinishMapper / Service:
+ - `InStockinDetailN selectByStockinDetailId(String stockinDetailId)`
+ - `List selectListByStockinGuid(String stockinGuid)`
+ - `int insert(InStockinDetailN row)` / `int update(InStockinDetailN row)` / `int deleteByStockinDetailId(String stockinDetailId)`
+ - `int batchArchiveByStockinGuid(String stockinGuid)`(从源明细表按主单 guid 复制到 finish 表)
+
+## 归档流程(可在 Service 中提供便捷方法)
+- 主单:`archiveStockinNByGuid(stockinGuid)`
+ - 从 `in_stockin_n` 查询主单,存在则插入/更新到 `in_stockin_n_finish`
+- 子单:`archiveStockinDetailNByGuid(stockinGuid)`
+ - 从 `in_stockin_detail_n` 批量查询 `stockin_guid=...` 的子单,批量插入到 `in_stockin_detail_n_finish`
+- 幂等:`insert` 使用 `stockin_guid` 或 `stockin_detail_id` 作为唯一约束进行“存在则更新”的 upsert 逻辑(与现有 XML 风格保持一致)。
+
+## 校验与验证
+- 本地运行现有入库拉取逻辑,手工触发一次归档方法,验证两张 finish 表插入成功。
+- 使用已有前端页面查询与导出功能验证数据一致性(如果后续增加 Controller,将绑定至 `/system/inStockInFinish` 与 `/system/inStockInDetailFinish`)。
+
+## 不改动项
+- 不修改实体字段与现有源表 Mapper/Service;Finish 仅新增并依赖现有结构。
+- 不自动生成 SQL DDL(如需要我可根据现有表结构快速给出 `_finish` 建表 SQL)。
+
+## 实施说明
+- 所有新增 Java/XML 文件将包含详细中文注释,解释与源表的字段一致性、使用场景与幂等处理原因。
+- 完成后不进行 Git 提交,等待您提供提交说明/提交人/提交时间/提交备注后再执行。
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/controller/ToErpApiController.java b/ruoyi-admin/src/main/java/com/ruoyi/api/controller/ToErpApiController.java
index 1bee8b9..4e531a5 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/api/controller/ToErpApiController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/api/controller/ToErpApiController.java
@@ -61,4 +61,9 @@ public class ToErpApiController {
public AjaxResult qqq() {
return toErpService.getErpStockinList();
}
+
+ @RequestMapping("/getInStock")
+ public AjaxResult getInStock(){
+ return toErpService.getInStock();
+ }
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/domain/erp/ErpInStockinNResponse.java b/ruoyi-admin/src/main/java/com/ruoyi/api/domain/erp/ErpInStockinNResponse.java
new file mode 100644
index 0000000..fdc5840
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/api/domain/erp/ErpInStockinNResponse.java
@@ -0,0 +1,39 @@
+package com.ruoyi.api.domain.erp;
+
+import cn.hutool.core.annotation.Alias;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ErpInStockinNResponse {
+ @Alias("Page")
+ private Page page;
+ @Alias("Cols")
+ private List cols;
+ @Alias("Rows")
+ private List> rows;
+ @Alias("Code")
+ private Integer code;
+ @Alias("Msg")
+ private String msg;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Page {
+ @Alias("RecordCount")
+ private int recordCount;
+ @Alias("PageSize")
+ private int pageSize;
+ @Alias("PageIndex")
+ private int pageIndex;
+ @Alias("PageCount")
+ private int pageCount;
+ }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/service/IToErpService.java b/ruoyi-admin/src/main/java/com/ruoyi/api/service/IToErpService.java
index 8f20a33..f168a0b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/api/service/IToErpService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/api/service/IToErpService.java
@@ -40,4 +40,5 @@ public interface IToErpService {
void AddStockinDetailByErpStockinDetail(ErpInStockinDetail erpInStockinetail);
+ AjaxResult getInStock();
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToErpServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToErpServiceImpl.java
index 4294820..393923e 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToErpServiceImpl.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToErpServiceImpl.java
@@ -46,6 +46,12 @@ import com.ruoyi.out.outStockoutDetail.domain.OutStockoutDetail;
import com.ruoyi.out.outStockoutDetail.service.IOutStockoutDetailService;
import com.ruoyi.stock.stock.domain.BasStock;
import com.ruoyi.stock.stock.service.IBasStockService;
+import com.ruoyi.system.domain.InStockinDetailN;
+import com.ruoyi.system.domain.InStockinN;
+import com.ruoyi.system.domain.InStockinNQueryRequest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.enums.StockInCompleteStatus;
+import com.ruoyi.system.service.IInStockinNService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
@@ -493,15 +499,12 @@ public class ToErpServiceImpl implements IToErpService {
log.info("请求异常:响应信息为空!检查请求内容!");
return null;
}
-
//------处理响应信息
ErpStockinResponse res = JSONUtil.toBean(response, ErpStockinResponse.class);
for (List