Explorar el Código

库存调拨及预存款(A)

lk hace 15 horas
padre
commit
7043fb340f
Se han modificado 35 ficheros con 2904 adiciones y 1 borrados
  1. 126 0
      java/fs-company-app/src/main/java/com/fs/app/controller/FsInventoryTransferController.java
  2. 108 0
      java/fs-saas-admin/src/main/java/com/fs/web/controller/supplychain/SupplyChainInventoryController.java
  3. 71 0
      java/fs-saas-admin/src/main/java/com/fs/web/controller/supplychain/SupplyChainProcurementController.java
  4. 326 0
      java/fs-service/src/main/java/com/fs/his/domain/FsInventoryTransfer.java
  5. 214 0
      java/fs-service/src/main/java/com/fs/his/domain/FsInventoryTransferItem.java
  6. 115 0
      java/fs-service/src/main/java/com/fs/his/domain/FsInventoryTransferLog.java
  7. 24 0
      java/fs-service/src/main/java/com/fs/his/mapper/FsInventoryTransferItemMapper.java
  8. 20 0
      java/fs-service/src/main/java/com/fs/his/mapper/FsInventoryTransferLogMapper.java
  9. 13 0
      java/fs-service/src/main/java/com/fs/his/mapper/FsInventoryTransferMapper.java
  10. 9 0
      java/fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java
  11. 30 0
      java/fs-service/src/main/java/com/fs/his/param/InventoryTransferAuditParam.java
  12. 48 0
      java/fs-service/src/main/java/com/fs/his/param/InventoryTransferItemParam.java
  13. 50 0
      java/fs-service/src/main/java/com/fs/his/param/InventoryTransferParam.java
  14. 45 0
      java/fs-service/src/main/java/com/fs/his/param/InventoryTransferQueryParam.java
  15. 26 0
      java/fs-service/src/main/java/com/fs/his/param/InventoryTransferReceiveParam.java
  16. 74 0
      java/fs-service/src/main/java/com/fs/his/service/IFsInventoryTransferService.java
  17. 15 0
      java/fs-service/src/main/java/com/fs/his/service/IFsStoreProductService.java
  18. 40 0
      java/fs-service/src/main/java/com/fs/his/service/IStorePreDepositService.java
  19. 476 0
      java/fs-service/src/main/java/com/fs/his/service/impl/FsInventoryTransferServiceImpl.java
  20. 21 0
      java/fs-service/src/main/java/com/fs/his/service/impl/FsStoreProductServiceImpl.java
  21. 119 0
      java/fs-service/src/main/java/com/fs/his/service/impl/StorePreDepositServiceImpl.java
  22. 67 0
      java/fs-service/src/main/java/com/fs/his/vo/InventoryTransferItemVO.java
  23. 45 0
      java/fs-service/src/main/java/com/fs/his/vo/InventoryTransferLogVO.java
  24. 118 0
      java/fs-service/src/main/java/com/fs/his/vo/InventoryTransferVO.java
  25. 56 0
      java/fs-service/src/main/java/com/fs/supplychain/domain/SupplyChainInventory.java
  26. 62 0
      java/fs-service/src/main/java/com/fs/supplychain/domain/SupplyChainTransferRecord.java
  27. 44 0
      java/fs-service/src/main/java/com/fs/supplychain/mapper/SupplyChainInventoryMapper.java
  28. 29 0
      java/fs-service/src/main/java/com/fs/supplychain/mapper/SupplyChainTransferRecordMapper.java
  29. 55 0
      java/fs-service/src/main/java/com/fs/supplychain/service/ISupplyChainInventoryService.java
  30. 128 0
      java/fs-service/src/main/java/com/fs/supplychain/service/impl/SupplyChainInventoryServiceImpl.java
  31. 73 0
      java/fs-service/src/main/resources/mapper/supplychain/SupplyChainInventoryMapper.xml
  32. 49 0
      java/fs-service/src/main/resources/mapper/supplychain/SupplyChainTransferRecordMapper.xml
  33. 100 0
      java/fs-user-app/src/main/java/com/fs/app/controller/ImChatController.java
  34. 107 0
      java/fs-user-app/src/main/java/com/fs/app/controller/PreDepositController.java
  35. 1 1
      saasmgnui/src/views/store/purchaseSuggestion/index.vue

+ 126 - 0
java/fs-company-app/src/main/java/com/fs/app/controller/FsInventoryTransferController.java

