diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/vo/HaiAnSOIssueNotifyOrderMaterialListDetail.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/vo/HaiAnSOIssueNotifyOrderMaterialListDetail.java
index 7977cb3f..fb827b41 100644
--- a/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/vo/HaiAnSOIssueNotifyOrderMaterialListDetail.java
+++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/vo/HaiAnSOIssueNotifyOrderMaterialListDetail.java
@@ -94,7 +94,10 @@ public class HaiAnSOIssueNotifyOrderMaterialListDetail {
private Integer allocSeq;
/**
- * 栈板编码(pallet_id) - rqrq
+ * 领料栈板(pallet_id)
+ *
来源表字段:haian_SOIssueNotifyOrderMaterialList_detail.pallet_id;下达预留时写入,对应本次领料占用的海安栈板号
+ * 是否落库:是;赋值时机:海安领料匹配/下达写明细;消费方:查看领料申请单预留标签页签、立库申请单完成情况预留标签清单,表头展示为「领料栈板」
+ * - rqrq
*/
private String palletId;
}
diff --git a/src/main/java/com/gaotao/modules/warehouse/controller/CrossSiteHuTransferController.java b/src/main/java/com/gaotao/modules/warehouse/controller/CrossSiteHuTransferController.java
new file mode 100644
index 00000000..7798b510
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/controller/CrossSiteHuTransferController.java
@@ -0,0 +1,65 @@
+package com.gaotao.modules.warehouse.controller;
+
+import com.gaotao.common.utils.PageUtils;
+import com.gaotao.common.utils.R;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferData;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferRequest;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferResult;
+import com.gaotao.modules.warehouse.entity.SiteSubOptionData;
+import com.gaotao.modules.warehouse.service.CrossSiteHuTransferService;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.List;
+
+/**
+ * @author rqrq
+ * @Description 跨小site调拨控制器:只负责收参、调用Service、返回统一R结构 - rqrq
+ * @date 2026/09/04
+ */
+@RestController
+@RequestMapping("warehouse/crossSiteHuTransfer")
+public class CrossSiteHuTransferController {
+
+ @Autowired
+ private CrossSiteHuTransferService crossSiteHuTransferService;
+
+ /**
+ * @author rqrq
+ * @Description 分页查询可跨小site调拨标签,入参缺失site或无查询条件时由Service抛出业务异常 - rqrq
+ * @param query 查询对象,site=登录大site,searchSite=可选小site
+ * @return page 分页结果
+ */
+ @PostMapping("/searchHandlingUnitList")
+ public R searchHandlingUnitList(@RequestBody CrossSiteHuTransferData query) {
+ PageUtils page = crossSiteHuTransferService.searchHandlingUnitList(query);
+ return R.ok().put("page", page);
+ }
+
+ /**
+ * @author rqrq
+ * @Description 查询目标小site下拉,排除当前选中标签的来源site - rqrq
+ * @param query site=登录大site,excludeSite=来源小site
+ * @return rows 目标小site列表
+ */
+ @PostMapping("/listTargetSubSites")
+ public R listTargetSubSites(@RequestBody CrossSiteHuTransferData query) {
+ List rows = crossSiteHuTransferService.listTargetSubSites(query);
+ return R.ok().put("rows", rows);
+ }
+
+ /**
+ * @author rqrq
+ * @Description 提交跨小site调拨:二次校验后写STO/STI并改标签site - rqrq
+ * @param request 登录大site、来源/目标小site、操作人、选中标签
+ * @return row 处理结果(含STO/STI事务号)
+ */
+ @PostMapping("/transferHuSite")
+ public R transferHuSite(@RequestBody CrossSiteHuTransferRequest request) {
+ CrossSiteHuTransferResult result = crossSiteHuTransferService.transferHuSite(request);
+ return R.ok().put("row", result);
+ }
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferData.java b/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferData.java
new file mode 100644
index 00000000..a177f748
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferData.java
@@ -0,0 +1,51 @@
+package com.gaotao.modules.warehouse.entity;
+
+import com.gaotao.modules.warehouse.entity.vo.HandlingUnitQueryVo;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.ibatis.type.Alias;
+
+/**
+ * @Description 跨小site调拨查询业务实体 - 用于分页查询可调拨标签 - rqrq
+ *
+ * 继承关系: 继承 HandlingUnitQueryVo,复用标签查询字段与分页参数
+ *
+ * 用途:
+ *
+ * 跨小site调拨页查询入参/出参
+ * site 表示当前登录大site,用于 site_sub 反查允许的小site集合
+ * searchSite 表示用户输入的标签小site过滤条件,对应 handling_unit.site
+ *
+ *
+ * 常用查询逻辑:
+ *
+ * WHERE h.in_stock_flag = 'Y'
+ * AND h.reserve_flag = 'N'
+ * AND h.site IN (SELECT ss.subsiteid FROM site_sub ss WHERE ss.siteid = #{query.site})
+ * AND h.site = #{query.searchSite} -- 仅当用户填写了site条件
+ *
+ *
+ * @author rqrq
+ * @date 2026/09/04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Alias("CrossSiteHuTransferData")
+public class CrossSiteHuTransferData extends HandlingUnitQueryVo {
+
+ /**
+ * 查询条件 - 标签小site(精确查询,对应 handling_unit.site)
+ * SQL示例:AND h.site = #{query.searchSite}
+ * 是否落库:否,仅查询入参;赋值时机:前端筛选区输入;消费方:查询SQL
+ * - rqrq
+ */
+ private String searchSite;
+
+ /**
+ * 查询条件 - 需要排除的小site(目标站点下拉用,对应当前选中标签的site)
+ * SQL示例:AND ss.subsiteid <> #{query.excludeSite}
+ * 是否落库:否,仅弹窗查询入参;赋值时机:点击跨site调拨后;消费方:目标site下拉SQL
+ * - rqrq
+ */
+ private String excludeSite;
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferRequest.java b/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferRequest.java
new file mode 100644
index 00000000..5ac67a3f
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferRequest.java
@@ -0,0 +1,55 @@
+package com.gaotao.modules.warehouse.entity;
+
+import com.gaotao.modules.warehouse.entity.vo.HandlingUnitVo;
+import lombok.Data;
+import org.apache.ibatis.type.Alias;
+
+import java.util.List;
+
+/**
+ * @Description 跨小site调拨提交请求 - 承载登录大site、来源/目标小site和选中标签 - rqrq
+ *
+ * 用途: 前端确认目标site后提交调拨。后台二次校验在库/未预留后,先按来源小site写STO出库,再按目标小site写STI入库,最后改 handling_unit.site。调拨外部接口后续接入。
+ *
+ * @author rqrq
+ * @date 2026/09/04
+ */
+@Data
+@Alias("CrossSiteHuTransferRequest")
+public class CrossSiteHuTransferRequest {
+
+ /**
+ * 当前登录大site,来源前端 this.$store.state.user.site,用于校验来源/目标小site都属于该大site
+ * 是否落库:否;赋值时机:前端提交;消费方:后端校验 site_sub 映射
+ * - rqrq
+ */
+ private String site;
+
+ /**
+ * 来源小site,必须等于选中标签的 handling_unit.site,且整批只能有一个来源
+ * 是否落库:否;赋值时机:前端根据选中行汇总;消费方:更新WHERE条件
+ * - rqrq
+ */
+ private String fromSite;
+
+ /**
+ * 目标小site,必须来自 site_sub,且不能等于 fromSite
+ * 是否落库:是,写入 handling_unit.site;出库单site用 fromSite,入库单site用 toSite
+ * - rqrq
+ */
+ private String toSite;
+
+ /**
+ * 操作人,来源前端登录用户名,写入 modified_by
+ * 是否落库:是,写入 handling_unit.modified_by
+ * - rqrq
+ */
+ private String createdBy;
+
+ /**
+ * 选中的标签列表,至少1条;仅使用 site+unitId 作为更新主键
+ * 是否落库:否,仅作为待处理清单
+ * - rqrq
+ */
+ private List selectedItems;
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferResult.java b/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferResult.java
new file mode 100644
index 00000000..68298471
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/entity/CrossSiteHuTransferResult.java
@@ -0,0 +1,57 @@
+package com.gaotao.modules.warehouse.entity;
+
+import lombok.Data;
+import org.apache.ibatis.type.Alias;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description 跨小site调拨处理结果 - 供前端弹窗展示成功/失败明细 - rqrq
+ *
+ * 数据来源: CrossSiteHuTransferServiceImpl.transferHuSite 汇总
+ *
+ * @author rqrq
+ * @date 2026/09/04
+ */
+@Data
+@Alias("CrossSiteHuTransferResult")
+public class CrossSiteHuTransferResult {
+
+ /**
+ * 成功数量,来源本次实际更新 handling_unit 的条数 - rqrq
+ */
+ private int successCount;
+
+ /**
+ * 失败数量,来源冲突校验或更新影响行数为0的标签数 - rqrq
+ */
+ private int failCount;
+
+ /**
+ * 失败标签号列表,前端用于展示失败序列号 - rqrq
+ */
+ private List failedUnitIds = new ArrayList<>();
+
+ /**
+ * 失败原因列表,与 failedUnitIds 顺序对应,前端展示第一条即可定位 - rqrq
+ */
+ private List failedReasons = new ArrayList<>();
+
+ /**
+ * 处理结果摘要,前端结果弹窗标题补充说明 - rqrq
+ */
+ private String message;
+
+ /**
+ * 出库事务号,来源登录大site的 transNoControl(STO),写入 trans_header.trans_no;前端结果弹窗回显 - rqrq
+ * 是否落库:是,对应 STO 出库单(site=来源小site);赋值时机:校验通过后生成;消费方:前端结果展示、后续调拨接口
+ */
+ private String stoTransNo;
+
+ /**
+ * 入库事务号,来源登录大site的 transNoControl(STI),写入 trans_header.trans_no;前端结果弹窗回显 - rqrq
+ * 是否落库:是,对应 STI 入库单(site=目标小site);赋值时机:校验通过后生成;消费方:前端结果展示、后续调拨接口
+ */
+ private String stiTransNo;
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/entity/SiteSubOptionData.java b/src/main/java/com/gaotao/modules/warehouse/entity/SiteSubOptionData.java
new file mode 100644
index 00000000..c1404e9c
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/entity/SiteSubOptionData.java
@@ -0,0 +1,27 @@
+package com.gaotao.modules.warehouse.entity;
+
+import lombok.Data;
+import org.apache.ibatis.type.Alias;
+
+/**
+ * @Description 跨小site调拨目标站点下拉结果 - 映射 site_sub 表 - rqrq
+ *
+ * 数据来源: site_sub.siteid / site_sub.subsiteid
+ *
+ * @author rqrq
+ * @date 2026/09/04
+ */
+@Data
+@Alias("SiteSubOptionData")
+public class SiteSubOptionData {
+
+ /**
+ * 大site编码,来源 site_sub.siteid,对应当前登录工厂 - rqrq
+ */
+ private String siteId;
+
+ /**
+ * 小site编码,来源 site_sub.subsiteid,前端下拉展示并作为调拨目标 - rqrq
+ */
+ private String subSiteId;
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/mapper/CrossSiteHuTransferMapper.java b/src/main/java/com/gaotao/modules/warehouse/mapper/CrossSiteHuTransferMapper.java
new file mode 100644
index 00000000..27fc9ea7
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/mapper/CrossSiteHuTransferMapper.java
@@ -0,0 +1,88 @@
+package com.gaotao.modules.warehouse.mapper;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.gaotao.modules.handlingunit.entity.HandlingUnit;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferData;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferRequest;
+import com.gaotao.modules.warehouse.entity.SiteSubOptionData;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @Description 跨小site调拨数据访问层,负责可调拨标签查询、小site清单和site改写 - rqrq
+ *
+ * @author rqrq
+ * @date 2026/09/04
+ */
+@Mapper
+public interface CrossSiteHuTransferMapper {
+
+ /**
+ * @Description 分页查询可跨小site调拨的在库未预留标签 - rqrq
+ * @param page 分页对象
+ * @param query 查询条件:site=登录大site,searchSite=可选小site
+ * @return 标签分页结果
+ */
+ IPage searchHandlingUnitList(Page page, @Param("query") CrossSiteHuTransferData query);
+
+ /**
+ * @Description 按登录大site查询可选小site,排除当前标签来源site - rqrq
+ * @param query site=登录大site,excludeSite=当前标签site
+ * @return 小site列表
+ */
+ List listTargetSubSites(@Param("query") CrossSiteHuTransferData query);
+
+ /**
+ * @Description 校验小site是否属于当前登录大site的 site_sub 映射 - rqrq
+ * @param site 登录大site
+ * @param subSite 待校验小site
+ * @return 命中数量,0表示映射不存在
+ */
+ int countSiteSubMapping(@Param("site") String site, @Param("subSite") String subSite);
+
+ /**
+ * @Description 按来源小site+标签号加锁查询待调拨标签,供二次校验预留/在库状态 - rqrq
+ * @param fromSite 来源小site,对应 handling_unit.site
+ * @param unitIds 待调拨标签号
+ * @return 当前库内标签清单;查不到的标签表示已被删除或site已变化
+ */
+ List listTransferHandlingUnits(@Param("fromSite") String fromSite, @Param("unitIds") List unitIds);
+
+ /**
+ * @Description 查询目标小site下已存在的同号标签,避免联合主键冲突 - rqrq
+ * @param toSite 目标小site
+ * @param unitIds 待调拨标签号
+ * @return 已存在的标签号
+ */
+ List listConflictUnitIds(@Param("toSite") String toSite, @Param("unitIds") List unitIds);
+
+ /**
+ * @Description 将符合在库未预留条件的标签site改写到目标小site - rqrq
+ * @param request 含 fromSite/toSite/createdBy
+ * @param unitIds 待更新标签号
+ * @return 实际更新行数
+ */
+ int updateHandlingUnitSite(@Param("request") CrossSiteHuTransferRequest request, @Param("unitIds") List unitIds);
+
+ /**
+ * @Description 查询海安托盘明细中是否存在本次调拨标签,serial_no对应handling_unit.unit_id - rqrq
+ * @param site 登录大site,对应 haian_pallet_detail.site
+ * @param unitIds 待调拨标签号
+ * @return 已存在明细的条码号;空列表表示这些标签不在海安托盘上
+ */
+ List listHaiAnPalletDetailSerialNos(@Param("site") String site, @Param("unitIds") List unitIds);
+
+ /**
+ * @Description 将海安托盘明细的sub_site改到目标小site,与handling_unit.site保持一致 - rqrq
+ * @param request 含 toSite
+ * @param site 登录大site,对应 haian_pallet_detail.site
+ * @param unitIds 已确认存在明细的条码号
+ * @return 实际更新行数
+ */
+ int updateHaiAnPalletDetailSubSite(@Param("request") CrossSiteHuTransferRequest request,
+ @Param("site") String site,
+ @Param("unitIds") List unitIds);
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/service/CrossSiteHuTransferService.java b/src/main/java/com/gaotao/modules/warehouse/service/CrossSiteHuTransferService.java
new file mode 100644
index 00000000..bbff02f4
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/service/CrossSiteHuTransferService.java
@@ -0,0 +1,41 @@
+package com.gaotao.modules.warehouse.service;
+
+import com.gaotao.common.utils.PageUtils;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferData;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferRequest;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferResult;
+import com.gaotao.modules.warehouse.entity.SiteSubOptionData;
+
+import java.util.List;
+
+/**
+ * @author rqrq
+ * @Description 跨小site调拨业务接口:查询可调拨标签、目标site下拉、校验后写STO/STI并改标签site - rqrq
+ * @date 2026/09/04
+ */
+public interface CrossSiteHuTransferService {
+
+ /**
+ * @author rqrq
+ * @Description 分页查询可跨小site调拨的在库未预留标签 - rqrq
+ * @param query site=登录大site,searchSite=可选小site过滤;必须至少有一个业务查询条件
+ * @return page 分页结果,供前端表格展示
+ */
+ PageUtils searchHandlingUnitList(CrossSiteHuTransferData query);
+
+ /**
+ * @author rqrq
+ * @Description 按登录大site查询可选目标小site,排除当前标签来源site - rqrq
+ * @param query site=登录大site,excludeSite=选中标签的来源小site
+ * @return 目标小site下拉列表
+ */
+ List listTargetSubSites(CrossSiteHuTransferData query);
+
+ /**
+ * @author rqrq
+ * @Description 二次校验预留/在库后生成STO出库、STI入库,再改写标签site;调拨外部接口当前留空 - rqrq
+ * @param request 登录大site、来源/目标小site、操作人、选中标签
+ * @return 成功数量、出库/入库事务号
+ */
+ CrossSiteHuTransferResult transferHuSite(CrossSiteHuTransferRequest request);
+}
diff --git a/src/main/java/com/gaotao/modules/warehouse/service/impl/CrossSiteHuTransferServiceImpl.java b/src/main/java/com/gaotao/modules/warehouse/service/impl/CrossSiteHuTransferServiceImpl.java
new file mode 100644
index 00000000..39fb4867
--- /dev/null
+++ b/src/main/java/com/gaotao/modules/warehouse/service/impl/CrossSiteHuTransferServiceImpl.java
@@ -0,0 +1,495 @@
+package com.gaotao.modules.warehouse.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.gaotao.common.utils.PageUtils;
+import com.gaotao.modules.handlingunit.entity.HandlingUnit;
+import com.gaotao.modules.sys.entity.SysUserEntity;
+import com.gaotao.modules.sys.service.SysUserService;
+import com.gaotao.modules.trans.entity.TransDetail;
+import com.gaotao.modules.trans.entity.TransDetailSub;
+import com.gaotao.modules.trans.entity.TransHeader;
+import com.gaotao.modules.trans.entity.TransNoControl;
+import com.gaotao.modules.trans.service.TransDetailService;
+import com.gaotao.modules.trans.service.TransDetailSubService;
+import com.gaotao.modules.trans.service.TransHeaderService;
+import com.gaotao.modules.trans.service.TransNoControlService;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferData;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferRequest;
+import com.gaotao.modules.warehouse.entity.CrossSiteHuTransferResult;
+import com.gaotao.modules.warehouse.entity.SiteSubOptionData;
+import com.gaotao.modules.warehouse.entity.vo.HandlingUnitVo;
+import com.gaotao.modules.warehouse.mapper.CrossSiteHuTransferMapper;
+import com.gaotao.modules.warehouse.service.CrossSiteHuTransferService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author rqrq
+ * @Description 跨小site调拨实现:查询限定大site下在库未预留标签;提交时二次校验后先写STO再写STI,再改标签site,海安托盘明细有记录则同步sub_site - rqrq
+ * @date 2026/09/04
+ */
+@Slf4j
+@Service
+public class CrossSiteHuTransferServiceImpl implements CrossSiteHuTransferService {
+
+ @Autowired
+ private CrossSiteHuTransferMapper crossSiteHuTransferMapper;
+
+ @Autowired
+ private TransNoControlService transNoControlService;
+
+ @Autowired
+ private TransHeaderService transHeaderService;
+
+ @Autowired
+ private TransDetailService transDetailService;
+
+ @Autowired
+ private TransDetailSubService transDetailSubService;
+
+ @Autowired
+ private SysUserService sysUserService;
+
+ /**
+ * @author rqrq
+ * @Description 分页查询可调拨标签;登录大site缺失、无查询条件时直接失败,避免全表扫描 - rqrq
+ * @param query site=登录大site,searchSite=可选小site
+ * @return 分页结果
+ */
+ @Override
+ public PageUtils searchHandlingUnitList(CrossSiteHuTransferData query) {
+ // 业务隔离必须使用请求中的登录大site,禁止后端兜底登录态 - rqrq
+ if (query == null || !StringUtils.hasText(query.getSite())) {
+ throw new RuntimeException("登录工厂site不能为空");
+ }
+ // 至少填一个业务条件才能查,防止无过滤把大site下全部在库标签拉出来 - rqrq
+ if (!hasAnySearchCondition(query)) {
+ throw new RuntimeException("请至少填写一个查询条件");
+ }
+ if (query.getPage() == null || query.getPage() < 1) {
+ query.setPage(1);
+ }
+ if (query.getLimit() == null || query.getLimit() < 1) {
+ query.setLimit(20);
+ }
+ // 用户填了小site时先校验属于当前大site,避免查出无关工厂标签 - rqrq
+ if (StringUtils.hasText(query.getSearchSite())) {
+ validateSubSiteBelongToLoginSite(query.getSite(), query.getSearchSite(), "查询site");
+ }
+
+ log.info("开始查询跨小site调拨标签 - rqrq,loginSite={}, searchSite={}, unitId={}, partNo={}",
+ query.getSite(), query.getSearchSite(), query.getUnitId(), query.getPartNo());
+
+ // 调用分页查询:SQL已固定 in_stock_flag=Y、reserve_flag=N,并限制 site_sub 范围;失败则无列表可展示 - rqrq
+ Page page = new Page<>(query.getPage(), query.getLimit());
+ IPage pageResult = crossSiteHuTransferMapper.searchHandlingUnitList(page, query);
+
+ log.info("跨小site调拨标签查询完成 - rqrq,total={}", pageResult.getTotal());
+ return new PageUtils(pageResult);
+ }
+
+ /**
+ * @author rqrq
+ * @Description 查询目标小site下拉;excludeSite用于排除当前选中标签的来源site - rqrq
+ * @param query site=登录大site,excludeSite=来源小site
+ * @return 目标小site列表
+ */
+ @Override
+ public List listTargetSubSites(CrossSiteHuTransferData query) {
+ if (query == null || !StringUtils.hasText(query.getSite())) {
+ throw new RuntimeException("登录工厂site不能为空");
+ }
+ if (!StringUtils.hasText(query.getExcludeSite())) {
+ throw new RuntimeException("来源site不能为空");
+ }
+ // 来源site必须属于当前大site,否则下拉结果会误导后续提交 - rqrq
+ validateSubSiteBelongToLoginSite(query.getSite(), query.getExcludeSite(), "来源site");
+
+ // 按大site查 site_sub,排除来源site;失败则弹窗无法选目标,中断调拨 - rqrq
+ List rows = crossSiteHuTransferMapper.listTargetSubSites(query);
+ return rows == null ? new ArrayList<>() : rows;
+ }
+
+ /**
+ * @author rqrq
+ * @Description 跨小site调拨主流程:二次校验预留/在库 -> 生成STO出库 -> 生成STI入库 -> 改标签site -> 调拨接口留空 - rqrq
+ *
+ * 事务边界:单据写入与标签site改写同一事务;任一校验或写入失败整批回滚,避免只出库不改site。
+ * 出入库记录site使用小site:STO.site=fromSite,STI.site=toSite。
+ *
+ * @param request 登录大site、来源/目标小site、操作人、选中标签
+ * @return 成功数量与STO/STI事务号
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CrossSiteHuTransferResult transferHuSite(CrossSiteHuTransferRequest request) {
+ // 先校验登录大site、来源/目标归属和整批同一来源;失败直接抛错,不写任何单据 - rqrq
+ validateTransferRequest(request);
+
+ // 从选中行提取去重条码号,空条码会失败,避免后续IN条件无值 - rqrq
+ List unitIds = collectUnitIds(request.getSelectedItems());
+ log.info("开始跨小site调拨 - rqrq,loginSite={}, fromSite={}, toSite={}, count={}",
+ request.getSite(), request.getFromSite(), request.getToSite(), unitIds.size());
+
+ // 加锁读取来源site下选中标签,入参fromSite+unitIds;失败或漏行则中断,避免按过期列表写单 - rqrq
+ List handlingUnits = crossSiteHuTransferMapper.listTransferHandlingUnits(request.getFromSite(), unitIds);
+ validateHandlingUnitStatus(unitIds, handlingUnits);
+
+ // 目标site已存在同号标签时禁止改联合主键,避免唯一约束冲突导致半成品单据 - rqrq
+ List conflictUnitIds = crossSiteHuTransferMapper.listConflictUnitIds(request.getToSite(), unitIds);
+ if (!CollectionUtils.isEmpty(conflictUnitIds)) {
+ throw new RuntimeException("目标site已存在相同条码,无法调拨:" + String.join(",", conflictUnitIds));
+ }
+
+ // 按操作人查用户档案,写入出入库单 userId/userName;查不到则仅用请求中的createdBy,不阻断调拨 - rqrq
+ SysUserEntity operatorUser = null;
+ if (StringUtils.hasText(request.getCreatedBy())) {
+ operatorUser = sysUserService.queryByUserName(request.getCreatedBy());
+ }
+ String userId = operatorUser != null && operatorUser.getUserId() != null
+ ? operatorUser.getUserId().toString() : "";
+ String userName = operatorUser != null && StringUtils.hasText(operatorUser.getUsername())
+ ? operatorUser.getUsername() : request.getCreatedBy();
+
+ // 流水号按登录大site取号(trans_no_control通常按大site维护);单据site仍写小site。取号失败则无法写单,整批回滚 - rqrq
+ TransNoControl stoTransData = transNoControlService.getTransNo(request.getSite(), "STO", 10);
+ if (stoTransData == null || !StringUtils.hasText(stoTransData.getNewTransNo())) {
+ throw new RuntimeException("生成出库事务号失败,请检查当前工厂的STO流水配置:" + request.getSite());
+ }
+ String stoTransNo = stoTransData.getNewTransNo();
+
+ // STI流水同样按登录大site取号;失败时STO号虽已占用(独立事务)但本事务回滚业务单据 - rqrq
+ TransNoControl stiTransData = transNoControlService.getTransNo(request.getSite(), "STI", 10);
+ if (stiTransData == null || !StringUtils.hasText(stiTransData.getNewTransNo())) {
+ throw new RuntimeException("生成入库事务号失败,请检查当前工厂的STI流水配置:" + request.getSite());
+ }
+ String stiTransNo = stiTransData.getNewTransNo();
+
+ String warehouseId = handlingUnits.get(0).getWarehouseId();
+
+ // 先写出库头:site=来源小site,orderRef1互指入库单号,便于后续对账 - rqrq
+ TransHeader stoHeader = buildTransHeader(request.getFromSite(), stoTransNo, "STO",
+ warehouseId, stiTransNo, "跨小site调拨出库 " + request.getFromSite() + " -> " + request.getToSite(),
+ userId, userName);
+ transHeaderService.save(stoHeader);
+
+ // 再写入库头:site=目标小site,orderRef1互指出库单号 - rqrq
+ TransHeader stiHeader = buildTransHeader(request.getToSite(), stiTransNo, "STI",
+ warehouseId, stoTransNo, "跨小site调拨入库 " + request.getFromSite() + " -> " + request.getToSite(),
+ userId, userName);
+ transHeaderService.save(stiHeader);
+
+ // 按物料+批次+库位+WDR+工程变更级别汇总明细,减少单据行数;库位不改,仅site变化 - rqrq
+ Map> groupedHUs = handlingUnits.stream()
+ .collect(Collectors.groupingBy(hu ->
+ nvl(hu.getPartNo()) + "|" + nvl(hu.getBatchNo()) + "|" +
+ nvl(hu.getLocationId()) + "|" + nvl(hu.getWdr()) + "|" +
+ (StringUtils.hasText(hu.getEngChgLevel()) ? hu.getEngChgLevel() : "1")));
+
+ int itemNo = 1;
+ for (Map.Entry> entry : groupedHUs.entrySet()) {
+ List groupHUs = entry.getValue();
+ HandlingUnit firstHU = groupHUs.get(0);
+ BigDecimal totalQty = groupHUs.stream()
+ .map(hu -> hu.getQty() == null ? BigDecimal.ZERO : hu.getQty())
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ // 写出库明细:site=来源小site,direction=-,库位仍为标签当前库位 - rqrq
+ transDetailService.save(buildTransDetail(request.getFromSite(), stoTransNo, itemNo, firstHU,
+ totalQty, "-", firstHU.getLocationId()));
+ // 写入库明细:site=目标小site,direction=+,库位不变(本次只改site) - rqrq
+ transDetailService.save(buildTransDetail(request.getToSite(), stiTransNo, itemNo, firstHU,
+ totalQty, "+", firstHU.getLocationId()));
+
+ int seqNo = 1;
+ for (HandlingUnit hu : groupHUs) {
+ // 出库子明细按标签落 subNo=unitId,site用来源小site - rqrq
+ transDetailSubService.save(buildTransDetailSub(request.getFromSite(), stoTransNo, (double) itemNo,
+ seqNo, hu, "-"));
+ // 入库子明细同样按标签落行,site用目标小site - rqrq
+ transDetailSubService.save(buildTransDetailSub(request.getToSite(), stiTransNo, (double) itemNo,
+ seqNo, hu, "+"));
+ seqNo++;
+ }
+ itemNo++;
+ }
+
+ // 单据写完后再改标签site;WHERE再次卡在库未预留,影响行数必须等于选中数,否则回滚 - rqrq
+ int updatedRows = crossSiteHuTransferMapper.updateHandlingUnitSite(request, unitIds);
+ if (updatedRows != unitIds.size()) {
+ throw new RuntimeException("更新标签site失败,期望更新" + unitIds.size() + "条,实际" + updatedRows
+ + "条,可能标签已被预留或出库,请刷新后重试");
+ }
+
+ // 标签site改完后再查海安托盘明细:有明细才改sub_site,无明细视为普通仓标签直接跳过 - rqrq
+ syncHaiAnPalletDetailSubSite(request, unitIds);
+
+ // 调拨外部接口当前留空:后续接入失败策略需与本事务一起评估,避免先提交本地再调外部导致不一致 - rqrq
+ callCrossSiteTransferApi(request, handlingUnits, stoTransNo, stiTransNo);
+
+ CrossSiteHuTransferResult result = new CrossSiteHuTransferResult();
+ result.setSuccessCount(updatedRows);
+ result.setFailCount(0);
+ result.setStoTransNo(stoTransNo);
+ result.setStiTransNo(stiTransNo);
+ result.setMessage("跨site调拨完成,出库事务号:" + stoTransNo + ",入库事务号:" + stiTransNo);
+ log.info("跨小site调拨完成 - rqrq,fromSite={}, toSite={}, sto={}, sti={}, count={}",
+ request.getFromSite(), request.getToSite(), stoTransNo, stiTransNo, updatedRows);
+ return result;
+ }
+
+ /**
+ * @author rqrq
+ * @Description 校验提交参数:登录大site、来源/目标小site归属、整批同一来源、禁止同site调拨 - rqrq
+ * @param request 调拨请求
+ */
+ private void validateTransferRequest(CrossSiteHuTransferRequest request) {
+ if (request == null) {
+ throw new RuntimeException("调拨请求不能为空");
+ }
+ if (!StringUtils.hasText(request.getSite())) {
+ throw new RuntimeException("登录工厂site不能为空");
+ }
+ if (!StringUtils.hasText(request.getFromSite())) {
+ throw new RuntimeException("来源site不能为空");
+ }
+ if (!StringUtils.hasText(request.getToSite())) {
+ throw new RuntimeException("目标site不能为空");
+ }
+ if (!StringUtils.hasText(request.getCreatedBy())) {
+ throw new RuntimeException("操作人不能为空");
+ }
+ if (request.getFromSite().equals(request.getToSite())) {
+ throw new RuntimeException("目标site不能与来源site相同");
+ }
+ if (CollectionUtils.isEmpty(request.getSelectedItems())) {
+ throw new RuntimeException("请先选择要调拨的标签");
+ }
+ // 前端可能漏传fromSite一致性,这里以后端选中行再校验一遍,避免跨多个小site混调 - rqrq
+ Set selectedSites = request.getSelectedItems().stream()
+ .map(HandlingUnitVo::getSite)
+ .filter(StringUtils::hasText)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ if (selectedSites.size() > 1) {
+ throw new RuntimeException("选中标签来自多个site,不允许一起调拨:" + String.join(",", selectedSites));
+ }
+ if (!selectedSites.isEmpty() && !selectedSites.contains(request.getFromSite())) {
+ throw new RuntimeException("来源site与选中标签site不一致");
+ }
+ // 来源/目标都必须落在当前登录大site的 site_sub 内,防止把标签调到无关工厂 - rqrq
+ validateSubSiteBelongToLoginSite(request.getSite(), request.getFromSite(), "来源site");
+ validateSubSiteBelongToLoginSite(request.getSite(), request.getToSite(), "目标site");
+ }
+
+ /**
+ * @author rqrq
+ * @Description 二次校验标签仍在库且未预留;漏行、已出库、已预留均整批失败 - rqrq
+ * @param unitIds 前端提交的标签号
+ * @param handlingUnits 加锁查询到的当前库数据
+ */
+ private void validateHandlingUnitStatus(List unitIds, List handlingUnits) {
+ if (CollectionUtils.isEmpty(handlingUnits)) {
+ throw new RuntimeException("未找到可调拨标签,请确认标签仍在来源site且在库未预留");
+ }
+ Map huMap = handlingUnits.stream()
+ .collect(Collectors.toMap(HandlingUnit::getUnitId, hu -> hu, (a, b) -> a));
+ List errors = new ArrayList<>();
+ for (String unitId : unitIds) {
+ HandlingUnit hu = huMap.get(unitId);
+ if (hu == null) {
+ errors.add(unitId + "不存在或不属于来源site");
+ continue;
+ }
+ if (!"Y".equals(hu.getInStockFlag())) {
+ errors.add(unitId + "不在库,当前在库状态=" + nvl(hu.getInStockFlag()));
+ }
+ if (!"N".equals(hu.getReserveFlag())) {
+ errors.add(unitId + "已被预留,当前预留标记=" + nvl(hu.getReserveFlag()));
+ }
+ }
+ if (!errors.isEmpty()) {
+ throw new RuntimeException("标签预留/在库校验失败:" + String.join(";", errors));
+ }
+ }
+
+ /**
+ * @author rqrq
+ * @Description 从选中行提取去重后的条码号,空条码直接失败 - rqrq
+ * @param selectedItems 前端选中标签
+ * @return 去重后的unitId列表
+ */
+ private List collectUnitIds(List selectedItems) {
+ List unitIds = selectedItems.stream()
+ .map(HandlingUnitVo::getUnitId)
+ .filter(StringUtils::hasText)
+ .distinct()
+ .collect(Collectors.toList());
+ if (unitIds.isEmpty()) {
+ throw new RuntimeException("选中标签缺少条码号");
+ }
+ return unitIds;
+ }
+
+ /**
+ * @author rqrq
+ * @Description 校验小site是否属于登录大site的 site_sub 映射,0条则拒绝 - rqrq
+ * @param loginSite 登录大site
+ * @param subSite 待校验小site
+ * @param fieldName 异常提示字段名
+ */
+ private void validateSubSiteBelongToLoginSite(String loginSite, String subSite, String fieldName) {
+ // 按大site+小site查 site_sub;0表示越权或录错,必须中断避免改到无关工厂 - rqrq
+ int count = crossSiteHuTransferMapper.countSiteSubMapping(loginSite, subSite);
+ if (count <= 0) {
+ throw new RuntimeException(fieldName + "[" + subSite + "]不属于当前登录工厂[" + loginSite + "]");
+ }
+ }
+
+ /**
+ * @author rqrq
+ * @Description 判断是否至少填写一个业务查询条件(含可选小site) - rqrq
+ * @param query 查询对象
+ * @return true=允许查询
+ */
+ private boolean hasAnySearchCondition(CrossSiteHuTransferData query) {
+ return StringUtils.hasText(query.getUnitId())
+ || StringUtils.hasText(query.getPartNo())
+ || StringUtils.hasText(query.getBatchNo())
+ || StringUtils.hasText(query.getLocationId())
+ || StringUtils.hasText(query.getWarehouseId())
+ || StringUtils.hasText(query.getSearchSite());
+ }
+
+ /**
+ * @author rqrq
+ * @Description 组装出入库单头;site必须是小site,status直接COMPLETED因为本地调拨同步完成 - rqrq
+ */
+ private TransHeader buildTransHeader(String site, String transNo, String transTypeDb, String warehouseId,
+ String orderRef1, String remark, String userId, String userName) {
+ TransHeader header = new TransHeader();
+ header.setSite(site);
+ header.setTransNo(transNo);
+ header.setTransDate(Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()));
+ header.setTransTypeDb(transTypeDb);
+ header.setWarehouseId(warehouseId);
+ header.setUserId(userId);
+ header.setUserName(userName);
+ header.setRemark(remark);
+ header.setOrderRef1(orderRef1);
+ header.setStatus("COMPLETED");
+ header.setStatusDb("C");
+ header.setEnterDate(new Date());
+ header.setIfsFlag("N");
+ return header;
+ }
+
+ /**
+ * @author rqrq
+ * @Description 组装出入库明细;site按出库/入库分别用来源或目标小site - rqrq
+ */
+ private TransDetail buildTransDetail(String site, String transNo, int itemNo, HandlingUnit hu,
+ BigDecimal totalQty, String direction, String locationId) {
+ TransDetail detail = new TransDetail();
+ detail.setSite(site);
+ detail.setTransNo(transNo);
+ detail.setItemNo((double) itemNo);
+ detail.setPartNo(hu.getPartNo());
+ detail.setTransQty(totalQty);
+ detail.setBatchNo(hu.getBatchNo());
+ detail.setLocationId(locationId);
+ detail.setDirection(direction);
+ detail.setWdrNo(hu.getWdr());
+ detail.setEngChgLevel(hu.getEngChgLevel());
+ return detail;
+ }
+
+ /**
+ * @author rqrq
+ * @Description 组装出入库子明细,一条标签一行,subNo使用unitId便于反查 - rqrq
+ */
+ private TransDetailSub buildTransDetailSub(String site, String transNo, Double itemNo, int seqNo,
+ HandlingUnit hu, String direction) {
+ TransDetailSub detailSub = new TransDetailSub();
+ detailSub.setSite(site);
+ detailSub.setTransNo(transNo);
+ detailSub.setItemNo(itemNo);
+ detailSub.setSubNo(hu.getUnitId());
+ detailSub.setSubQty(hu.getQty() == null ? 0D : hu.getQty().doubleValue());
+ detailSub.setDirection(direction);
+ detailSub.setSeqNo((double) seqNo);
+ detailSub.setPartNo(hu.getPartNo());
+ detailSub.setBatchNo(hu.getBatchNo());
+ detailSub.setLocationId(hu.getLocationId());
+ detailSub.setEngChgLevel(hu.getEngChgLevel());
+ return detailSub;
+ }
+
+ /**
+ * @author rqrq
+ * @Description 同步海安托盘明细sub_site;先按条码查询是否存在明细,没有则跳过,有则改成目标小site - rqrq
+ *
+ * haian_pallet_detail.serial_no 对应 handling_unit.unit_id;sub_site 对应 handling_unit.site。
+ * 查不到明细不算失败,因为跨site调拨页也会处理非海安托盘上的普通标签。
+ *
+ * @param request 含登录大site和目标小site
+ * @param unitIds 已改写handling_unit.site的标签号
+ */
+ private void syncHaiAnPalletDetailSubSite(CrossSiteHuTransferRequest request, List unitIds) {
+ // 按登录大site+条码查海安托盘明细;失败则无法判断是否组盘,中断并回滚以免HU与托盘明细site不一致 - rqrq
+ List haiAnSerialNos = crossSiteHuTransferMapper.listHaiAnPalletDetailSerialNos(request.getSite(), unitIds);
+ if (CollectionUtils.isEmpty(haiAnSerialNos)) {
+ log.info("跨site调拨未命中海安托盘明细,跳过sub_site改写 - rqrq,loginSite={}, count={}",
+ request.getSite(), unitIds.size());
+ return;
+ }
+ // 仅改已存在的明细行,sub_site改为目标小site,与刚改完的handling_unit.site对齐 - rqrq
+ int haiAnUpdatedRows = crossSiteHuTransferMapper.updateHaiAnPalletDetailSubSite(request, request.getSite(), haiAnSerialNos);
+ log.info("跨site调拨已同步海安托盘明细sub_site - rqrq,loginSite={}, toSite={}, hit={}, updated={}",
+ request.getSite(), request.getToSite(), haiAnSerialNos.size(), haiAnUpdatedRows);
+ if (haiAnUpdatedRows <= 0) {
+ throw new RuntimeException("海安托盘明细已查到" + haiAnSerialNos.size() + "条标签,但更新sub_site影响行数为0,请刷新后重试");
+ }
+ }
+
+ /**
+ * @author rqrq
+ * @Description 调用跨site调拨外部接口的预留点;当前不发请求,只记日志,后续补接口时在此接入 - rqrq
+ *
+ * 后续接入时需明确:成功条件、失败是否回滚本地STO/STI/标签site、幂等键用哪张单据号。
+ *
+ * @param request 调拨请求
+ * @param handlingUnits 已校验通过的标签
+ * @param stoTransNo 出库事务号
+ * @param stiTransNo 入库事务号
+ */
+ private void callCrossSiteTransferApi(CrossSiteHuTransferRequest request, List handlingUnits,
+ String stoTransNo, String stiTransNo) {
+ log.info("跨site调拨外部接口暂未接入,本次仅完成本地STO/STI单据和标签site改写 - rqrq,fromSite={}, toSite={}, sto={}, sti={}, count={}",
+ request.getFromSite(), request.getToSite(), stoTransNo, stiTransNo,
+ handlingUnits == null ? 0 : handlingUnits.size());
+ }
+
+ /**
+ * @author rqrq
+ * @Description 空字符串转空,避免分组key出现null导致NPE - rqrq
+ */
+ private String nvl(String value) {
+ return value == null ? "" : value;
+ }
+}
diff --git a/src/main/resources/mapper/warehouse/CrossSiteHuTransferMapper.xml b/src/main/resources/mapper/warehouse/CrossSiteHuTransferMapper.xml
new file mode 100644
index 00000000..d21b821d
--- /dev/null
+++ b/src/main/resources/mapper/warehouse/CrossSiteHuTransferMapper.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+ SELECT
+ h.unit_id AS unitId,
+ h.site,
+ h.parent_unit_id AS parentUnitId,
+ h.unit_type AS unitType,
+ h.unit_type_db AS unitTypeDb,
+ h.part_no AS partNo,
+ h.part_desc AS partDesc,
+ h.qty,
+ h.batch_no AS batchNo,
+ h.location_id AS locationId,
+ h.warehouse_id AS warehouseId,
+ h.wdr,
+ h.availability_control_id AS availabilityControlId,
+ h.status,
+ h.status_db AS statusDb,
+ h.freeze_flag AS freezeFlag,
+ h.merged_flag AS mergedFlag,
+ h.in_stock_flag AS inStockFlag,
+ h.order_ref1 AS orderRef1,
+ h.order_ref2 AS orderRef2,
+ h.order_ref3 AS orderRef3,
+ h.supplier_id AS supplierId,
+ h.customer_id AS customerId,
+ h.manufacture_date AS manufactureDate,
+ h.expired_date AS expiredDate,
+ h.created_date AS createdDate,
+ h.created_by AS createdBy,
+ h.modified_date AS modifiedDate,
+ h.modified_by AS modifiedBy,
+ h.remark,
+ h.source_type AS sourceType,
+ h.source_ref AS sourceRef,
+ h.gross_weight AS grossWeight,
+ h.net_weight AS netWeight,
+ h.weight_unit AS weightUnit,
+ h.volume,
+ h.volume_unit AS volumeUnit,
+ h.qr_code AS qrCode,
+ h.bar_code AS barCode,
+ h.printer_name AS printerName,
+ h.last_print_date AS lastPrintDate,
+ h.print_count AS printCount,
+ h.receive_date AS receiveDate,
+ h.original_qty AS originalQty,
+ h.tid,
+ h.epc,
+ h.length,
+ h.width,
+ h.um_id AS umId,
+ h.reserve_flag AS reserveFlag,
+ h.reserve_order_ref1 AS reserveOrderRef1,
+ h.reserve_order_ref2 AS reserveOrderRef2,
+ h.reserve_order_ref3 AS reserveOrderRef3,
+ h.height,
+ h.eng_chg_level AS engChgLevel
+ FROM handling_unit h WITH (NOLOCK)
+ WHERE h.in_stock_flag = 'Y'
+ AND h.reserve_flag = 'N'
+ AND h.site IN (
+ SELECT ss.subsiteid
+ FROM site_sub ss WITH (NOLOCK)
+ WHERE ss.siteid = #{query.site}
+ )
+
+ AND h.site = #{query.searchSite}
+
+
+ AND h.unit_id LIKE '%' + #{query.unitId} + '%'
+
+
+ AND h.part_no = #{query.partNo}
+
+
+ AND h.batch_no LIKE '%' + #{query.batchNo} + '%'
+
+
+ AND h.location_id LIKE '%' + #{query.locationId} + '%'
+
+
+ AND h.warehouse_id = #{query.warehouseId}
+
+ ORDER BY h.created_date DESC
+
+
+
+
+ SELECT
+ ss.siteid AS siteId,
+ ss.subsiteid AS subSiteId
+ FROM site_sub ss WITH (NOLOCK)
+ WHERE ss.siteid = #{query.site}
+
+ AND ss.subsiteid <> #{query.excludeSite}
+
+ ORDER BY ss.subsiteid
+
+
+
+
+ SELECT COUNT(1)
+ FROM site_sub WITH (NOLOCK)
+ WHERE siteid = #{site}
+ AND subsiteid = #{subSite}
+
+
+
+
+ SELECT
+ h.unit_id,
+ h.site,
+ h.part_no,
+ h.part_desc,
+ h.qty,
+ h.batch_no,
+ h.location_id,
+ h.warehouse_id,
+ h.wdr,
+ h.eng_chg_level,
+ h.in_stock_flag,
+ h.reserve_flag
+ FROM handling_unit h WITH (UPDLOCK, ROWLOCK)
+ WHERE h.site = #{fromSite}
+ AND h.unit_id IN
+
+ #{unitId}
+
+
+
+
+
+ SELECT h.unit_id
+ FROM handling_unit h WITH (NOLOCK)
+ WHERE h.site = #{toSite}
+ AND h.unit_id IN
+
+ #{unitId}
+
+
+
+
+
+ UPDATE handling_unit WITH (ROWLOCK)
+ SET site = #{request.toSite},
+ modified_by = #{request.createdBy},
+ modified_date = GETDATE()
+ WHERE site = #{request.fromSite}
+ AND in_stock_flag = 'Y'
+ AND reserve_flag = 'N'
+ AND unit_id IN
+
+ #{unitId}
+
+
+
+
+
+ SELECT DISTINCT d.serial_no
+ FROM haian_pallet_detail d WITH (NOLOCK)
+ WHERE d.site = #{site}
+ AND d.serial_no IN
+
+ #{unitId}
+
+
+
+
+
+ UPDATE haian_pallet_detail WITH (ROWLOCK)
+ SET sub_site = #{request.toSite}
+ WHERE site = #{site}
+ AND serial_no IN
+
+ #{unitId}
+
+
+
+