@@ -0,0 +1,126 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.his.param.InventoryTransferAuditParam;
+import com.fs.his.param.InventoryTransferParam;
+import com.fs.his.param.InventoryTransferQueryParam;
+import com.fs.his.param.InventoryTransferReceiveParam;
+import com.fs.his.service.IFsInventoryTransferService;
+import com.fs.his.vo.InventoryTransferLogVO;
+import com.fs.his.vo.InventoryTransferVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 库存调拨 Controller
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Api("库存调拨")
+@RestController
+@RequestMapping("/app/inventoryTransfer")
+public class FsInventoryTransferController extends AppBaseController {
+
+    @Autowired
+    private IFsInventoryTransferService inventoryTransferService;
+
+    @Login
+    @ApiOperation("分页查询调拨单列表")
+    @GetMapping("/page")
+    public R page(InventoryTransferQueryParam param) {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<InventoryTransferVO> list = inventoryTransferService.selectTransferList(param);
+        PageInfo<InventoryTransferVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
+    @Login
+    @ApiOperation("获取调拨单详情")
+    @GetMapping("/detail")
+    public R detail(@ApiParam(value = "调拨单号", required = true) @RequestParam("transferNo") String transferNo) {
+        InventoryTransferVO vo = inventoryTransferService.selectTransferDetail(transferNo);
+        return R.ok().put("data", vo);
+    }
+
+    @Login
+    @ApiOperation("创建调拨单(草稿)")
+    @PostMapping("/create")
+    public R create(@Valid @RequestBody InventoryTransferParam param) {
+        String transferNo = inventoryTransferService.createTransfer(param);
+        return R.ok().put("transferNo", transferNo).put("msg", "创建成功");
+    }
+
+    @Login
+    @ApiOperation("修改调拨单")
+    @PostMapping("/update")
+    public R update(@Valid @RequestBody InventoryTransferParam param) {
+        inventoryTransferService.updateTransfer(param);
+        return R.ok().put("msg", "修改成功");
+    }
+
+    @Login
+    @ApiOperation("删除调拨单(仅草稿状态可删除)")
+    @PostMapping("/delete")
+    public R delete(@ApiParam(value = "调拨单号", required = true) @RequestParam("transferNo") String transferNo) {
+        inventoryTransferService.deleteTransfer(transferNo);
+        return R.ok().put("msg", "删除成功");
+    }
+
+    @Login
+    @ApiOperation("提交调拨单(草稿 -> 待审核)")
+    @PostMapping("/submit")
+    public R submit(@ApiParam(value = "调拨单号", required = true) @RequestParam("transferNo") String transferNo) {
+        inventoryTransferService.submitTransfer(transferNo);
+        return R.ok().put("msg", "提交成功");
+    }
+
+    @Login
+    @ApiOperation("审批调拨单")
+    @PostMapping("/audit")
+    public R audit(@Valid @RequestBody InventoryTransferAuditParam param) {
+        inventoryTransferService.auditTransfer(param);
+        return R.ok().put("msg", "审批完成");
+    }
+
+    @Login
+    @ApiOperation("发货确认(已审核 -> 运输中)")
+    @PostMapping("/ship")
+    public R ship(@ApiParam(value = "调拨单号", required = true) @RequestParam("transferNo") String transferNo) {
+        inventoryTransferService.shipTransfer(transferNo);
+        return R.ok().put("msg", "发货成功");
+    }
+
+    @Login
+    @ApiOperation("收货确认(运输中 -> 已收货)")
+    @PostMapping("/receive")
+    public R receive(@Valid @RequestBody InventoryTransferReceiveParam param) {
+        inventoryTransferService.receiveTransfer(param);
+        return R.ok().put("msg", "收货成功");
+    }
+
+    @Login
+    @ApiOperation("取消调拨单")
+    @PostMapping("/cancel")
+    public R cancel(@ApiParam(value = "调拨单号", required = true) @RequestParam("transferNo") String transferNo) {
+        inventoryTransferService.cancelTransfer(transferNo);
+        return R.ok().put("msg", "取消成功");
+    }
+
+    @Login
+    @ApiOperation("获取操作日志")
+    @GetMapping("/logs")
+    public R logs(@ApiParam(value = "调拨单号", required = true) @RequestParam("transferNo") String transferNo) {
+        List<InventoryTransferLogVO> logs = inventoryTransferService.selectTransferLogs(transferNo);
+        return R.ok().put("data", logs);
+    }
+}

+ 108 - 0
java/fs-saas-admin/src/main/java/com/fs/web/controller/supplychain/SupplyChainInventoryController.java

@@ -0,0 +1,108 @@
+package com.fs.web.controller.supplychain;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.supplychain.domain.SupplyChainInventory;
+import com.fs.supplychain.domain.SupplyChainTransferRecord;
+import com.fs.supplychain.service.ISupplyChainInventoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 库存管理 Controller
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@RestController
+@RequestMapping("/supplychain/inventory")
+public class SupplyChainInventoryController extends BaseController {
+
+    @Autowired
+    private ISupplyChainInventoryService inventoryService;
+
+    /**
+     * 库存列表
+     */
+    @PreAuthorize("@ss.hasPermi('supplychain:inventory:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SupplyChainInventory inventory) {
+        startPage();
+        List<SupplyChainInventory> list = inventoryService.selectInventoryList(inventory);
+        return getDataTable(list);
+    }
+
+    /**
+     * 库存预警列表
+     */
+    @PreAuthorize("@ss.hasPermi('supplychain:inventory:list')")
+    @GetMapping("/alert")
+    public TableDataInfo alert(SupplyChainInventory inventory) {
+        startPage();
+        List<SupplyChainInventory> list = inventoryService.selectInventoryList(inventory);
+        // 仅返回当前库存 <= 预警阈值的条目
+        list.removeIf(item -> item.getQuantity() > item.getAlertThreshold());
+        return getDataTable(list);
+    }
+
+    /**
+     * 库存调拨
+     */
+    @PreAuthorize("@ss.hasPermi('supplychain:inventory:transfer')")
+    @Log(title = "库存调拨", businessType = BusinessType.UPDATE)
+    @PostMapping("/transfer")
+    public AjaxResult transfer(@RequestBody Map<String, Object> param) {
+        SupplyChainTransferRecord record = new SupplyChainTransferRecord();
+        record.setGoodsName((String) param.get("goodsName"));
+        record.setFromWarehouse((String) param.get("fromWarehouse"));
+        record.setToWarehouse((String) param.get("toWarehouse"));
+        record.setQuantity(param.get("quantity") != null ? ((Number) param.get("quantity")).intValue() : 0);
+        record.setRemark((String) param.get("remark"));
+        record.setOperator(getUsername());
+        record.setCreateBy(getUsername());
+
+        if (param.get("goodsId") != null) {
+            record.setGoodsId(((Number) param.get("goodsId")).longValue());
+        }
+
+        inventoryService.transferInventory(record);
+        return AjaxResult.success("调拨成功");
+    }
+
+    /**
+     * 调拨记录
+     */
+    @PreAuthorize("@ss.hasPermi('supplychain:inventory:transfer')")
+    @GetMapping("/transfer/list")
+    public TableDataInfo transferList(SupplyChainTransferRecord record) {
+        startPage();
+        List<SupplyChainTransferRecord> list = inventoryService.selectTransferRecordList(record);
+        return getDataTable(list);
+    }
+
+    /**
+     * 库存盘点
+     */
+    @PreAuthorize("@ss.hasPermi('supplychain:inventory:stockTake')")
+    @Log(title = "库存盘点", businessType = BusinessType.UPDATE)
+    @PostMapping("/stockTake")
+    public AjaxResult stockTake(@RequestBody Map<String, Object> param) {
+        Long goodsId = param.get("goodsId") != null ? ((Number) param.get("goodsId")).longValue() : null;
+        Integer actualQuantity = param.get("actualQuantity") != null ? ((Number) param.get("actualQuantity")).intValue() : 0;
+        String remark = (String) param.get("remark");
+
+        if (goodsId == null) {
+            return AjaxResult.error("商品ID不能为空");
+        }
+
+        inventoryService.stockTake(goodsId, actualQuantity, remark);
+        return AjaxResult.success("盘点完成");
+    }
+}

+ 71 - 0
java/fs-saas-admin/src/main/java/com/fs/web/controller/supplychain/SupplyChainProcurementController.java

@@ -0,0 +1,71 @@
+package com.fs.web.controller.supplychain;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.supplychain.domain.SupplyChainPurchase;
+import com.fs.supplychain.service.ISupplyChainPurchaseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 采购订单 Controller
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@RestController
+@RequestMapping("/supplychain/procurement")
+public class SupplyChainProcurementController extends BaseController {
+
+    @Autowired
+    private ISupplyChainPurchaseService purchaseService;
+
+    @PreAuthorize("@ss.hasPermi('supplychain:procurement:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SupplyChainPurchase purchase) {
+        startPage();
+        List<SupplyChainPurchase> list = purchaseService.selectPurchaseList(purchase);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('supplychain:procurement:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(purchaseService.selectPurchaseById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('supplychain:procurement:add')")
+    @Log(title = "采购订单", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SupplyChainPurchase purchase) {
+        purchase.setCreateBy(getUsername());
+        return toAjax(purchaseService.insertPurchase(purchase));
+    }
+
+    @PreAuthorize("@ss.hasPermi('supplychain:procurement:edit')")
+    @Log(title = "采购订单", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SupplyChainPurchase purchase) {
+        purchase.setUpdateBy(getUsername());
+        return toAjax(purchaseService.updatePurchase(purchase));
+    }
+
+    @PreAuthorize("@ss.hasPermi('supplychain:procurement:remove')")
+    @Log(title = "采购订单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{id}")
+    public AjaxResult remove(@PathVariable Long id) {
+        return toAjax(purchaseService.deletePurchaseById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('supplychain:procurement:query')")
+    @GetMapping("/track/{id}")
+    public AjaxResult track(@PathVariable Long id) {
+        return AjaxResult.success(purchaseService.selectPurchaseById(id));
+    }
+}

+ 326 - 0
java/fs-service/src/main/java/com/fs/his/domain/FsInventoryTransfer.java

@@ -0,0 +1,326 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 库存调拨单对象 fs_inventory_transfer
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@TableName("fs_inventory_transfer")
+public class FsInventoryTransfer extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 调拨单号 */
+    @Excel(name = "调拨单号")
+    private String transferNo;
+
+    /** 调出门店ID */
+    @NotNull(message = "调出门店不能为空")
+    @Excel(name = "调出门店ID")
+    private Long fromStoreId;
+
+    /** 调出门店名称 */
+    @Excel(name = "调出门店", width = 20)
+    private String fromStoreName;
+
+    /** 调入门店ID */
+    @NotNull(message = "调入门店不能为空")
+    @Excel(name = "调入门店ID")
+    private Long toStoreId;
+
+    /** 调入门店名称 */
+    @Excel(name = "调入门店", width = 20)
+    private String toStoreName;
+
+    /** 调拨总数量 */
+    @Excel(name = "调拨数量")
+    private Integer totalQuantity;
+
+    /** 调拨总金额(成本价) */
+    @Excel(name = "调拨总金额")
+    private BigDecimal totalAmount;
+
+    /** 状态: DRAFT/SUBMITTED/APPROVED/REJECTED/IN_TRANSIT/RECEIVED/CANCELLED */
+    @Excel(name = "状态", readConverterExp = "DRAFT=草稿,SUBMITTED=待审核,APPROVED=已审核,REJECTED=已驳回,IN_TRANSIT=运输中,RECEIVED=已收货,CANCELLED=已取消")
+    private String status;
+
+    /** 申请人ID */
+    private Long applyUserId;
+
+    /** 申请人姓名 */
+    @Excel(name = "申请人", width = 15)
+    private String applyUserName;
+
+    /** 申请时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "申请时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date applyTime;
+
+    /** 审批人ID */
+    private Long approveUserId;
+
+    /** 审批人姓名 */
+    @Excel(name = "审批人", width = 15)
+    private String approveUserName;
+
+    /** 审批时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "审批时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date approveTime;
+
+    /** 审批备注 */
+    private String approveRemark;
+
+    /** 发货人ID */
+    private Long shipUserId;
+
+    /** 发货人姓名 */
+    @Excel(name = "发货人", width = 15)
+    private String shipUserName;
+
+    /** 发货时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "发货时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date shipTime;
+
+    /** 收货人ID */
+    private Long receiveUserId;
+
+    /** 收货人姓名 */
+    @Excel(name = "收货人", width = 15)
+    private String receiveUserName;
+
+    /** 收货时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "收货时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date receiveTime;
+
+    /** 收货备注 */
+    private String receiveRemark;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTransferNo() {
+        return transferNo;
+    }
+
+    public void setTransferNo(String transferNo) {
+        this.transferNo = transferNo;
+    }
+
+    public Long getFromStoreId() {
+        return fromStoreId;
+    }
+
+    public void setFromStoreId(Long fromStoreId) {
+        this.fromStoreId = fromStoreId;
+    }
+
+    public String getFromStoreName() {
+        return fromStoreName;
+    }
+
+    public void setFromStoreName(String fromStoreName) {
+        this.fromStoreName = fromStoreName;
+    }
+
+    public Long getToStoreId() {
+        return toStoreId;
+    }
+
+    public void setToStoreId(Long toStoreId) {
+        this.toStoreId = toStoreId;
+    }
+
+    public String getToStoreName() {
+        return toStoreName;
+    }
+
+    public void setToStoreName(String toStoreName) {
+        this.toStoreName = toStoreName;
+    }
+
+    public Integer getTotalQuantity() {
+        return totalQuantity;
+    }
+
+    public void setTotalQuantity(Integer totalQuantity) {
+        this.totalQuantity = totalQuantity;
+    }
+
+    public BigDecimal getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(BigDecimal totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public Long getApplyUserId() {
+        return applyUserId;
+    }
+
+    public void setApplyUserId(Long applyUserId) {
+        this.applyUserId = applyUserId;
+    }
+
+    public String getApplyUserName() {
+        return applyUserName;
+    }
+
+    public void setApplyUserName(String applyUserName) {
+        this.applyUserName = applyUserName;
+    }
+
+    public Date getApplyTime() {
+        return applyTime;
+    }
+
+    public void setApplyTime(Date applyTime) {
+        this.applyTime = applyTime;
+    }
+
+    public Long getApproveUserId() {
+        return approveUserId;
+    }
+
+    public void setApproveUserId(Long approveUserId) {
+        this.approveUserId = approveUserId;
+    }
+
+    public String getApproveUserName() {
+        return approveUserName;
+    }
+
+    public void setApproveUserName(String approveUserName) {
+        this.approveUserName = approveUserName;
+    }
+
+    public Date getApproveTime() {
+        return approveTime;
+    }
+
+    public void setApproveTime(Date approveTime) {
+        this.approveTime = approveTime;
+    }
+
+    public String getApproveRemark() {
+        return approveRemark;
+    }
+
+    public void setApproveRemark(String approveRemark) {
+        this.approveRemark = approveRemark;
+    }
+
+    public Long getShipUserId() {
+        return shipUserId;
+    }
+
+    public void setShipUserId(Long shipUserId) {
+        this.shipUserId = shipUserId;
+    }
+
+    public String getShipUserName() {
+        return shipUserName;
+    }
+
+    public void setShipUserName(String shipUserName) {
+        this.shipUserName = shipUserName;
+    }
+
+    public Date getShipTime() {
+        return shipTime;
+    }
+
+    public void setShipTime(Date shipTime) {
+        this.shipTime = shipTime;
+    }
+
+    public Long getReceiveUserId() {
+        return receiveUserId;
+    }
+
+    public void setReceiveUserId(Long receiveUserId) {
+        this.receiveUserId = receiveUserId;
+    }
+
+    public String getReceiveUserName() {
+        return receiveUserName;
+    }
+
+    public void setReceiveUserName(String receiveUserName) {
+        this.receiveUserName = receiveUserName;
+    }
+
+    public Date getReceiveTime() {
+        return receiveTime;
+    }
+
+    public void setReceiveTime(Date receiveTime) {
+        this.receiveTime = receiveTime;
+    }
+
+    public String getReceiveRemark() {
+        return receiveRemark;
+    }
+
+    public void setReceiveRemark(String receiveRemark) {
+        this.receiveRemark = receiveRemark;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("id", getId())
+                .append("transferNo", getTransferNo())
+                .append("fromStoreId", getFromStoreId())
+                .append("fromStoreName", getFromStoreName())
+                .append("toStoreId", getToStoreId())
+                .append("toStoreName", getToStoreName())
+                .append("totalQuantity", getTotalQuantity())
+                .append("totalAmount", getTotalAmount())
+                .append("status", getStatus())
+                .append("applyUserId", getApplyUserId())
+                .append("applyUserName", getApplyUserName())
+                .append("applyTime", getApplyTime())
+                .append("createBy", getCreateBy())
+                .append("createTime", getCreateTime())
+                .append("updateBy", getUpdateBy())
+                .append("updateTime", getUpdateTime())
+                .append("remark", getRemark())
+                .toString();
+    }
+}

+ 214 - 0
java/fs-service/src/main/java/com/fs/his/domain/FsInventoryTransferItem.java

@@ -0,0 +1,214 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fs.common.annotation.Excel;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 库存调拨明细对象 fs_inventory_transfer_item
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@TableName("fs_inventory_transfer_item")
+public class FsInventoryTransferItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 关联调拨单号 */
+    private String transferNo;
+
+    /** 商品ID */
+    @NotNull(message = "商品不能为空")
+    private Long productId;
+
+    /** 商品名称 */
+    @Excel(name = "商品名称", width = 30)
+    private String productName;
+
+    /** 商品图片 */
+    private String productImage;
+
+    /** 商品规格 */
+    @Excel(name = "规格")
+    private String productSpec;
+
+    /** 单位 */
+    @Excel(name = "单位")
+    private String unit;
+
+    /** 调拨数量 */
+    @NotNull(message = "调拨数量不能为空")
+    @Min(value = 1, message = "调拨数量必须大于0")
+    @Excel(name = "数量")
+    private Integer quantity;
+
+    /** 成本单价 */
+    @Excel(name = "成本单价")
+    private BigDecimal costPrice;
+
+    /** 小计金额 */
+    @Excel(name = "小计金额")
+    private BigDecimal totalAmount;
+
+    /** 调出前库存 */
+    private Integer fromStockBefore;
+
+    /** 调出后库存 */
+    private Integer fromStockAfter;
+
+    /** 调入前库存 */
+    private Integer toStockBefore;
+
+    /** 调入后库存 */
+    private Integer toStockAfter;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTransferNo() {
+        return transferNo;
+    }
+
+    public void setTransferNo(String transferNo) {
+        this.transferNo = transferNo;
+    }
+
+    public Long getProductId() {
+        return productId;
+    }
+
+    public void setProductId(Long productId) {
+        this.productId = productId;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public void setProductName(String productName) {
+        this.productName = productName;
+    }
+
+    public String getProductImage() {
+        return productImage;
+    }
+
+    public void setProductImage(String productImage) {
+        this.productImage = productImage;
+    }
+
+    public String getProductSpec() {
+        return productSpec;
+    }
+
+    public void setProductSpec(String productSpec) {
+        this.productSpec = productSpec;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(Integer quantity) {
+        this.quantity = quantity;
+    }
+
+    public BigDecimal getCostPrice() {
+        return costPrice;
+    }
+
+    public void setCostPrice(BigDecimal costPrice) {
+        this.costPrice = costPrice;
+    }
+
+    public BigDecimal getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(BigDecimal totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public Integer getFromStockBefore() {
+        return fromStockBefore;
+    }
+
+    public void setFromStockBefore(Integer fromStockBefore) {
+        this.fromStockBefore = fromStockBefore;
+    }
+
+    public Integer getFromStockAfter() {
+        return fromStockAfter;
+    }
+
+    public void setFromStockAfter(Integer fromStockAfter) {
+        this.fromStockAfter = fromStockAfter;
+    }
+
+    public Integer getToStockBefore() {
+        return toStockBefore;
+    }
+
+    public void setToStockBefore(Integer toStockBefore) {
+        this.toStockBefore = toStockBefore;
+    }
+
+    public Integer getToStockAfter() {
+        return toStockAfter;
+    }
+
+    public void setToStockAfter(Integer toStockAfter) {
+        this.toStockAfter = toStockAfter;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("id", getId())
+                .append("transferNo", getTransferNo())
+                .append("productId", getProductId())
+                .append("productName", getProductName())
+                .append("quantity", getQuantity())
+                .append("costPrice", getCostPrice())
+                .append("totalAmount", getTotalAmount())
+                .toString();
+    }
+}

+ 115 - 0
java/fs-service/src/main/java/com/fs/his/domain/FsInventoryTransferLog.java

@@ -0,0 +1,115 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 库存调拨操作日志对象 fs_inventory_transfer_log
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@TableName("fs_inventory_transfer_log")
+public class FsInventoryTransferLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 关联调拨单号 */
+    private String transferNo;
+
+    /** 操作类型 */
+    private String operation;
+
+    /** 操作人ID */
+    private Long operatorId;
+
+    /** 操作人姓名 */
+    private String operatorName;
+
+    /** 操作内容 */
+    private String content;
+
+    /** 操作时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTransferNo() {
+        return transferNo;
+    }
+
+    public void setTransferNo(String transferNo) {
+        this.transferNo = transferNo;
+    }
+
+    public String getOperation() {
+        return operation;
+    }
+
+    public void setOperation(String operation) {
+        this.operation = operation;
+    }
+
+    public Long getOperatorId() {
+        return operatorId;
+    }
+
+    public void setOperatorId(Long operatorId) {
+        this.operatorId = operatorId;
+    }
+
+    public String getOperatorName() {
+        return operatorName;
+    }
+
+    public void setOperatorName(String operatorName) {
+        this.operatorName = operatorName;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("id", getId())
+                .append("transferNo", getTransferNo())
+                .append("operation", getOperation())
+                .append("operatorId", getOperatorId())
+                .append("operatorName", getOperatorName())
+                .append("content", getContent())
+                .append("createTime", getCreateTime())
+                .toString();
+    }
+}

+ 24 - 0
java/fs-service/src/main/java/com/fs/his/mapper/FsInventoryTransferItemMapper.java

@@ -0,0 +1,24 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsInventoryTransferItem;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 库存调拨明细 Mapper
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface FsInventoryTransferItemMapper extends BaseMapper<FsInventoryTransferItem> {
+
+    @Select("SELECT * FROM fs_inventory_transfer_item WHERE transfer_no = #{transferNo}")
+    List<FsInventoryTransferItem> selectByTransferNo(@Param("transferNo") String transferNo);
+
+    @Delete("DELETE FROM fs_inventory_transfer_item WHERE transfer_no = #{transferNo}")
+    int deleteByTransferNo(@Param("transferNo") String transferNo);
+}

+ 20 - 0
java/fs-service/src/main/java/com/fs/his/mapper/FsInventoryTransferLogMapper.java

@@ -0,0 +1,20 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsInventoryTransferLog;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 库存调拨操作日志 Mapper
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface FsInventoryTransferLogMapper extends BaseMapper<FsInventoryTransferLog> {
+
+    @Select("SELECT * FROM fs_inventory_transfer_log WHERE transfer_no = #{transferNo} ORDER BY create_time ASC")
+    List<FsInventoryTransferLog> selectByTransferNo(@Param("transferNo") String transferNo);
+}

+ 13 - 0
java/fs-service/src/main/java/com/fs/his/mapper/FsInventoryTransferMapper.java

@@ -0,0 +1,13 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsInventoryTransfer;
+
+/**
+ * 库存调拨单 Mapper
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface FsInventoryTransferMapper extends BaseMapper<FsInventoryTransfer> {
+}

+ 9 - 0
java/fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java

@@ -269,6 +269,15 @@ public interface FsStoreProductMapper {
     @Update({"UPDATE fs_store_product SET stock=#{stock}  WHERE product_id = #{productId} "})
     int updateStoreProductStock(@Param("productId") Long productId, @Param("stock") Integer stock);
 
+    @Select("SELECT stock FROM fs_store_product WHERE store_id = #{storeId} AND product_id = #{productId}")
+    Integer getStoreProductStock(@Param("storeId") Long storeId, @Param("productId") Long productId);
+
+    @Update("UPDATE fs_store_product SET stock = stock - #{num} WHERE store_id = #{storeId} AND product_id = #{productId} AND stock >= #{num}")
+    int deductStoreProductStock(@Param("storeId") Long storeId, @Param("productId") Long productId, @Param("num") Integer num);
+
+    @Update("UPDATE fs_store_product SET stock = stock + #{num} WHERE store_id = #{storeId} AND product_id = #{productId}")
+    int addStoreProductStock(@Param("storeId") Long storeId, @Param("productId") Long productId, @Param("num") Integer num);
+
 
     /**
      * 批量获取商品信息

+ 30 - 0
java/fs-service/src/main/java/com/fs/his/param/InventoryTransferAuditParam.java

@@ -0,0 +1,30 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 库存调拨审批参数
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferAuditParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank(message = "调拨单号不能为空")
+    @ApiModelProperty("调拨单号")
+    private String transferNo;
+
+    @NotBlank(message = "审批结果不能为空")
+    @ApiModelProperty("审批结果: APPROVED-通过 / REJECTED-驳回")
+    private String approveResult;
+
+    @ApiModelProperty("审批备注")
+    private String approveRemark;
+}

+ 48 - 0
java/fs-service/src/main/java/com/fs/his/param/InventoryTransferItemParam.java

@@ -0,0 +1,48 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 库存调拨明细参数
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferItemParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @NotNull(message = "商品不能为空")
+    @ApiModelProperty("商品ID")
+    private Long productId;
+
+    @ApiModelProperty("商品名称")
+    private String productName;
+
+    @ApiModelProperty("商品图片")
+    private String productImage;
+
+    @ApiModelProperty("商品规格")
+    private String productSpec;
+
+    @ApiModelProperty("单位")
+    private String unit;
+
+    @NotNull(message = "调拨数量不能为空")
+    @Min(value = 1, message = "调拨数量必须大于0")
+    @ApiModelProperty("调拨数量")
+    private Integer quantity;
+
+    @ApiModelProperty("成本单价")
+    private BigDecimal costPrice;
+
+    @ApiModelProperty("小计金额")
+    private BigDecimal totalAmount;
+}

+ 50 - 0
java/fs-service/src/main/java/com/fs/his/param/InventoryTransferParam.java

@@ -0,0 +1,50 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 库存调拨单参数
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键(编辑时传入) */
+    @ApiModelProperty("调拨单ID")
+    private Long id;
+
+    /** 调拨单号(编辑时传入) */
+    @ApiModelProperty("调拨单号")
+    private String transferNo;
+
+    @NotNull(message = "调出门店不能为空")
+    @ApiModelProperty("调出门店ID")
+    private Long fromStoreId;
+
+    @ApiModelProperty("调出门店名称")
+    private String fromStoreName;
+
+    @NotNull(message = "调入门店不能为空")
+    @ApiModelProperty("调入门店ID")
+    private Long toStoreId;
+
+    @ApiModelProperty("调入门店名称")
+    private String toStoreName;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @NotEmpty(message = "调拨明细不能为空")
+    @ApiModelProperty("调拨明细")
+    private List<InventoryTransferItemParam> items;
+}

+ 45 - 0
java/fs-service/src/main/java/com/fs/his/param/InventoryTransferQueryParam.java

@@ -0,0 +1,45 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 库存调拨单查询参数
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferQueryParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("调拨单号")
+    private String transferNo;
+
+    @ApiModelProperty("调出门店ID")
+    private Long fromStoreId;
+
+    @ApiModelProperty("调入门店ID")
+    private Long toStoreId;
+
+    @ApiModelProperty("状态: DRAFT/SUBMITTED/APPROVED/REJECTED/IN_TRANSIT/RECEIVED/CANCELLED")
+    private String status;
+
+    @ApiModelProperty("申请人ID")
+    private Long applyUserId;
+
+    @ApiModelProperty("起始创建时间 (yyyy-MM-dd HH:mm:ss)")
+    private String beginTime;
+
+    @ApiModelProperty("结束创建时间 (yyyy-MM-dd HH:mm:ss)")
+    private String endTime;
+
+    @ApiModelProperty("页码")
+    private Integer pageNum = 1;
+
+    @ApiModelProperty("每页大小")
+    private Integer pageSize = 10;
+}

+ 26 - 0
java/fs-service/src/main/java/com/fs/his/param/InventoryTransferReceiveParam.java

@@ -0,0 +1,26 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 库存调拨收货参数
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferReceiveParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank(message = "调拨单号不能为空")
+    @ApiModelProperty("调拨单号")
+    private String transferNo;
+
+    @ApiModelProperty("收货备注")
+    private String receiveRemark;
+}

+ 74 - 0
java/fs-service/src/main/java/com/fs/his/service/IFsInventoryTransferService.java

@@ -0,0 +1,74 @@
+package com.fs.his.service;
+
+import com.fs.his.param.InventoryTransferAuditParam;
+import com.fs.his.param.InventoryTransferParam;
+import com.fs.his.param.InventoryTransferQueryParam;
+import com.fs.his.param.InventoryTransferReceiveParam;
+import com.fs.his.vo.InventoryTransferLogVO;
+import com.fs.his.vo.InventoryTransferVO;
+
+import java.util.List;
+
+/**
+ * 库存调拨 Service 接口
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface IFsInventoryTransferService {
+
+    /**
+     * 查询调拨单列表(分页由 PageHelper 拦截处理)
+     */
+    List<InventoryTransferVO> selectTransferList(InventoryTransferQueryParam param);
+
+    /**
+     * 查询调拨单详情(含明细)
+     */
+    InventoryTransferVO selectTransferDetail(String transferNo);
+
+    /**
+     * 创建调拨单(草稿)
+     */
+    String createTransfer(InventoryTransferParam param);
+
+    /**
+     * 修改调拨单(仅草稿状态可修改)
+     */
+    void updateTransfer(InventoryTransferParam param);
+
+    /**
+     * 删除调拨单(仅草稿状态可删除)
+     */
+    void deleteTransfer(String transferNo);
+
+    /**
+     * 提交调拨单(草稿 -> 待审核)
+     */
+    void submitTransfer(String transferNo);
+
+    /**
+     * 审批调拨单(待审核 -> 已审核/已驳回)
+     */
+    void auditTransfer(InventoryTransferAuditParam param);
+
+    /**
+     * 发货确认(已审核 -> 运输中)
+     */
+    void shipTransfer(String transferNo);
+
+    /**
+     * 收货确认(运输中 -> 已收货)
+     */
+    void receiveTransfer(InventoryTransferReceiveParam param);
+
+    /**
+     * 取消调拨单(草稿/待审核 -> 已取消)
+     */
+    void cancelTransfer(String transferNo);
+
+    /**
+     * 查询调拨单操作日志
+     */
+    List<InventoryTransferLogVO> selectTransferLogs(String transferNo);
+}

+ 15 - 0
java/fs-service/src/main/java/com/fs/his/service/IFsStoreProductService.java

@@ -108,4 +108,19 @@ public interface IFsStoreProductService
     List<FsStoreProduct> getStoreProductInProductIds(List<Long> productIds);
 
     List<FsStoreProductListVO> liveList(LiveGoods liveId);
+
+    /**
+     * 获取指定门店指定商品的库存
+     */
+    Integer getStoreProductStock(Long storeId, Long productId);
+
+    /**
+     * 扣减指定门店指定商品的库存
+     */
+    void deductStoreProductStock(Long storeId, Long productId, Integer num);
+
+    /**
+     * 增加指定门店指定商品的库存
+     */
+    void addStoreProductStock(Long storeId, Long productId, Integer num);
 }

+ 40 - 0
java/fs-service/src/main/java/com/fs/his/service/IStorePreDepositService.java

@@ -0,0 +1,40 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.StorePreDeposit;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * C端预存款 Service 接口
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface IStorePreDepositService {
+
+    /**
+     * 查询预存款记录列表
+     */
+    List<StorePreDeposit> selectPreDepositList(Long customerId, Integer type);
+
+    /**
+     * 查询客户当前余额
+     */
+    BigDecimal getBalance(Long customerId);
+
+    /**
+     * 充值
+     */
+    StorePreDeposit recharge(Long customerId, String customerName, BigDecimal amount, String payMethod, String remark);
+
+    /**
+     * 消费
+     */
+    StorePreDeposit consume(Long customerId, String customerName, BigDecimal amount, String remark);
+
+    /**
+     * 退款
+     */
+    StorePreDeposit refund(Long customerId, String customerName, BigDecimal amount, String remark);
+}

+ 476 - 0
java/fs-service/src/main/java/com/fs/his/service/impl/FsInventoryTransferServiceImpl.java

@@ -0,0 +1,476 @@
+package com.fs.his.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.his.domain.FsInventoryTransfer;
+import com.fs.his.domain.FsInventoryTransferItem;
+import com.fs.his.domain.FsInventoryTransferLog;
+import com.fs.his.domain.FsStore;
+import com.fs.his.mapper.FsInventoryTransferItemMapper;
+import com.fs.his.mapper.FsInventoryTransferLogMapper;
+import com.fs.his.mapper.FsInventoryTransferMapper;
+import com.fs.his.param.InventoryTransferAuditParam;
+import com.fs.his.param.InventoryTransferItemParam;
+import com.fs.his.param.InventoryTransferParam;
+import com.fs.his.param.InventoryTransferQueryParam;
+import com.fs.his.param.InventoryTransferReceiveParam;
+import com.fs.his.service.IFsInventoryTransferService;
+import com.fs.his.service.IFsStoreProductService;
+import com.fs.his.service.IFsStoreService;
+import com.fs.his.vo.InventoryTransferItemVO;
+import com.fs.his.vo.InventoryTransferLogVO;
+import com.fs.his.vo.InventoryTransferVO;
+import com.fs.utils.TwelveDigitSnowflake;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 库存调拨 Service 实现
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Service
+public class FsInventoryTransferServiceImpl implements IFsInventoryTransferService {
+
+    @Autowired
+    private FsInventoryTransferMapper transferMapper;
+
+    @Autowired
+    private FsInventoryTransferItemMapper transferItemMapper;
+
+    @Autowired
+    private FsInventoryTransferLogMapper transferLogMapper;
+
+    @Autowired
+    private IFsStoreService storeService;
+
+    @Autowired
+    private IFsStoreProductService storeProductService;
+
+    private static final TwelveDigitSnowflake SNOWFLAKE = new TwelveDigitSnowflake(1);
+
+    /**
+     * 调拨单状态常量
+     */
+    public static final String STATUS_DRAFT = "DRAFT";
+    public static final String STATUS_SUBMITTED = "SUBMITTED";
+    public static final String STATUS_APPROVED = "APPROVED";
+    public static final String STATUS_REJECTED = "REJECTED";
+    public static final String STATUS_IN_TRANSIT = "IN_TRANSIT";
+    public static final String STATUS_RECEIVED = "RECEIVED";
+    public static final String STATUS_CANCELLED = "CANCELLED";
+
+    @Override
+    public List<InventoryTransferVO> selectTransferList(InventoryTransferQueryParam param) {
+        LambdaQueryWrapper<FsInventoryTransfer> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(param.getFromStoreId() != null, FsInventoryTransfer::getFromStoreId, param.getFromStoreId());
+        wrapper.eq(param.getToStoreId() != null, FsInventoryTransfer::getToStoreId, param.getToStoreId());
+        wrapper.eq(StrUtil.isNotBlank(param.getStatus()), FsInventoryTransfer::getStatus, param.getStatus());
+        wrapper.eq(param.getApplyUserId() != null, FsInventoryTransfer::getApplyUserId, param.getApplyUserId());
+        wrapper.eq(StrUtil.isNotBlank(param.getTransferNo()), FsInventoryTransfer::getTransferNo, param.getTransferNo());
+        wrapper.apply(StrUtil.isNotBlank(param.getBeginTime()), "create_time >= '" + param.getBeginTime() + "'");
+        wrapper.apply(StrUtil.isNotBlank(param.getEndTime()), "create_time <= '" + param.getEndTime() + "'");
+        wrapper.orderByDesc(FsInventoryTransfer::getCreateTime);
+
+        List<FsInventoryTransfer> list = transferMapper.selectList(wrapper);
+        return list.stream().map(this::convertToVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public InventoryTransferVO selectTransferDetail(String transferNo) {
+        FsInventoryTransfer transfer = getByTransferNo(transferNo);
+        InventoryTransferVO vo = convertToVO(transfer);
+
+        // 查询明细
+        List<FsInventoryTransferItem> items = transferItemMapper.selectByTransferNo(transferNo);
+        vo.setItems(items.stream().map(this::convertItemToVO).collect(Collectors.toList()));
+
+        // 查询操作日志
+        List<FsInventoryTransferLog> logs = transferLogMapper.selectByTransferNo(transferNo);
+        vo.setLogs(logs.stream().map(this::convertLogToVO).collect(Collectors.toList()));
+
+        return vo;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String createTransfer(InventoryTransferParam param) {
+        // 校验调出门店和调入门店不能相同
+        if (param.getFromStoreId().equals(param.getToStoreId())) {
+            throw new CustomException("调出门店和调入门店不能相同");
+        }
+
+        // 生成调拨单号
+        String transferNo = generateTransferNo();
+
+        // 计算总数量和总金额
+        int totalQuantity = 0;
+        BigDecimal totalAmount = BigDecimal.ZERO;
+        for (InventoryTransferItemParam itemParam : param.getItems()) {
+            totalQuantity += itemParam.getQuantity();
+            if (itemParam.getTotalAmount() != null) {
+                totalAmount = totalAmount.add(itemParam.getTotalAmount());
+            }
+        }
+
+        // 构建调拨单
+        FsInventoryTransfer transfer = new FsInventoryTransfer();
+        transfer.setTransferNo(transferNo);
+        transfer.setFromStoreId(param.getFromStoreId());
+        transfer.setToStoreId(param.getToStoreId());
+        transfer.setTotalQuantity(totalQuantity);
+        transfer.setTotalAmount(totalAmount);
+        transfer.setStatus(STATUS_DRAFT);
+        transfer.setApplyUserId(SecurityUtils.getUserId());
+        transfer.setApplyUserName(SecurityUtils.getUsername());
+        transfer.setApplyTime(new Date());
+        transfer.setRemark(param.getRemark());
+
+        // 设置创建人
+        transfer.setCreateBy(SecurityUtils.getUsername());
+        transfer.setCreateTime(DateUtils.getNowDate());
+
+        // 设置门店名称
+        resolveStoreName(transfer);
+
+        transferMapper.insert(transfer);
+
+        // 保存明细
+        saveTransferItems(transferNo, param.getItems());
+
+        // 记录日志
+        addLog(transferNo, "CREATE", "创建调拨单");
+
+        return transferNo;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateTransfer(InventoryTransferParam param) {
+        FsInventoryTransfer transfer = getByTransferNo(param.getTransferNo());
+        if (!STATUS_DRAFT.equals(transfer.getStatus())) {
+            throw new CustomException("仅草稿状态的调拨单可修改");
+        }
+
+        // 校验调出门店和调入门店不能相同
+        if (param.getFromStoreId().equals(param.getToStoreId())) {
+            throw new CustomException("调出门店和调入门店不能相同");
+        }
+
+        // 计算总数量和总金额
+        int totalQuantity = 0;
+        BigDecimal totalAmount = BigDecimal.ZERO;
+        for (InventoryTransferItemParam itemParam : param.getItems()) {
+            totalQuantity += itemParam.getQuantity();
+            if (itemParam.getTotalAmount() != null) {
+                totalAmount = totalAmount.add(itemParam.getTotalAmount());
+            }
+        }
+
+        // 更新调拨单
+        transfer.setFromStoreId(param.getFromStoreId());
+        transfer.setToStoreId(param.getToStoreId());
+        transfer.setTotalQuantity(totalQuantity);
+        transfer.setTotalAmount(totalAmount);
+        transfer.setRemark(param.getRemark());
+        transfer.setUpdateBy(SecurityUtils.getUsername());
+        transfer.setUpdateTime(DateUtils.getNowDate());
+        resolveStoreName(transfer);
+        transferMapper.updateById(transfer);
+
+        // 删除旧明细,重新插入
+        transferItemMapper.deleteByTransferNo(param.getTransferNo());
+        saveTransferItems(param.getTransferNo(), param.getItems());
+
+        // 记录日志
+        addLog(param.getTransferNo(), "UPDATE", "修改调拨单");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteTransfer(String transferNo) {
+        FsInventoryTransfer transfer = getByTransferNo(transferNo);
+        if (!STATUS_DRAFT.equals(transfer.getStatus())) {
+            throw new CustomException("仅草稿状态的调拨单可删除");
+        }
+        transferItemMapper.deleteByTransferNo(transferNo);
+        transferMapper.deleteById(transfer.getId());
+        // 清理日志
+        LambdaQueryWrapper<FsInventoryTransferLog> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(FsInventoryTransferLog::getTransferNo, transferNo);
+        transferLogMapper.delete(wrapper);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitTransfer(String transferNo) {
+        FsInventoryTransfer transfer = getByTransferNo(transferNo);
+        if (!STATUS_DRAFT.equals(transfer.getStatus())) {
+            throw new CustomException("仅草稿状态的调拨单可提交");
+        }
+
+        // 校验调拨单至少有一条明细
+        List<FsInventoryTransferItem> items = transferItemMapper.selectByTransferNo(transferNo);
+        if (items.isEmpty()) {
+            throw new CustomException("调拨单至少需要一条明细");
+        }
+
+        transfer.setStatus(STATUS_SUBMITTED);
+        transfer.setApplyTime(new Date());
+        transfer.setUpdateBy(SecurityUtils.getUsername());
+        transfer.setUpdateTime(DateUtils.getNowDate());
+        transferMapper.updateById(transfer);
+
+        addLog(transferNo, "SUBMIT", "提交调拨单");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void auditTransfer(InventoryTransferAuditParam param) {
+        FsInventoryTransfer transfer = getByTransferNo(param.getTransferNo());
+        if (!STATUS_SUBMITTED.equals(transfer.getStatus())) {
+            throw new CustomException("仅待审核状态的调拨单可审批");
+        }
+
+        if (STATUS_APPROVED.equals(param.getApproveResult())) {
+            transfer.setStatus(STATUS_APPROVED);
+        } else if (STATUS_REJECTED.equals(param.getApproveResult())) {
+            transfer.setStatus(STATUS_REJECTED);
+        } else {
+            throw new CustomException("审批结果不正确");
+        }
+
+        transfer.setApproveUserId(SecurityUtils.getUserId());
+        transfer.setApproveUserName(SecurityUtils.getUsername());
+        transfer.setApproveTime(new Date());
+        transfer.setApproveRemark(param.getApproveRemark());
+        transfer.setUpdateBy(SecurityUtils.getUsername());
+        transfer.setUpdateTime(DateUtils.getNowDate());
+        transferMapper.updateById(transfer);
+
+        String operation = STATUS_APPROVED.equals(param.getApproveResult()) ? "APPROVE" : "REJECT";
+        String content = STATUS_APPROVED.equals(param.getApproveResult()) ?
+                "审批通过" + (StrUtil.isNotBlank(param.getApproveRemark()) ? ":" + param.getApproveRemark() : "") :
+                "审批驳回" + (StrUtil.isNotBlank(param.getApproveRemark()) ? ":" + param.getApproveRemark() : "");
+        addLog(param.getTransferNo(), operation, content);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void shipTransfer(String transferNo) {
+        FsInventoryTransfer transfer = getByTransferNo(transferNo);
+        if (!STATUS_APPROVED.equals(transfer.getStatus())) {
+            throw new CustomException("仅已审核状态的调拨单可发货");
+        }
+
+        // 扣减调出门店的库存
+        List<FsInventoryTransferItem> items = transferItemMapper.selectByTransferNo(transferNo);
+        for (FsInventoryTransferItem item : items) {
+            // 记录调出前库存
+            Integer fromStockBefore = storeProductService.getStoreProductStock(transfer.getFromStoreId(), item.getProductId());
+            item.setFromStockBefore(fromStockBefore);
+
+            // 扣减调出门店库存
+            storeProductService.deductStoreProductStock(transfer.getFromStoreId(), item.getProductId(), item.getQuantity());
+
+            // 记录调出后库存
+            Integer fromStockAfter = storeProductService.getStoreProductStock(transfer.getFromStoreId(), item.getProductId());
+            item.setFromStockAfter(fromStockAfter);
+
+            transferItemMapper.updateById(item);
+        }
+
+        transfer.setStatus(STATUS_IN_TRANSIT);
+        transfer.setShipUserId(SecurityUtils.getUserId());
+        transfer.setShipUserName(SecurityUtils.getUsername());
+        transfer.setShipTime(new Date());
+        transfer.setUpdateBy(SecurityUtils.getUsername());
+        transfer.setUpdateTime(DateUtils.getNowDate());
+        transferMapper.updateById(transfer);
+
+        addLog(transferNo, "SHIP", "发货出库");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void receiveTransfer(InventoryTransferReceiveParam param) {
+        FsInventoryTransfer transfer = getByTransferNo(param.getTransferNo());
+        if (!STATUS_IN_TRANSIT.equals(transfer.getStatus())) {
+            throw new CustomException("仅运输中状态的调拨单可收货");
+        }
+
+        // 增加调入门店的库存
+        List<FsInventoryTransferItem> items = transferItemMapper.selectByTransferNo(param.getTransferNo());
+        for (FsInventoryTransferItem item : items) {
+            // 记录调入前库存
+            Integer toStockBefore = storeProductService.getStoreProductStock(transfer.getToStoreId(), item.getProductId());
+            item.setToStockBefore(toStockBefore);
+
+            // 增加调入门店库存
+            storeProductService.addStoreProductStock(transfer.getToStoreId(), item.getProductId(), item.getQuantity());
+
+            // 记录调入后库存
+            Integer toStockAfter = storeProductService.getStoreProductStock(transfer.getToStoreId(), item.getProductId());
+            item.setToStockAfter(toStockAfter);
+
+            transferItemMapper.updateById(item);
+        }
+
+        transfer.setStatus(STATUS_RECEIVED);
+        transfer.setReceiveUserId(SecurityUtils.getUserId());
+        transfer.setReceiveUserName(SecurityUtils.getUsername());
+        transfer.setReceiveTime(new Date());
+        transfer.setReceiveRemark(param.getReceiveRemark());
+        transfer.setUpdateBy(SecurityUtils.getUsername());
+        transfer.setUpdateTime(DateUtils.getNowDate());
+        transferMapper.updateById(transfer);
+
+        addLog(param.getTransferNo(), "RECEIVE", "收货入库" +
+                (StrUtil.isNotBlank(param.getReceiveRemark()) ? ":" + param.getReceiveRemark() : ""));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelTransfer(String transferNo) {
+        FsInventoryTransfer transfer = getByTransferNo(transferNo);
+        if (!(STATUS_DRAFT.equals(transfer.getStatus()) || STATUS_SUBMITTED.equals(transfer.getStatus()))) {
+            throw new CustomException("仅草稿或待审核状态的调拨单可取消");
+        }
+
+        transfer.setStatus(STATUS_CANCELLED);
+        transfer.setUpdateBy(SecurityUtils.getUsername());
+        transfer.setUpdateTime(DateUtils.getNowDate());
+        transferMapper.updateById(transfer);
+
+        addLog(transferNo, "CANCEL", "取消调拨单");
+    }
+
+    @Override
+    public List<InventoryTransferLogVO> selectTransferLogs(String transferNo) {
+        List<FsInventoryTransferLog> logs = transferLogMapper.selectByTransferNo(transferNo);
+        return logs.stream().map(this::convertLogToVO).collect(Collectors.toList());
+    }
+
+    // ==================== 私有方法 ====================
+
+    private String generateTransferNo() {
+        String datePart = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        long seq = SNOWFLAKE.nextId();
+        return "DB" + datePart + String.format("%06d", seq % 1000000);
+    }
+
+    private FsInventoryTransfer getByTransferNo(String transferNo) {
+        LambdaQueryWrapper<FsInventoryTransfer> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(FsInventoryTransfer::getTransferNo, transferNo);
+        FsInventoryTransfer transfer = transferMapper.selectOne(wrapper);
+        if (transfer == null) {
+            throw new CustomException("调拨单不存在:" + transferNo);
+        }
+        return transfer;
+    }
+
+    private void resolveStoreName(FsInventoryTransfer transfer) {
+        if (transfer.getFromStoreId() != null) {
+            FsStore fromStore = storeService.selectFsStoreByStoreId(transfer.getFromStoreId());
+            if (fromStore != null) {
+                transfer.setFromStoreName(fromStore.getStoreName());
+            }
+        }
+        if (transfer.getToStoreId() != null) {
+            FsStore toStore = storeService.selectFsStoreByStoreId(transfer.getToStoreId());
+            if (toStore != null) {
+                transfer.setToStoreName(toStore.getStoreName());
+            }
+        }
+    }
+
+    private void saveTransferItems(String transferNo, List<InventoryTransferItemParam> items) {
+        for (InventoryTransferItemParam itemParam : items) {
+            FsInventoryTransferItem item = new FsInventoryTransferItem();
+            item.setTransferNo(transferNo);
+            item.setProductId(itemParam.getProductId());
+            item.setProductName(itemParam.getProductName());
+            item.setProductImage(itemParam.getProductImage());
+            item.setProductSpec(itemParam.getProductSpec());
+            item.setUnit(itemParam.getUnit());
+            item.setQuantity(itemParam.getQuantity());
+            item.setCostPrice(itemParam.getCostPrice());
+            item.setTotalAmount(itemParam.getTotalAmount());
+            item.setCreateTime(new Date());
+            transferItemMapper.insert(item);
+        }
+    }
+
+    private void addLog(String transferNo, String operation, String content) {
+        FsInventoryTransferLog log = new FsInventoryTransferLog();
+        log.setTransferNo(transferNo);
+        log.setOperation(operation);
+        log.setOperatorId(SecurityUtils.getUserId());
+        log.setOperatorName(SecurityUtils.getUsername());
+        log.setContent(content);
+        log.setCreateTime(new Date());
+        transferLogMapper.insert(log);
+    }
+
+    private InventoryTransferVO convertToVO(FsInventoryTransfer transfer) {
+        InventoryTransferVO vo = new InventoryTransferVO();
+        BeanUtils.copyProperties(transfer, vo);
+        vo.setStatusName(getStatusName(transfer.getStatus()));
+        return vo;
+    }
+
+    private InventoryTransferItemVO convertItemToVO(FsInventoryTransferItem item) {
+        InventoryTransferItemVO vo = new InventoryTransferItemVO();
+        BeanUtils.copyProperties(item, vo);
+        return vo;
+    }
+
+    private InventoryTransferLogVO convertLogToVO(FsInventoryTransferLog log) {
+        InventoryTransferLogVO vo = new InventoryTransferLogVO();
+        BeanUtils.copyProperties(log, vo);
+        vo.setOperationName(getOperationName(log.getOperation()));
+        return vo;
+    }
+
+    private String getStatusName(String status) {
+        switch (status) {
+            case STATUS_DRAFT: return "草稿";
+            case STATUS_SUBMITTED: return "待审核";
+            case STATUS_APPROVED: return "已审核";
+            case STATUS_REJECTED: return "已驳回";
+            case STATUS_IN_TRANSIT: return "运输中";
+            case STATUS_RECEIVED: return "已收货";
+            case STATUS_CANCELLED: return "已取消";
+            default: return status;
+        }
+    }
+
+    private String getOperationName(String operation) {
+        switch (operation) {
+            case "CREATE": return "创建";
+            case "UPDATE": return "修改";
+            case "SUBMIT": return "提交";
+            case "APPROVE": return "审批通过";
+            case "REJECT": return "审批驳回";
+            case "SHIP": return "发货";
+            case "RECEIVE": return "收货";
+            case "CANCEL": return "取消";
+            default: return operation;
+        }
+    }
+}

+ 21 - 0
java/fs-service/src/main/java/com/fs/his/service/impl/FsStoreProductServiceImpl.java

@@ -888,4 +888,25 @@ public class FsStoreProductServiceImpl implements IFsStoreProductService {
     public List<FsStoreProductListVO> liveList(LiveGoods liveId) {
         return fsStoreProductMapper.liveList(liveId);
     }
+
+    @Override
+    public Integer getStoreProductStock(Long storeId, Long productId) {
+        Integer stock = fsStoreProductMapper.getStoreProductStock(storeId, productId);
+        return stock == null ? 0 : stock;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deductStoreProductStock(Long storeId, Long productId, Integer num) {
+        int affected = fsStoreProductMapper.deductStoreProductStock(storeId, productId, num);
+        if (affected <= 0) {
+            throw new CustomException("扣减库存失败,库存不足或商品不存在");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void addStoreProductStock(Long storeId, Long productId, Integer num) {
+        fsStoreProductMapper.addStoreProductStock(storeId, productId, num);
+    }
 }

+ 119 - 0
java/fs-service/src/main/java/com/fs/his/service/impl/StorePreDepositServiceImpl.java

@@ -0,0 +1,119 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.StorePreDeposit;
+import com.fs.his.mapper.StorePreDepositMapper;
+import com.fs.his.service.IStorePreDepositService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * C端预存款 Service 实现
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Service
+public class StorePreDepositServiceImpl implements IStorePreDepositService {
+
+    @Autowired
+    private StorePreDepositMapper storePreDepositMapper;
+
+    @Override
+    public List<StorePreDeposit> selectPreDepositList(Long customerId, Integer type) {
+        StorePreDeposit query = new StorePreDeposit();
+        query.setCustomerId(customerId);
+        query.setType(type);
+        return storePreDepositMapper.selectStorePreDepositList(query);
+    }
+
+    @Override
+    public BigDecimal getBalance(Long customerId) {
+        StorePreDeposit lastRecord = getLastRecord(customerId);
+        if (lastRecord != null && lastRecord.getBalance() != null) {
+            return lastRecord.getBalance();
+        }
+        return BigDecimal.ZERO;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public StorePreDeposit recharge(Long customerId, String customerName, BigDecimal amount,
+                                     String payMethod, String remark) {
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new CustomException("充值金额必须大于0");
+        }
+        BigDecimal currentBalance = getBalance(customerId);
+        StorePreDeposit record = new StorePreDeposit();
+        record.setCustomerId(customerId);
+        record.setCustomerName(customerName);
+        record.setAmount(amount);
+        record.setBalance(currentBalance.add(amount));
+        record.setType(0);
+        record.setPayMethod(payMethod);
+        record.setTradeTime(new Date());
+        record.setCreateTime(DateUtils.getNowDate());
+        record.setRemark(remark);
+        storePreDepositMapper.insertStorePreDeposit(record);
+        return record;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public StorePreDeposit consume(Long customerId, String customerName, BigDecimal amount, String remark) {
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new CustomException("消费金额必须大于0");
+        }
+        BigDecimal currentBalance = getBalance(customerId);
+        if (currentBalance.compareTo(amount) < 0) {
+            throw new CustomException("预存款余额不足");
+        }
+        StorePreDeposit record = new StorePreDeposit();
+        record.setCustomerId(customerId);
+        record.setCustomerName(customerName);
+        record.setAmount(amount);
+        record.setBalance(currentBalance.subtract(amount));
+        record.setType(1);
+        record.setTradeTime(new Date());
+        record.setCreateTime(DateUtils.getNowDate());
+        record.setRemark(remark);
+        storePreDepositMapper.insertStorePreDeposit(record);
+        return record;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public StorePreDeposit refund(Long customerId, String customerName, BigDecimal amount, String remark) {
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new CustomException("退款金额必须大于0");
+        }
+        BigDecimal currentBalance = getBalance(customerId);
+        StorePreDeposit record = new StorePreDeposit();
+        record.setCustomerId(customerId);
+        record.setCustomerName(customerName);
+        record.setAmount(amount);
+        record.setBalance(currentBalance.add(amount));
+        record.setType(2);
+        record.setTradeTime(new Date());
+        record.setCreateTime(DateUtils.getNowDate());
+        record.setRemark(remark);
+        storePreDepositMapper.insertStorePreDeposit(record);
+        return record;
+    }
+
+    private StorePreDeposit getLastRecord(Long customerId) {
+        StorePreDeposit query = new StorePreDeposit();
+        query.setCustomerId(customerId);
+        List<StorePreDeposit> list = storePreDepositMapper.selectStorePreDepositList(query);
+        if (list != null && !list.isEmpty()) {
+            return list.get(0);
+        }
+        return null;
+    }
+}

+ 67 - 0
java/fs-service/src/main/java/com/fs/his/vo/InventoryTransferItemVO.java

@@ -0,0 +1,67 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 库存调拨明细 VO
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferItemVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键")
+    private Long id;
+
+    @ApiModelProperty("关联调拨单号")
+    private String transferNo;
+
+    @ApiModelProperty("商品ID")
+    private Long productId;
+
+    @ApiModelProperty("商品名称")
+    private String productName;
+
+    @ApiModelProperty("商品图片")
+    private String productImage;
+
+    @ApiModelProperty("商品规格")
+    private String productSpec;
+
+    @ApiModelProperty("单位")
+    private String unit;
+
+    @ApiModelProperty("调拨数量")
+    private Integer quantity;
+
+    @ApiModelProperty("成本单价")
+    private BigDecimal costPrice;
+
+    @ApiModelProperty("小计金额")
+    private BigDecimal totalAmount;
+
+    @ApiModelProperty("调出前库存")
+    private Integer fromStockBefore;
+
+    @ApiModelProperty("调出后库存")
+    private Integer fromStockAfter;
+
+    @ApiModelProperty("调入前库存")
+    private Integer toStockBefore;
+
+    @ApiModelProperty("调入后库存")
+    private Integer toStockAfter;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 45 - 0
java/fs-service/src/main/java/com/fs/his/vo/InventoryTransferLogVO.java

@@ -0,0 +1,45 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 库存调拨操作日志 VO
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferLogVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键")
+    private Long id;
+
+    @ApiModelProperty("关联调拨单号")
+    private String transferNo;
+
+    @ApiModelProperty("操作类型")
+    private String operation;
+
+    @ApiModelProperty("操作类型名称")
+    private String operationName;
+
+    @ApiModelProperty("操作人ID")
+    private Long operatorId;
+
+    @ApiModelProperty("操作人姓名")
+    private String operatorName;
+
+    @ApiModelProperty("操作内容")
+    private String content;
+
+    @ApiModelProperty("操作时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 118 - 0
java/fs-service/src/main/java/com/fs/his/vo/InventoryTransferVO.java

@@ -0,0 +1,118 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 库存调拨单 VO
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class InventoryTransferVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键")
+    private Long id;
+
+    @ApiModelProperty("调拨单号")
+    private String transferNo;
+
+    @ApiModelProperty("调出门店ID")
+    private Long fromStoreId;
+
+    @ApiModelProperty("调出门店名称")
+    private String fromStoreName;
+
+    @ApiModelProperty("调入门店ID")
+    private Long toStoreId;
+
+    @ApiModelProperty("调入门店名称")
+    private String toStoreName;
+
+    @ApiModelProperty("调拨总数量")
+    private Integer totalQuantity;
+
+    @ApiModelProperty("调拨总金额")
+    private BigDecimal totalAmount;
+
+    @ApiModelProperty("状态")
+    private String status;
+
+    @ApiModelProperty("状态名称")
+    private String statusName;
+
+    @ApiModelProperty("申请人ID")
+    private Long applyUserId;
+
+    @ApiModelProperty("申请人姓名")
+    private String applyUserName;
+
+    @ApiModelProperty("申请时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date applyTime;
+
+    @ApiModelProperty("审批人ID")
+    private Long approveUserId;
+
+    @ApiModelProperty("审批人姓名")
+    private String approveUserName;
+
+    @ApiModelProperty("审批时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date approveTime;
+
+    @ApiModelProperty("审批备注")
+    private String approveRemark;
+
+    @ApiModelProperty("发货人ID")
+    private Long shipUserId;
+
+    @ApiModelProperty("发货人姓名")
+    private String shipUserName;
+
+    @ApiModelProperty("发货时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date shipTime;
+
+    @ApiModelProperty("收货人ID")
+    private Long receiveUserId;
+
+    @ApiModelProperty("收货人姓名")
+    private String receiveUserName;
+
+    @ApiModelProperty("收货时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date receiveTime;
+
+    @ApiModelProperty("收货备注")
+    private String receiveRemark;
+
+    @ApiModelProperty("创建人")
+    private String createBy;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @ApiModelProperty("更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty("调拨明细列表")
+    private List<InventoryTransferItemVO> items;
+
+    @ApiModelProperty("操作日志列表")
+    private List<InventoryTransferLogVO> logs;
+}

+ 56 - 0
java/fs-service/src/main/java/com/fs/supplychain/domain/SupplyChainInventory.java

@@ -0,0 +1,56 @@
+package com.fs.supplychain.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 库存对象 sc_inventory
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class SupplyChainInventory {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    private Long id;
+
+    /** 租户ID */
+    private Long companyId;
+
+    /** 商品名称 */
+    private String goodsName;
+
+    /** 规格 */
+    private String spec;
+
+    /** 仓库名称 */
+    private String warehouse;
+
+    /** 库存数量 */
+    private Integer quantity;
+
+    /** 单位 */
+    private String unit;
+
+    /** 预警阈值 */
+    private Integer alertThreshold;
+
+    /** 创建人 */
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新人 */
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 62 - 0
java/fs-service/src/main/java/com/fs/supplychain/domain/SupplyChainTransferRecord.java

@@ -0,0 +1,62 @@
+package com.fs.supplychain.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 调拨记录对象 sc_transfer_record
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Data
+public class SupplyChainTransferRecord {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    private Long id;
+
+    /** 租户ID */
+    private Long companyId;
+
+    /** 商品ID */
+    private Long goodsId;
+
+    /** 商品名称 */
+    private String goodsName;
+
+    /** 调出仓库 */
+    private String fromWarehouse;
+
+    /** 调入仓库 */
+    private String toWarehouse;
+
+    /** 调拨数量 */
+    private Integer quantity;
+
+    /** 状态: processing-调拨中 done-已完成 */
+    private String status;
+
+    /** 操作人 */
+    private String operator;
+
+    /** 备注 */
+    private String remark;
+
+    /** 创建人 */
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新人 */
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 44 - 0
java/fs-service/src/main/java/com/fs/supplychain/mapper/SupplyChainInventoryMapper.java

@@ -0,0 +1,44 @@
+package com.fs.supplychain.mapper;
+
+import com.fs.supplychain.domain.SupplyChainInventory;
+
+import java.util.List;
+
+/**
+ * 库存 Mapper
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface SupplyChainInventoryMapper {
+
+    /**
+     * 查询库存列表
+     */
+    List<SupplyChainInventory> selectInventoryList(SupplyChainInventory inventory);
+
+    /**
+     * 根据ID查询库存
+     */
+    SupplyChainInventory selectInventoryById(Long id);
+
+    /**
+     * 新增库存
+     */
+    int insertInventory(SupplyChainInventory inventory);
+
+    /**
+     * 修改库存
+     */
+    int updateInventory(SupplyChainInventory inventory);
+
+    /**
+     * 更新库存数量
+     */
+    int updateInventoryQuantity(Long id, Integer quantity);
+
+    /**
+     * 删除库存
+     */
+    int deleteInventoryById(Long id);
+}

+ 29 - 0
java/fs-service/src/main/java/com/fs/supplychain/mapper/SupplyChainTransferRecordMapper.java

@@ -0,0 +1,29 @@
+package com.fs.supplychain.mapper;
+
+import com.fs.supplychain.domain.SupplyChainTransferRecord;
+
+import java.util.List;
+
+/**
+ * 调拨记录 Mapper
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface SupplyChainTransferRecordMapper {
+
+    /**
+     * 查询调拨记录列表
+     */
+    List<SupplyChainTransferRecord> selectTransferRecordList(SupplyChainTransferRecord record);
+
+    /**
+     * 新增调拨记录
+     */
+    int insertTransferRecord(SupplyChainTransferRecord record);
+
+    /**
+     * 更新调拨状态
+     */
+    int updateTransferStatus(Long id, String status);
+}

+ 55 - 0
java/fs-service/src/main/java/com/fs/supplychain/service/ISupplyChainInventoryService.java

@@ -0,0 +1,55 @@
+package com.fs.supplychain.service;
+
+import com.fs.supplychain.domain.SupplyChainInventory;
+import com.fs.supplychain.domain.SupplyChainTransferRecord;
+
+import java.util.List;
+
+/**
+ * 库存管理 Service 接口
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+public interface ISupplyChainInventoryService {
+
+    /**
+     * 查询库存列表
+     */
+    List<SupplyChainInventory> selectInventoryList(SupplyChainInventory inventory);
+
+    /**
+     * 根据ID查询库存
+     */
+    SupplyChainInventory selectInventoryById(Long id);
+
+    /**
+     * 新增库存
+     */
+    int insertInventory(SupplyChainInventory inventory);
+
+    /**
+     * 修改库存
+     */
+    int updateInventory(SupplyChainInventory inventory);
+
+    /**
+     * 删除库存
+     */
+    int deleteInventoryById(Long id);
+
+    /**
+     * 库存调拨
+     */
+    int transferInventory(SupplyChainTransferRecord record);
+
+    /**
+     * 查询调拨记录列表
+     */
+    List<SupplyChainTransferRecord> selectTransferRecordList(SupplyChainTransferRecord record);
+
+    /**
+     * 库存盘点
+     */
+    int stockTake(Long goodsId, Integer actualQuantity, String remark);
+}

+ 128 - 0
java/fs-service/src/main/java/com/fs/supplychain/service/impl/SupplyChainInventoryServiceImpl.java

@@ -0,0 +1,128 @@
+package com.fs.supplychain.service.impl;
+
+import com.fs.common.exception.CustomException;
+import com.fs.supplychain.domain.SupplyChainInventory;
+import com.fs.supplychain.domain.SupplyChainTransferRecord;
+import com.fs.supplychain.mapper.SupplyChainInventoryMapper;
+import com.fs.supplychain.mapper.SupplyChainTransferRecordMapper;
+import com.fs.supplychain.service.ISupplyChainInventoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 库存管理 Service 实现
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Service
+public class SupplyChainInventoryServiceImpl implements ISupplyChainInventoryService {
+
+    @Autowired
+    private SupplyChainInventoryMapper inventoryMapper;
+
+    @Autowired
+    private SupplyChainTransferRecordMapper transferRecordMapper;
+
+    @Override
+    public List<SupplyChainInventory> selectInventoryList(SupplyChainInventory inventory) {
+        return inventoryMapper.selectInventoryList(inventory);
+    }
+
+    @Override
+    public SupplyChainInventory selectInventoryById(Long id) {
+        return inventoryMapper.selectInventoryById(id);
+    }
+
+    @Override
+    public int insertInventory(SupplyChainInventory inventory) {
+        return inventoryMapper.insertInventory(inventory);
+    }
+
+    @Override
+    public int updateInventory(SupplyChainInventory inventory) {
+        return inventoryMapper.updateInventory(inventory);
+    }
+
+    @Override
+    public int deleteInventoryById(Long id) {
+        return inventoryMapper.deleteInventoryById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int transferInventory(SupplyChainTransferRecord record) {
+        // 查询调出仓库的库存
+        SupplyChainInventory fromInventory = findInventory(record.getGoodsName(), record.getFromWarehouse());
+        if (fromInventory == null) {
+            throw new CustomException("调出仓库没有该商品: " + record.getGoodsName());
+        }
+        if (fromInventory.getQuantity() < record.getQuantity()) {
+            throw new CustomException("调出仓库库存不足,当前库存: " + fromInventory.getQuantity());
+        }
+
+        // 扣减调出仓库库存
+        int newFromQty = fromInventory.getQuantity() - record.getQuantity();
+        inventoryMapper.updateInventoryQuantity(fromInventory.getId(), newFromQty);
+
+        // 检查调入仓库是否有该商品
+        SupplyChainInventory toInventory = findInventory(record.getGoodsName(), record.getToWarehouse());
+        if (toInventory != null) {
+            // 增加调入仓库库存
+            int newToQty = toInventory.getQuantity() + record.getQuantity();
+            inventoryMapper.updateInventoryQuantity(toInventory.getId(), newToQty);
+        } else {
+            // 在调入仓库新增商品记录
+            SupplyChainInventory newInventory = new SupplyChainInventory();
+            newInventory.setCompanyId(record.getCompanyId());
+            newInventory.setGoodsName(record.getGoodsName());
+            newInventory.setSpec(fromInventory.getSpec());
+            newInventory.setWarehouse(record.getToWarehouse());
+            newInventory.setQuantity(record.getQuantity());
+            newInventory.setUnit(fromInventory.getUnit());
+            newInventory.setAlertThreshold(fromInventory.getAlertThreshold());
+            newInventory.setCreateBy(record.getOperator());
+            newInventory.setCreateTime(new Date());
+            inventoryMapper.insertInventory(newInventory);
+        }
+
+        // 记录调拨日志
+        record.setStatus("done");
+        record.setCreateTime(new Date());
+        return transferRecordMapper.insertTransferRecord(record);
+    }
+
+    @Override
+    public List<SupplyChainTransferRecord> selectTransferRecordList(SupplyChainTransferRecord record) {
+        return transferRecordMapper.selectTransferRecordList(record);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int stockTake(Long goodsId, Integer actualQuantity, String remark) {
+        SupplyChainInventory inventory = inventoryMapper.selectInventoryById(goodsId);
+        if (inventory == null) {
+            throw new CustomException("库存记录不存在");
+        }
+        return inventoryMapper.updateInventoryQuantity(goodsId, actualQuantity);
+    }
+
+    /**
+     * 根据商品名称和仓库查找库存
+     */
+    private SupplyChainInventory findInventory(String goodsName, String warehouse) {
+        SupplyChainInventory query = new SupplyChainInventory();
+        query.setGoodsName(goodsName);
+        query.setWarehouse(warehouse);
+        List<SupplyChainInventory> list = inventoryMapper.selectInventoryList(query);
+        // 精确匹配
+        return list.stream()
+                .filter(i -> goodsName.equals(i.getGoodsName()) && warehouse.equals(i.getWarehouse()))
+                .findFirst()
+                .orElse(null);
+    }
+}

+ 73 - 0
java/fs-service/src/main/resources/mapper/supplychain/SupplyChainInventoryMapper.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.supplychain.mapper.SupplyChainInventoryMapper">
+
+    <resultMap type="com.fs.supplychain.domain.SupplyChainInventory" id="InventoryResult">
+        <result property="id" column="id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="goodsName" column="goods_name"/>
+        <result property="spec" column="spec"/>
+        <result property="warehouse" column="warehouse"/>
+        <result property="quantity" column="quantity"/>
+        <result property="unit" column="unit"/>
+        <result property="alertThreshold" column="alert_threshold"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectInventoryList" parameterType="com.fs.supplychain.domain.SupplyChainInventory" resultMap="InventoryResult">
+        SELECT id, company_id, goods_name, spec, warehouse, quantity, unit,
+               alert_threshold, create_by, create_time, update_by, update_time
+        FROM sc_inventory
+        <where>
+            <if test="companyId != null">AND company_id = #{companyId}</if>
+            <if test="goodsName != null and goodsName != ''">AND goods_name LIKE CONCAT('%', #{goodsName}, '%')</if>
+            <if test="warehouse != null and warehouse != ''">AND warehouse LIKE CONCAT('%', #{warehouse}, '%')</if>
+        </where>
+        ORDER BY update_time DESC, id DESC
+    </select>
+
+    <select id="selectInventoryById" parameterType="Long" resultMap="InventoryResult">
+        SELECT id, company_id, goods_name, spec, warehouse, quantity, unit,
+               alert_threshold, create_by, create_time, update_by, update_time
+        FROM sc_inventory
+        WHERE id = #{id}
+    </select>
+
+    <insert id="insertInventory" parameterType="com.fs.supplychain.domain.SupplyChainInventory" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO sc_inventory(
+            company_id, goods_name, spec, warehouse, quantity, unit,
+            alert_threshold, create_by, create_time
+        ) VALUES (
+            #{companyId}, #{goodsName}, #{spec}, #{warehouse}, #{quantity}, #{unit},
+            #{alertThreshold}, #{createBy}, sysdate()
+        )
+    </insert>
+
+    <update id="updateInventory" parameterType="com.fs.supplychain.domain.SupplyChainInventory">
+        UPDATE sc_inventory
+        <set>
+            <if test="goodsName != null">goods_name = #{goodsName},</if>
+            <if test="spec != null">spec = #{spec},</if>
+            <if test="warehouse != null">warehouse = #{warehouse},</if>
+            <if test="quantity != null">quantity = #{quantity},</if>
+            <if test="unit != null">unit = #{unit},</if>
+            <if test="alertThreshold != null">alert_threshold = #{alertThreshold},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = sysdate()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <update id="updateInventoryQuantity">
+        UPDATE sc_inventory SET quantity = #{quantity}, update_time = sysdate()
+        WHERE id = #{id}
+    </update>
+
+    <delete id="deleteInventoryById" parameterType="Long">
+        DELETE FROM sc_inventory WHERE id = #{id}
+    </delete>
+
+</mapper>

+ 49 - 0
java/fs-service/src/main/resources/mapper/supplychain/SupplyChainTransferRecordMapper.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.supplychain.mapper.SupplyChainTransferRecordMapper">
+
+    <resultMap type="com.fs.supplychain.domain.SupplyChainTransferRecord" id="TransferRecordResult">
+        <result property="id" column="id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="goodsId" column="goods_id"/>
+        <result property="goodsName" column="goods_name"/>
+        <result property="fromWarehouse" column="from_warehouse"/>
+        <result property="toWarehouse" column="to_warehouse"/>
+        <result property="quantity" column="quantity"/>
+        <result property="status" column="status"/>
+        <result property="operator" column="operator"/>
+        <result property="remark" column="remark"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectTransferRecordList" parameterType="com.fs.supplychain.domain.SupplyChainTransferRecord" resultMap="TransferRecordResult">
+        SELECT id, company_id, goods_id, goods_name, from_warehouse, to_warehouse,
+               quantity, status, operator, remark, create_by, create_time, update_by, update_time
+        FROM sc_transfer_record
+        <where>
+            <if test="companyId != null">AND company_id = #{companyId}</if>
+            <if test="goodsName != null and goodsName != ''">AND goods_name LIKE CONCAT('%', #{goodsName}, '%')</if>
+            <if test="status != null and status != ''">AND status = #{status}</if>
+        </where>
+        ORDER BY create_time DESC, id DESC
+    </select>
+
+    <insert id="insertTransferRecord" parameterType="com.fs.supplychain.domain.SupplyChainTransferRecord" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO sc_transfer_record(
+            company_id, goods_id, goods_name, from_warehouse, to_warehouse,
+            quantity, status, operator, remark, create_by, create_time
+        ) VALUES (
+            #{companyId}, #{goodsId}, #{goodsName}, #{fromWarehouse}, #{toWarehouse},
+            #{quantity}, #{status}, #{operator}, #{remark}, #{createBy}, sysdate()
+        )
+    </insert>
+
+    <update id="updateTransferStatus">
+        UPDATE sc_transfer_record SET status = #{status}, update_time = sysdate()
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 100 - 0
java/fs-user-app/src/main/java/com/fs/app/controller/ImChatController.java

@@ -0,0 +1,100 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.im.service.OpenIMService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * C端 IM 在线咨询入口 Controller
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Api("IM在线咨询")
+@RestController
+@RequestMapping("/app/im")
+public class ImChatController extends AppBaseController {
+
+    @Autowired
+    private OpenIMService openIMService;
+
+    /**
+     * 获取当前用户的IM账号信息(用于初始化IM SDK)
+     */
+    @Login
+    @ApiOperation("获取IM用户信息")
+    @GetMapping("/userInfo")
+    public R getUserInfo() {
+        String userId = getUserId();
+        if (userId == null) {
+            return R.error("未登录");
+        }
+        return R.ok().put("userId", "fs_" + userId).put("token", openIMService.getAdminToken());
+    }
+
+    /**
+     * 获取IM管理Token(用于客户端SDK初始化)
+     */
+    @Login
+    @ApiOperation("获取IM Token")
+    @GetMapping("/token")
+    public R getToken() {
+        String token = openIMService.getAdminToken();
+        return R.ok().put("token", token);
+    }
+
+    /**
+     * 检查用户IM账号状态
+     */
+    @Login
+    @ApiOperation("检查IM账号状态")
+    @GetMapping("/accountCheck")
+    public R accountCheck(@RequestParam String type) {
+        String userId = getUserId();
+        return openIMService.accountCheck(userId, type);
+    }
+
+    /**
+     * 检查两个用户是否为好友关系
+     */
+    @Login
+    @ApiOperation("检查好友关系")
+    @GetMapping("/isFriend")
+    public R isFriend(@RequestParam String targetUserId) {
+        String userId = "fs_" + getUserId();
+        return R.ok().put("isFriend",
+                openIMService.isFriend(userId, targetUserId));
+    }
+
+    /**
+     * 添加好友(用户添加医生/药师为好友)
+     */
+    @Login
+    @ApiOperation("添加好友")
+    @PostMapping("/addFriend")
+    public R addFriend(@RequestBody Map<String, Object> params) {
+        String targetUserId = (String) params.get("targetUserId");
+        String userId = "fs_" + getUserId();
+        openIMService.importFriend(userId, java.util.Collections.singletonList(targetUserId));
+        return R.ok();
+    }
+
+    /**
+     * 获取IM初始化配置(SDK连接参数)
+     */
+    @Login
+    @ApiOperation("获取IM初始化配置")
+    @GetMapping("/config")
+    public R getConfig() {
+        String userId = getUserId();
+        return R.ok()
+                .put("userId", "fs_" + userId)
+                .put("adminToken", openIMService.getAdminToken());
+    }
+}

+ 107 - 0
java/fs-user-app/src/main/java/com/fs/app/controller/PreDepositController.java

@@ -0,0 +1,107 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.StorePreDeposit;
+import com.fs.his.service.IStorePreDepositService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * C端预存款 Controller
+ *
+ * @author fs
+ * @date 2026-07-25
+ */
+@Api("预存款")
+@RestController
+@RequestMapping("/app/preDeposit")
+public class PreDepositController extends AppBaseController {
+
+    @Autowired
+    private IStorePreDepositService storePreDepositService;
+
+    /**
+     * 查询预存款余额
+     */
+    @Login
+    @ApiOperation("查询预存款余额")
+    @GetMapping("/balance")
+    public R balance() {
+        Long customerId = Long.parseLong(getUserId());
+        BigDecimal balance = storePreDepositService.getBalance(customerId);
+        return R.ok().put("balance", balance);
+    }
+
+    /**
+     * 预存款记录列表(充值/消费/退款记录)
+     */
+    @Login
+    @ApiOperation("预存款记录列表")
+    @GetMapping("/records")
+    public R records(@RequestParam(required = false) Integer type,
+                     @RequestParam(defaultValue = "1") Integer pageNum,
+                     @RequestParam(defaultValue = "10") Integer pageSize) {
+        Long customerId = Long.parseLong(getUserId());
+        PageHelper.startPage(pageNum, pageSize);
+        List<StorePreDeposit> list = storePreDepositService.selectPreDepositList(customerId, type);
+        PageInfo<StorePreDeposit> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
+    /**
+     * 预存款充值
+     */
+    @Login
+    @ApiOperation("预存款充值")
+    @PostMapping("/recharge")
+    public R recharge(@RequestBody Map<String, Object> params) {
+        Long customerId = Long.parseLong(getUserId());
+        String customerName = (String) params.getOrDefault("customerName", "");
+        BigDecimal amount = new BigDecimal(params.get("amount").toString());
+        String payMethod = (String) params.getOrDefault("payMethod", "WXPAY");
+
+        StorePreDeposit result = storePreDepositService.recharge(
+                customerId, customerName, amount, payMethod, null);
+        return R.ok().put("balance", result.getBalance());
+    }
+
+    /**
+     * 消费记录查询(仅查询消费类型记录)
+     */
+    @Login
+    @ApiOperation("消费记录")
+    @GetMapping("/consumes")
+    public R consumes(@RequestParam(defaultValue = "1") Integer pageNum,
+                      @RequestParam(defaultValue = "10") Integer pageSize) {
+        Long customerId = Long.parseLong(getUserId());
+        PageHelper.startPage(pageNum, pageSize);
+        List<StorePreDeposit> list = storePreDepositService.selectPreDepositList(customerId, 1);
+        PageInfo<StorePreDeposit> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
+    /**
+     * 充值记录查询
+     */
+    @Login
+    @ApiOperation("充值记录")
+    @GetMapping("/recharges")
+    public R recharges(@RequestParam(defaultValue = "1") Integer pageNum,
+                       @RequestParam(defaultValue = "10") Integer pageSize) {
+        Long customerId = Long.parseLong(getUserId());
+        PageHelper.startPage(pageNum, pageSize);
+        List<StorePreDeposit> list = storePreDepositService.selectPreDepositList(customerId, 0);
+        PageInfo<StorePreDeposit> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+}

+ 1 - 1
saasmgnui/src/views/store/purchaseSuggestion/index.vue

@@ -62,7 +62,7 @@
             <span :style="{ color: row.status === 'out' ? '#F56C6C' : '#E6A23C', fontWeight: 600 }">{{ row.currentStock || 0 }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="近{{ daysRange }}天销量" prop="recentSales" width="110" align="center" sortable />
+        <el-table-column :label="'近' + daysRange + '天销量'" prop="recentSales" width="110" align="center" sortable />
         <el-table-column label="订单数" prop="orderCount" width="80" align="center" />
         <el-table-column label="建议采购量" prop="suggestedQty" width="100" align="center">
           <template slot-scope="{row}">