feat(oa): 添加库存管理相关实体和接口

- 新增 GearStock、GearStockBo、GearStockIo、GearStockIoBo、GearStockIoDetail、GearStockIoDetailBo 等实体类
- 新增 GearStockController、GearStockIoController、GearStockIoDetailController 等控制器
- 新增 GearStockIoDetailMapper 及其 XML 文件
- 优化 GearAttendanceSummaryServiceImpl 中的代码格式
This commit is contained in:
2025-09-17 15:20:53 +08:00
parent 7c02456d56
commit 310c359a15
44 changed files with 3635 additions and 19 deletions

View File

@@ -0,0 +1,112 @@
package com.gear.oa.controller;
import com.gear.common.annotation.Log;
import com.gear.common.annotation.RepeatSubmit;
import com.gear.common.core.controller.BaseController;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.domain.R;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.common.utils.poi.ExcelUtil;
import com.gear.oa.domain.bo.GearStockBo;
import com.gear.oa.domain.vo.GearStockVo;
import com.gear.oa.service.IGearStockService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 库存:原材料/产品与库区/库位的存放关系
*
* @author Joshi
* @date 2025-07-18
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/gear/stock")
public class GearStockController extends BaseController {
private final IGearStockService iGearStockService;
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
@GetMapping("/list")
public TableDataInfo<GearStockVo> list(GearStockBo bo, PageQuery pageQuery) {
return iGearStockService.queryPageList(bo, pageQuery);
}
/**
* 导出库存:原材料/产品与库区/库位的存放关系列表
*/
@Log(title = "库存:原材料/产品与库区/库位的存放关系", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(GearStockBo bo, HttpServletResponse response) {
List<GearStockVo> list = iGearStockService.queryList(bo);
ExcelUtil.exportExcel(list, "库存:原材料-产品与库区-库位的存放关系", GearStockVo.class, response);
}
/**
* 获取库存:原材料/产品与库区/库位的存放关系详细信息
*
* @param stockId 主键
*/
@GetMapping("/{stockId}")
public R<GearStockVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long stockId) {
return R.ok(iGearStockService.queryById(stockId));
}
/**
* 新增库存:原材料/产品与库区/库位的存放关系
*/
@Log(title = "库存:原材料/产品与库区/库位的存放关系", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody GearStockBo bo) {
return toAjax(iGearStockService.insertByBo(bo));
}
/**
* 修改库存:原材料/产品与库区/库位的存放关系
*/
@Log(title = "库存:原材料/产品与库区/库位的存放关系", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody GearStockBo bo) {
return toAjax(iGearStockService.updateByBo(bo));
}
/**
* 删除库存:原材料/产品与库区/库位的存放关系
*
* @param stockIds 主键串
*/
@Log(title = "库存:原材料/产品与库区/库位的存放关系", businessType = BusinessType.DELETE)
@DeleteMapping("/{stockIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] stockIds) {
return toAjax(iGearStockService.deleteWithValidByIds(Arrays.asList(stockIds), true));
}
/**
* 测试递归查询功能
*/
@GetMapping("/test/warehouse/{warehouseId}")
public R<String> testWarehouseQuery(@PathVariable Long warehouseId) {
GearStockBo bo = new GearStockBo();
bo.setWarehouseId(warehouseId);
List<GearStockVo> list = iGearStockService.queryList(bo);
return R.ok("查询到 " + list.size() + " 条库存记录仓库ID: " + warehouseId);
}
}

View File

@@ -0,0 +1,182 @@
package com.gear.oa.controller;
import com.gear.common.annotation.Log;
import com.gear.common.annotation.RepeatSubmit;
import com.gear.common.core.controller.BaseController;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.domain.R;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.common.utils.poi.ExcelUtil;
import com.gear.oa.domain.bo.GearStockIoBo;
import com.gear.oa.domain.bo.GearStockIoDetailBo;
import com.gear.oa.domain.bo.GearStockIoWithDetailBo;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import com.gear.oa.domain.vo.GearStockIoVo;
import com.gear.oa.service.IGearStockIoService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 出入库单主表
*
* @author Joshi
* @date 2025-07-18
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/gear/stockIo")
public class GearStockIoController extends BaseController {
private final IGearStockIoService iGearStockIoService;
/**
* 查询出入库单主列表
*/
@GetMapping("/list")
public TableDataInfo<GearStockIoVo> list(GearStockIoBo bo, PageQuery pageQuery) {
return iGearStockIoService.queryPageList(bo, pageQuery);
}
/**
* 导出出入库单主列表
*/
@Log(title = "出入库单主", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(GearStockIoBo bo, HttpServletResponse response) {
List<GearStockIoVo> list = iGearStockIoService.queryList(bo);
ExcelUtil.exportExcel(list, "出入库单主", GearStockIoVo.class, response);
}
/**
* 获取出入库单主详细信息
*
* @param stockIoId 主键
*/
@GetMapping("/{stockIoId}")
public R<GearStockIoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long stockIoId) {
return R.ok(iGearStockIoService.queryById(stockIoId));
}
/**
* 新增出入库单主
*/
@Log(title = "出入库单主", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody GearStockIoBo bo) {
return toAjax(iGearStockIoService.insertByBo(bo));
}
/**
* 批量新增主表和明细
*/
@PostMapping("/withDetail")
public R<Void> addWithDetail(@RequestBody GearStockIoWithDetailBo bo) {
iGearStockIoService.addWithDetail(bo);
return R.ok();
}
/**
* 修改出入库单主
*/
@Log(title = "出入库单主", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody GearStockIoBo bo) {
return toAjax(iGearStockIoService.updateByBo(bo));
}
/**
* 删除出入库单主
*
* @param stockIoIds 主键串
*/
@Log(title = "出入库单主", businessType = BusinessType.DELETE)
@DeleteMapping("/{stockIoIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] stockIoIds) {
return toAjax(iGearStockIoService.deleteWithValidByIds(Arrays.asList(stockIoIds), true));
}
/**
* 审核出入库/移库单
*/
@Log(title = "出入库单主", businessType = BusinessType.UPDATE)
@PostMapping("/audit/{stockIoId}")
public R<Void> audit(@NotNull(message = "主键不能为空") @PathVariable Long stockIoId) {
return toAjax(iGearStockIoService.auditStockIo(stockIoId));
}
/**
* 撤销出入库/移库单
*/
@Log(title = "出入库单主", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PostMapping("/cancel/{stockIoId}")
public R<Void> cancel(@NotNull(message = "主键不能为空") @PathVariable Long stockIoId) {
return toAjax(iGearStockIoService.cancelStockIo(stockIoId));
}
/**
* 根据ioType和stockIoId联查明细
*/
@GetMapping("/detailByTypeAndId")
public R<List<GearStockIoDetailVo>> detailByTypeAndId(@RequestParam String ioType, @RequestParam Long stockIoId) {
return R.ok(iGearStockIoService.detailByTypeAndId(ioType, stockIoId));
}
/**
* 更新出入库单状态
*/
@Log(title = "出入库单主", businessType = BusinessType.UPDATE)
@PostMapping("/updateStatus/{stockIoId}")
public R<Void> updateStatus(@NotNull(message = "主键不能为空") @PathVariable Long stockIoId,
@RequestParam Integer status) {
return toAjax(iGearStockIoService.updateStatus(stockIoId, status));
}
// /**
// * 扫码枪专用根据明细ID直接入库无需审核整单
// */
// @PostMapping("/scanInStock")
// public R<Void> scanInStock(@RequestBody GearStockIoDetailBo bo) {
// try {
// boolean result = iGearStockIoService.scanInStockByBo(bo);
// return toAjax(result);
// } catch (Exception e) {
// return R.fail(e.getMessage());
// }
// }
// @PostMapping("/scanOutStock")
// public R<Void> scanOutStock(@RequestBody GearStockIoDetailBo bo) {
// try {
// boolean result = iGearStockIoService.scanOutStockByBo(bo);
// return toAjax(result);
// } catch (Exception e) {
// return R.fail(e.getMessage());
// }
// }
// //退库操作
// @PostMapping("/returnStock")
// public R<Void> scanReturnStock(@RequestBody GearStockIoBo bo) {
// try {
// boolean result = iGearStockIoService.scanReturnStockByBo(bo);
// return toAjax(result);
// } catch (Exception e) {
// return R.fail(e.getMessage());
// }
// }
}

View File

@@ -0,0 +1,105 @@
package com.gear.oa.controller;
import com.gear.common.annotation.Log;
import com.gear.common.annotation.RepeatSubmit;
import com.gear.common.core.controller.BaseController;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.domain.R;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.common.utils.poi.ExcelUtil;
import com.gear.oa.domain.bo.GearStockIoDetailBo;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import com.gear.oa.service.IGearStockIoDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 出入库单明细
*
* @author Joshi
* @date 2025-07-18
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/gear/stockIoDetail")
public class GearStockIoDetailController extends BaseController {
private final IGearStockIoDetailService iGearStockIoDetailService;
/**
* 查询出入库单明细列表
*/
@GetMapping("/list")
public TableDataInfo<GearStockIoDetailVo> list(GearStockIoDetailBo bo, PageQuery pageQuery) {
return iGearStockIoDetailService.queryPageList(bo, pageQuery);
}
/**
* 导出出入库单明细列表
*/
@Log(title = "出入库单明细", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(GearStockIoDetailBo bo, HttpServletResponse response) {
List<GearStockIoDetailVo> list = iGearStockIoDetailService.queryList(bo);
ExcelUtil.exportExcel(list, "出入库单明细", GearStockIoDetailVo.class, response);
}
/**
* 获取出入库单明细详细信息
*
* @param detailId 主键
*/
@GetMapping("/{detailId}")
public R<GearStockIoDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long detailId) {
return R.ok(iGearStockIoDetailService.queryById(detailId));
}
/**
* 新增出入库单明细
*/
@Log(title = "出入库单明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody GearStockIoDetailBo bo) {
return toAjax(iGearStockIoDetailService.insertByBo(bo));
}
/**
* 修改出入库单明细
*/
@Log(title = "出入库单明细", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody GearStockIoDetailBo bo) {
return toAjax(iGearStockIoDetailService.updateByBo(bo));
}
/**
* 删除出入库单明细
*
* @param detailIds 主键串
*/
@Log(title = "出入库单明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{detailIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] detailIds) {
return toAjax(iGearStockIoDetailService.deleteWithValidByIds(Arrays.asList(detailIds), true));
}
//根据批次号联查出入库单主表以及明细表信息
@PostMapping("/batch")
public R<List<GearStockIoDetailVo>> batchQuery(@RequestBody GearStockIoDetailBo bo) {
return R.ok(iGearStockIoDetailService.batchQuery(bo));
}
}

View File

@@ -0,0 +1,100 @@
package com.gear.oa.controller;
import com.gear.common.annotation.Log;
import com.gear.common.annotation.RepeatSubmit;
import com.gear.common.core.controller.BaseController;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.domain.R;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.common.utils.poi.ExcelUtil;
import com.gear.oa.domain.bo.GearStockLogBo;
import com.gear.oa.domain.vo.GearStockLogVo;
import com.gear.oa.service.IGearStockLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 库存流水
*
* @author JR
* @date 2025-08-11
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/gear/stockLog")
public class GearStockLogController extends BaseController {
private final IGearStockLogService iGearStockLogService;
/**
* 查询库存流水列表
*/
@GetMapping("/list")
public TableDataInfo<GearStockLogVo> list(GearStockLogBo bo, PageQuery pageQuery) {
return iGearStockLogService.queryPageList(bo, pageQuery);
}
/**
* 导出库存流水列表
*/
@Log(title = "库存流水", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(GearStockLogBo bo, HttpServletResponse response) {
List<GearStockLogVo> list = iGearStockLogService.queryList(bo);
ExcelUtil.exportExcel(list, "库存流水", GearStockLogVo.class, response);
}
/**
* 获取库存流水详细信息
*
* @param id 主键
*/
@GetMapping("/{id}")
public R<GearStockLogVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(iGearStockLogService.queryById(id));
}
/**
* 新增库存流水
*/
@Log(title = "库存流水", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody GearStockLogBo bo) {
return toAjax(iGearStockLogService.insertByBo(bo));
}
/**
* 修改库存流水
*/
@Log(title = "库存流水", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody GearStockLogBo bo) {
return toAjax(iGearStockLogService.updateByBo(bo));
}
/**
* 删除库存流水
*
* @param ids 主键串
*/
@Log(title = "库存流水", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(iGearStockLogService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}

View File

@@ -0,0 +1,99 @@
package com.gear.oa.controller;
import com.gear.common.annotation.Log;
import com.gear.common.annotation.RepeatSubmit;
import com.gear.common.core.controller.BaseController;
import com.gear.common.core.domain.R;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.common.utils.poi.ExcelUtil;
import com.gear.oa.domain.bo.GearWarehouseBo;
import com.gear.oa.domain.vo.GearWarehouseVo;
import com.gear.oa.service.IGearWarehouseService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 仓库/库区/库位自关联
*
* @author JR
* @date 2025-07-18
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/gear/warehouse")
public class GearWarehouseController extends BaseController {
private final IGearWarehouseService iGearWarehouseService;
/**
* 查询仓库/库区/库位自关联列表
*/
@GetMapping("/list")
public R<List<GearWarehouseVo>> list(GearWarehouseBo bo) {
List<GearWarehouseVo> list = iGearWarehouseService.queryList(bo);
return R.ok(list);
}
/**
* 导出仓库/库区/库位自关联列表
*/
@Log(title = "仓库/库区/库位自关联", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(GearWarehouseBo bo, HttpServletResponse response) {
List<GearWarehouseVo> list = iGearWarehouseService.queryList(bo);
ExcelUtil.exportExcel(list, "仓库/库区/库位自关联", GearWarehouseVo.class, response);
}
/**
* 获取仓库/库区/库位自关联详细信息
*
* @param warehouseId 主键
*/
@GetMapping("/{warehouseId}")
public R<GearWarehouseVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long warehouseId) {
return R.ok(iGearWarehouseService.queryById(warehouseId));
}
/**
* 新增仓库/库区/库位自关联
*/
@Log(title = "仓库/库区/库位自关联", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody GearWarehouseBo bo) {
return toAjax(iGearWarehouseService.insertByBo(bo));
}
/**
* 修改仓库/库区/库位自关联
*/
@Log(title = "仓库/库区/库位自关联", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody GearWarehouseBo bo) {
return toAjax(iGearWarehouseService.updateByBo(bo));
}
/**
* 删除仓库/库区/库位自关联
*
* @param warehouseIds 主键串
*/
@Log(title = "仓库/库区/库位自关联", businessType = BusinessType.DELETE)
@DeleteMapping("/{warehouseIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] warehouseIds) {
return toAjax(iGearWarehouseService.deleteWithValidByIds(Arrays.asList(warehouseIds), true));
}
}

View File

@@ -0,0 +1,64 @@
package com.gear.oa.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
* 库存:原材料/产品与库区/库位的存放关系对象 gear_stock
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gear_stock")
public class GearStock extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@TableId(value = "stock_id")
private Long stockId;
/**
* 仓库/库区/库位ID
*/
private Long warehouseId;
/**
* 物品类型raw_material/product
*/
private String itemType;
/**
* 物品ID指向原材料或产品主键
*/
private Long itemId;
/**
* 库存数量
*/
private BigDecimal quantity;
/**
* 单位
*/
private String unit;
/**
* 批次号(可选)
*/
private String batchNo;
/**
* 备注
*/
private String remark;
/**
* 删除标志0=正常1=已删除)
*/
@TableLogic
private Integer delFlag;
}

View File

@@ -0,0 +1,57 @@
package com.gear.oa.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 出入库单主对象 gear_stock_io
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gear_stock_io")
public class GearStockIo extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 出入库单ID
*/
@TableId(value = "stock_io_id")
private Long stockIoId;
/**
* 出入库单号
*/
private String stockIoCode;
/**
* 类型in=入库out=出库)
*/
private String ioType;
/**
* 业务类型(采购、销售、退货、调拨等)
*/
private String bizType;
/**
* 单据状态0=草稿1=已提交2=已审核3=已完成)
*/
private Integer status;
/**
* 备注
*/
private String remark;
//
private Long parentId;
/**
* 删除标志0=正常1=已删除)
*/
@TableLogic
private Integer delFlag;
}

View File

@@ -0,0 +1,76 @@
package com.gear.oa.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
* 出入库单明细对象 gear_stock_io_detail
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gear_stock_io_detail")
public class GearStockIoDetail extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 明细ID
*/
@TableId(value = "detail_id")
private Long detailId;
/**
* 出入库单ID
*/
private Long stockIoId;
/**
* 库区/库位ID
*/
private Long warehouseId;
/**
* 物品类型raw_material/product
*/
private String itemType;
/**
* 物品ID
*/
private Long itemId;
/**
* 数量
*/
private BigDecimal quantity;
/**
* 单位
*/
private String unit;
/**
* 批次号
*/
private String batchNo;
/**
* 备注
*/
private String remark;
/**
* 删除标志0=正常1=已删除)
*/
@TableLogic
private Integer delFlag;
/**
* 源库位ID移库时使用
*/
private Long fromWarehouseId;
/**
* 记录类型0详情1扫码枪记录
*/
private Integer recordType;
}

View File

@@ -0,0 +1,72 @@
package com.gear.oa.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* 库存流水对象 gear_stock_log
*
* @author JR
* @date 2025-08-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gear_stock_log")
public class GearStockLog extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 仓库/库区/库位ID
*/
private Long warehouseId;
/**
* 物品ID指向原材料或产品主键
*/
private Long itemId;
/**
* 物品类型raw_material/product
*/
private String itemType;
/**
* 变动数量(正=入库,负=出库)
*/
private BigDecimal changeQty;
/**
* 变动后的库存数量
*/
private BigDecimal afterQty;
/**
* 变动类型(入库/出库等)
*/
private String changeType;
/**
* 实际库存变动时间
*/
private Date changeTime;
/**
* 备注
*/
private String remark;
/**
* 删除标志0=正常,1=删除)
*/
@TableLogic
private Long delFlag;
//批次号
private String batchNo;
}

View File

@@ -0,0 +1,58 @@
package com.gear.oa.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gear.common.core.domain.TreeEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 仓库/库区/库位自关联对象 gear_warehouse
*
* @author JR
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gear_warehouse")
public class GearWarehouse extends TreeEntity<GearWarehouse> {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@TableId(value = "warehouse_id")
private Long warehouseId;
/**
* 仓库/库区编码
*/
private String warehouseCode;
/**
* 仓库/库区名称
*/
private String warehouseName;
/**
* 类型0=仓库1=库区2=库位,…
*/
private Long warehouseType;
/**
* 同级排序号
*/
private Long sortNo;
/**
* 是否启用0=否1=是)
*/
private Integer isEnabled;
/**
* 删除标志0=正常1=已删除)
*/
@TableLogic
private Integer delFlag;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,71 @@
package com.gear.oa.domain.bo;
import com.gear.common.core.domain.BaseEntity;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 库存:原材料/产品与库区/库位的存放关系业务对象 gear_stock
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GearStockBo extends BaseEntity {
/**
* 主键ID
*/
private Long stockId;
/**
* 仓库/库区/库位ID
*/
@NotNull(message = "仓库/库区/库位ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long warehouseId;
/**
* 物品类型raw_material/product
*/
@NotBlank(message = "物品类型raw_material/product不能为空", groups = { AddGroup.class, EditGroup.class })
private String itemType;
/**
* 物品ID指向原材料或产品主键
*/
@NotNull(message = "物品ID指向原材料或产品主键不能为空", groups = { AddGroup.class, EditGroup.class })
private Long itemId;
/**
* 库存数量
*/
@NotNull(message = "库存数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal quantity;
/**
* 单位
*/
@NotBlank(message = "单位不能为空", groups = { AddGroup.class, EditGroup.class })
private String unit;
/**
* 批次号(可选)
*/
@NotBlank(message = "批次号(可选)不能为空", groups = { AddGroup.class, EditGroup.class })
private String batchNo;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,63 @@
package com.gear.oa.domain.bo;
import com.gear.common.core.domain.BaseEntity;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 出入库单主业务对象 gear_stock_io
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GearStockIoBo extends BaseEntity {
/**
* 出入库单ID
*/
private Long stockIoId;
/**
* 出入库单号
*/
@NotBlank(message = "出入库单号不能为空", groups = { AddGroup.class, EditGroup.class })
private String stockIoCode;
/**
* 类型in=入库out=出库)
*/
@NotBlank(message = "类型in=入库out=出库)不能为空", groups = { AddGroup.class, EditGroup.class })
private String ioType;
/**
* 业务类型(采购、销售、退货、调拨等)
*/
@NotBlank(message = "业务类型(采购、销售、退货、调拨等)不能为空", groups = { AddGroup.class, EditGroup.class })
private String bizType;
/**
* 单据状态0=草稿1=已提交2=已审核3=已完成)
*/
@NotNull(message = "单据状态0=草稿1=已提交2=已审核3=已完成)不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 父级ID用于退库时关联原出库单
*/
private Long parentId;
}

View File

@@ -0,0 +1,86 @@
package com.gear.oa.domain.bo;
import com.gear.common.core.domain.BaseEntity;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 出入库单明细业务对象 gear_stock_io_detail
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GearStockIoDetailBo extends BaseEntity {
/**
* 明细ID
*/
private Long detailId;
/**
* 出入库单ID
*/
@NotNull(message = "出入库单ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long stockIoId;
/**
* 库区/库位ID
*/
@NotNull(message = "库区/库位ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long warehouseId;
/**
* 物品类型raw_material/product
*/
@NotBlank(message = "物品类型raw_material/product不能为空", groups = { AddGroup.class, EditGroup.class })
private String itemType;
/**
* 物品ID
*/
@NotNull(message = "物品ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long itemId;
/**
* 数量
*/
@NotNull(message = "数量不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal quantity;
/**
* 单位
*/
private String unit;
/**
* 批次号
*/
@NotBlank(message = "批次号不能为空", groups = { AddGroup.class, EditGroup.class })
private String batchNo;
/**
* 备注
*/
private String remark;
/**
* 源库位ID移库时使用
*/
private Long fromWarehouseId;
/**
* 记录类型0详情1扫码枪记录
*/
private Integer recordType;
}

View File

@@ -0,0 +1,56 @@
package com.gear.oa.domain.bo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 出入库单主+明细批量插入业务对象
*/
@Data
public class GearStockIoWithDetailBo {
// 主表字段
/**
* 出入库单ID
*/
private Long stockIoId;
/**
* 出入库单号
*/
@NotBlank(message = "出入库单号不能为空", groups = { AddGroup.class, EditGroup.class })
private String stockIoCode;
/**
* 类型in=入库out=出库)
*/
@NotBlank(message = "类型in=入库out=出库)不能为空", groups = { AddGroup.class, EditGroup.class })
private String ioType;
/**
* 业务类型(采购、销售、退货、调拨等)
*/
@NotBlank(message = "业务类型(采购、销售、退货、调拨等)不能为空", groups = { AddGroup.class, EditGroup.class })
private String bizType;
/**
* 单据状态0=草稿1=已提交2=已审核3=已完成)
*/
@NotNull(message = "单据状态0=草稿1=已提交2=已审核3=已完成)不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer status;
/**
* 备注
*/
private String remark;
// ...如有其他主表字段可补充
// 明细列表
private List<GearStockIoDetailBo> details;
private Long parentId;
}

View File

@@ -0,0 +1,85 @@
package com.gear.oa.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
* 库存流水业务对象 gear_stock_log
*
* @author JR
* @date 2025-08-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GearStockLogBo extends BaseEntity {
/**
* 主键ID
*/
private Long id;
/**
* 仓库/库区/库位ID
*/
private Long warehouseId;
/**
* 物品ID指向原材料或产品主键
*/
private Long itemId;
/**
* 物品类型raw_material/product
*/
private String itemType;
/**
* 变动数量(正=入库,负=出库)
*/
private BigDecimal changeQty;
/**
* 变动后的库存数量
*/
private BigDecimal afterQty;
/**
* 变动类型(入库/出库等)
*/
private String changeType;
/**
* 实际库存变动时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date changeTime;
/**
* 备注
*/
private String remark;
/**
* 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
//批次号
private String batchNo;
}

View File

@@ -0,0 +1,62 @@
package com.gear.oa.domain.bo;
import com.gear.common.core.domain.TreeEntity;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 仓库/库区/库位自关联业务对象 gear_warehouse
*
* @author JR
* @date 2025-07-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GearWarehouseBo extends TreeEntity<GearWarehouseBo> {
/**
* 主键ID
*/
private Long warehouseId;
/**
* 仓库/库区编码
*/
@NotBlank(message = "仓库/库区编码不能为空", groups = { AddGroup.class, EditGroup.class })
private String warehouseCode;
/**
* 仓库/库区名称
*/
@NotBlank(message = "仓库/库区名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String warehouseName;
/**
* 类型0=仓库1=库区2=库位,…
*/
@NotNull(message = "类型0=仓库1=库区2=库位,…不能为空", groups = { AddGroup.class, EditGroup.class })
private Long warehouseType;
/**
* 同级排序号
*/
private Long sortNo;
/**
* 是否启用0=否1=是)
*/
private Integer isEnabled;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,104 @@
package com.gear.oa.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.gear.common.annotation.ExcelDictFormat;
import com.gear.common.convert.ExcelDictConvert;
import lombok.Data;
import java.math.BigDecimal;
/**
* 出入库单明细视图对象 gear_stock_io_detail
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@ExcelIgnoreUnannotated
public class GearStockIoDetailVo {
private static final long serialVersionUID = 1L;
/**
* 明细ID
*/
@ExcelProperty(value = "明细ID")
private Long detailId;
/**
* 出入库单ID
*/
@ExcelProperty(value = "出入库单ID")
private Long stockIoId;
/**
* 库区/库位ID
*/
@ExcelProperty(value = "库区/库位ID")
private Long warehouseId;
/**
* 物品类型raw_material/product
*/
@ExcelProperty(value = "物品类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "r=aw_material/product")
private String itemType;
/**
* 物品ID
*/
@ExcelProperty(value = "物品ID")
private Long itemId;
/**
* 数量
*/
@ExcelProperty(value = "数量")
private BigDecimal quantity;
/**
* 单位
*/
@ExcelProperty(value = "单位")
private String unit;
/**
* 批次号
*/
@ExcelProperty(value = "批次号")
private String batchNo;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 记录类型0详情1扫码枪记录
*/
private Integer recordType;
/**
* 库区/库位名称
*/
private String warehouseName;
/**
* 源库区/库位名称(移库用)
*/
private String fromWarehouseName;
/**
* 源库位ID移库时使用
*/
private Long fromWarehouseId;
//主表的 数据号 类型以及业务类型
private String stockIoCode;
private String ioType;
private String bizType;
}

View File

@@ -0,0 +1,65 @@
package com.gear.oa.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.gear.common.annotation.ExcelDictFormat;
import com.gear.common.convert.ExcelDictConvert;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
/**
* 出入库单主视图对象 gear_stock_io
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@ExcelIgnoreUnannotated
public class GearStockIoVo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 出入库单ID
*/
@ExcelProperty(value = "出入库单ID")
private Long stockIoId;
/**
* 出入库单号
*/
@ExcelProperty(value = "出入库单号")
private String stockIoCode;
/**
* 类型in=入库out=出库)
*/
@ExcelProperty(value = "类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "i=n=入库out=出库")
private String ioType;
/**
* 业务类型(采购、销售、退货、调拨等)
*/
@ExcelProperty(value = "业务类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "采=购、销售、退货、调拨等")
private String bizType;
/**
* 单据状态0=草稿1=已提交2=已审核3=已完成)
*/
@ExcelProperty(value = "单据状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0==草稿1=已提交2=已审核3=已完成")
private Integer status;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
private Long parentId;
}

View File

@@ -0,0 +1,93 @@
package com.gear.oa.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.gear.common.annotation.ExcelDictFormat;
import com.gear.common.convert.ExcelDictConvert;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 库存流水视图对象 gear_stock_log
*
* @author JR
* @date 2025-08-11
*/
@Data
@ExcelIgnoreUnannotated
public class GearStockLogVo {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 仓库/库区/库位ID
*/
@ExcelProperty(value = "仓库/库区/库位ID")
private Long warehouseId;
/**
* 物品ID指向原材料或产品主键
*/
@ExcelProperty(value = "物品ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "指=向原材料或产品主键")
private Long itemId;
/**
* 物品类型raw_material/product
*/
@ExcelProperty(value = "物品类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "r=aw_material/product")
private String itemType;
/**
* 变动数量(正=入库,负=出库)
*/
@ExcelProperty(value = "变动数量", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "正==入库,负=出库")
private BigDecimal changeQty;
/**
* 变动后的库存数量
*/
@ExcelProperty(value = "变动后的库存数量")
private BigDecimal afterQty;
/**
* 变动类型(入库/出库等)
*/
@ExcelProperty(value = "变动类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "入=库/出库等")
private String changeType;
/**
* 实际库存变动时间
*/
@ExcelProperty(value = "实际库存变动时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date changeTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 仓库/库区名称
*/
@ExcelProperty(value = "仓库/库区名称")
private String warehouseName;
//批次号
private String batchNo;
}

View File

@@ -0,0 +1,96 @@
package com.gear.oa.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.gear.common.annotation.ExcelDictFormat;
import com.gear.common.convert.ExcelDictConvert;
import lombok.Data;
import java.math.BigDecimal;
/**
* 库存:原材料/产品与库区/库位的存放关系视图对象 gear_stock
*
* @author Joshi
* @date 2025-07-18
*/
@Data
@ExcelIgnoreUnannotated
public class GearStockVo {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long stockId;
/**
* 仓库/库区/库位ID
*/
@ExcelProperty(value = "仓库/库区/库位ID")
private Long warehouseId;
/**
* 物品类型raw_material/product
*/
@ExcelProperty(value = "物品类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "raw_material/product")
private String itemType;
/**
* 物品ID指向原材料或产品主键
*/
@ExcelProperty(value = "物品ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "指向原材料或产品主键")
private Long itemId;
/**
* 物品名称(动态:产品或原材料)
*/
private String itemName;
/**
* 物品编码(动态:产品或原材料)
*/
private String itemCode;
/**
* 库存数量
*/
@ExcelProperty(value = "库存数量")
private BigDecimal quantity;
/**
* 单位
*/
@ExcelProperty(value = "单位")
private String unit;
/**
* 批次号(可选)
*/
@ExcelProperty(value = "批次号", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "可=选")
private String batchNo;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 仓库/库区名称
*/
@ExcelProperty(value = "仓库/库区名称")
private String warehouseName;
/**
* 在途量
*/
@ExcelProperty(value = "在途量")
private BigDecimal onTheWay;
}

View File

@@ -0,0 +1,73 @@
package com.gear.oa.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.gear.common.annotation.ExcelDictFormat;
import com.gear.common.convert.ExcelDictConvert;
import lombok.Data;
/**
* 仓库/库区/库位自关联视图对象 gear_warehouse
*
* @author JR
* @date 2025-07-18
*/
@Data
@ExcelIgnoreUnannotated
public class GearWarehouseVo {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long warehouseId;
/**
* 父节点ID指向同表 warehouse_id
*/
@ExcelProperty(value = "父节点ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "指=向同表,w=arehouse_id")
private Long parentId;
/**
* 仓库/库区编码
*/
@ExcelProperty(value = "仓库/库区编码")
private String warehouseCode;
/**
* 仓库/库区名称
*/
@ExcelProperty(value = "仓库/库区名称")
private String warehouseName;
/**
* 类型0=仓库1=库区2=库位,…
*/
@ExcelProperty(value = "类型0=仓库1=库区2=库位,…")
private Long warehouseType;
/**
* 同级排序号
*/
@ExcelProperty(value = "同级排序号")
private Long sortNo;
/**
* 是否启用0=否1=是)
*/
@ExcelProperty(value = "是否启用", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0==否1=是")
private Integer isEnabled;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,34 @@
package com.gear.oa.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.mapper.BaseMapperPlus;
import com.gear.oa.domain.GearStockIoDetail;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 出入库单明细Mapper接口
*
* VO带有库区/库位名称和源库区/库位名称
*
* @author Joshi
* @date 2025-07-18
*/
public interface GearStockIoDetailMapper extends BaseMapperPlus<GearStockIoDetailMapper, GearStockIoDetail, GearStockIoDetailVo> {
/**
* 联查库区/库位名称的明细列表返回Map
*/
List<Map<String, Object>> selectDetailWithWarehouseName(@Param("stockIoId") Long stockIoId);
/**
* 分页联查库区/库位名称的明细列表支持Wrapper动态条件返回Page<GearStockIoDetailVo>
*/
Page<GearStockIoDetailVo> selectVoPagePlus(Page<?> page, @Param("ew") Wrapper<GearStockIoDetail> wrapper);
List<GearStockIoDetailVo> batchQuery(@Param("batchNo") String batchNo);
}

View File

@@ -0,0 +1,15 @@
package com.gear.oa.mapper;
import com.gear.common.core.mapper.BaseMapperPlus;
import com.gear.oa.domain.GearStockIo;
import com.gear.oa.domain.vo.GearStockIoVo;
/**
* 出入库单主Mapper接口
*
* @author Joshi
* @date 2025-07-18
*/
public interface GearStockIoMapper extends BaseMapperPlus<GearStockIoMapper, GearStockIo, GearStockIoVo> {
}

View File

@@ -0,0 +1,19 @@
package com.gear.oa.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.mapper.BaseMapperPlus;
import com.gear.oa.domain.GearStockLog;
import com.gear.oa.domain.vo.GearStockLogVo;
import org.apache.ibatis.annotations.Param;
/**
* 库存流水Mapper接口
*
* @author JR
* @date 2025-08-11
*/
public interface GearStockLogMapper extends BaseMapperPlus<GearStockLogMapper, GearStockLog, GearStockLogVo> {
Page<GearStockLogVo> selectVoPagePlus(Page<?> page, @Param("ew") Wrapper<GearStockLog> wrapper);
}

View File

@@ -0,0 +1,27 @@
package com.gear.oa.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.mapper.BaseMapperPlus;
import com.gear.oa.domain.GearStock;
import com.gear.oa.domain.vo.GearStockVo;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
/**
* 库存:原材料/产品与库区/库位的存放关系Mapper接口
*
* @author Joshi
* @date 2025-07-18
*/
public interface GearStockMapper extends BaseMapperPlus<GearStockMapper, GearStock, GearStockVo> {
BigDecimal getStockByItemId(Long rawMaterialId);
/**
* 分页联查物品名称和编码支持Wrapper动态条件返回Page<GearStockVo>
*/
Page<GearStockVo> selectVoPagePlus(Page<?> page, @Param("ew") Wrapper<GearStock> wrapper);
}

View File

@@ -0,0 +1,15 @@
package com.gear.oa.mapper;
import com.gear.common.core.mapper.BaseMapperPlus;
import com.gear.oa.domain.GearWarehouse;
import com.gear.oa.domain.vo.GearWarehouseVo;
/**
* 仓库/库区/库位自关联Mapper接口
*
* @author JR
* @date 2025-07-18
*/
public interface GearWarehouseMapper extends BaseMapperPlus<GearWarehouseMapper, GearWarehouse, GearWarehouseVo> {
}

View File

@@ -46,49 +46,49 @@ public interface IGearProductService {
* 校验并批量删除产品信息 * 校验并批量删除产品信息
*/ */
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/** /**
* 数据看板 - 获取所有数据 * 数据看板 - 获取所有数据
*/ */
ProductDashboardVO getDashboard(); ProductDashboardVO getDashboard();
/** /**
* 数据看板 - 产品销售情况 * 数据看板 - 产品销售情况
*/ */
List<ProductSalesPerformanceVO> selectProductSalesPerformance(); List<ProductSalesPerformanceVO> selectProductSalesPerformance();
/** /**
* 数据看板 - 销售人员业绩 * 数据看板 - 销售人员业绩
*/ */
List<SalesPersonPerformanceVO> selectSalesPersonPerformance(); List<SalesPersonPerformanceVO> selectSalesPersonPerformance();
/** /**
* 数据看板 - 总订单数量统计 * 数据看板 - 总订单数量统计
*/ */
OrderCountStatisticsVO selectOrderCountStatistics(); OrderCountStatisticsVO selectOrderCountStatistics();
/** /**
* 数据看板 - 订单所需的产品统计 * 数据看板 - 订单所需的产品统计
*/ */
List<OrderProductStatisticsVO> selectOrderProductStatistics(); List<OrderProductStatisticsVO> selectOrderProductStatistics();
/** /**
* 数据看板 - 根据BOM计算的原料需求 * 数据看板 - 根据BOM计算的原料需求
*/ */
List<ProductMaterialRequirementVO> selectProductMaterialRequirements(); List<ProductMaterialRequirementVO> selectProductMaterialRequirements();
/** /**
* 数据看板 - 原料库存和需求情况 * 数据看板 - 原料库存和需求情况
*/ */
List<RawMaterialInventoryVO> selectRawMaterialInventory(); List<RawMaterialInventoryVO> selectRawMaterialInventory();
/** /**
* 数据看板 - 订单维度推荐 * 数据看板 - 订单维度推荐
*/ */
List<OrderRecommendationVO> selectOrderRecommendations(); List<OrderRecommendationVO> selectOrderRecommendations();
/** /**
* 数据看板 - 原料维度推荐 * 数据看板 - 原料维度推荐
*/ */
List<MaterialRecommendationVO> selectMaterialRecommendations(); List<MaterialRecommendationVO> selectMaterialRecommendations();
} }

View File

@@ -0,0 +1,50 @@
package com.gear.oa.service;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.oa.domain.bo.GearStockIoDetailBo;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import java.util.Collection;
import java.util.List;
/**
* 出入库单明细Service接口
*
* @author Joshi
* @date 2025-07-18
*/
public interface IGearStockIoDetailService {
/**
* 查询出入库单明细
*/
GearStockIoDetailVo queryById(Long detailId);
/**
* 查询出入库单明细列表
*/
TableDataInfo<GearStockIoDetailVo> queryPageList(GearStockIoDetailBo bo, PageQuery pageQuery);
/**
* 查询出入库单明细列表
*/
List<GearStockIoDetailVo> queryList(GearStockIoDetailBo bo);
/**
* 新增出入库单明细
*/
Boolean insertByBo(GearStockIoDetailBo bo);
/**
* 修改出入库单明细
*/
Boolean updateByBo(GearStockIoDetailBo bo);
/**
* 校验并批量删除出入库单明细信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
List<GearStockIoDetailVo> batchQuery(GearStockIoDetailBo bo);
}

View File

@@ -0,0 +1,83 @@
package com.gear.oa.service;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.oa.domain.bo.GearStockIoBo;
import com.gear.oa.domain.bo.GearStockIoDetailBo;
import com.gear.oa.domain.bo.GearStockIoWithDetailBo;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import com.gear.oa.domain.vo.GearStockIoVo;
import java.util.Collection;
import java.util.List;
/**
* 出入库单主Service接口
*
* @author Joshi
* @date 2025-07-18
*/
public interface IGearStockIoService {
/**
* 查询出入库单主
*/
GearStockIoVo queryById(Long stockIoId);
/**
* 查询出入库单主列表
*/
TableDataInfo<GearStockIoVo> queryPageList(GearStockIoBo bo, PageQuery pageQuery);
/**
* 查询出入库单主列表
*/
List<GearStockIoVo> queryList(GearStockIoBo bo);
/**
* 新增出入库单主
*/
Boolean insertByBo(GearStockIoBo bo);
/**
* 修改出入库单主
*/
Boolean updateByBo(GearStockIoBo bo);
/**
* 校验并批量删除出入库单主信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 审核出入库/移库单,变更库存,含库存校验
*/
Boolean auditStockIo(Long stockIoId);
/**
* 撤销出入库/移库单,库存回滚
*/
Boolean cancelStockIo(Long stockIoId);
/**
* 根据ioType和stockIoId联查明细
*/
List<GearStockIoDetailVo> detailByTypeAndId(String ioType, Long stockIoId);
/**
* 更新出入库单状态
*/
Boolean updateStatus(Long stockIoId, Integer status);
/**
* 批量新增主表和明细
*/
void addWithDetail(GearStockIoWithDetailBo bo);
// boolean scanInStockByBo(GearStockIoDetailBo bo);
//
// boolean scanOutStockByBo(GearStockIoDetailBo bo);
//
// boolean scanReturnStockByBo(GearStockIoBo bo);
}

View File

@@ -0,0 +1,48 @@
package com.gear.oa.service;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.oa.domain.bo.GearStockLogBo;
import com.gear.oa.domain.vo.GearStockLogVo;
import java.util.Collection;
import java.util.List;
/**
* 库存流水Service接口
*
* @author JR
* @date 2025-08-11
*/
public interface IGearStockLogService {
/**
* 查询库存流水
*/
GearStockLogVo queryById(Long id);
/**
* 查询库存流水列表
*/
TableDataInfo<GearStockLogVo> queryPageList(GearStockLogBo bo, PageQuery pageQuery);
/**
* 查询库存流水列表
*/
List<GearStockLogVo> queryList(GearStockLogBo bo);
/**
* 新增库存流水
*/
Boolean insertByBo(GearStockLogBo bo);
/**
* 修改库存流水
*/
Boolean updateByBo(GearStockLogBo bo);
/**
* 校验并批量删除库存流水信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -0,0 +1,56 @@
package com.gear.oa.service;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.oa.domain.bo.GearStockBo;
import com.gear.oa.domain.vo.GearStockVo;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
/**
* 库存:原材料/产品与库区/库位的存放关系Service接口
*
* @author Joshi
* @date 2025-07-18
*/
public interface IGearStockService {
/**
* 查询库存:原材料/产品与库区/库位的存放关系
*/
GearStockVo queryById(Long stockId);
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
TableDataInfo<GearStockVo> queryPageList(GearStockBo bo, PageQuery pageQuery);
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
List<GearStockVo> queryList(GearStockBo bo);
/**
* 新增库存:原材料/产品与库区/库位的存放关系
*/
Boolean insertByBo(GearStockBo bo);
/**
* 修改库存:原材料/产品与库区/库位的存放关系
*/
Boolean updateByBo(GearStockBo bo);
/**
* 校验并批量删除库存:原材料/产品与库区/库位的存放关系信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
// /**
// * 根据原材料ID获取库存数量 (用于生成推荐采购计划)
// */
// BigDecimal getStockByItemId(Long rawMaterialId);
}

View File

@@ -0,0 +1,42 @@
package com.gear.oa.service;
import com.gear.oa.domain.bo.GearWarehouseBo;
import com.gear.oa.domain.vo.GearWarehouseVo;
import java.util.Collection;
import java.util.List;
/**
* 仓库/库区/库位自关联Service接口
*
* @author JR
* @date 2025-07-18
*/
public interface IGearWarehouseService {
/**
* 查询仓库/库区/库位自关联
*/
GearWarehouseVo queryById(Long warehouseId);
/**
* 查询仓库/库区/库位自关联列表
*/
List<GearWarehouseVo> queryList(GearWarehouseBo bo);
/**
* 新增仓库/库区/库位自关联
*/
Boolean insertByBo(GearWarehouseBo bo);
/**
* 修改仓库/库区/库位自关联
*/
Boolean updateByBo(GearWarehouseBo bo);
/**
* 校验并批量删除仓库/库区/库位自关联信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -144,17 +144,17 @@ public class GearAttendanceSummaryServiceImpl implements IGearAttendanceSummaryS
// 按userId和recordType分组确保每个用户每种类型正确统计 // 按userId和recordType分组确保每个用户每种类型正确统计
Map<String, List<GearAttendanceRecord>> groupedRecords = records.stream() Map<String, List<GearAttendanceRecord>> groupedRecords = records.stream()
.collect(Collectors.groupingBy(r -> r.getUserId() + "_" + r.getRecordType())); .collect(Collectors.groupingBy(r -> r.getUserId() + "_" + r.getRecordType()));
// 为每个用户创建汇总明细 // 为每个用户创建汇总明细
Map<Long, GearAttendanceSummaryDetailVo> summaryDetails = new HashMap<>(); Map<Long, GearAttendanceSummaryDetailVo> summaryDetails = new HashMap<>();
for (List<GearAttendanceRecord> recordList : groupedRecords.values()) { for (List<GearAttendanceRecord> recordList : groupedRecords.values()) {
if (recordList.isEmpty()) continue; if (recordList.isEmpty()) continue;
GearAttendanceRecord firstRecord = recordList.get(0); GearAttendanceRecord firstRecord = recordList.get(0);
Long userId = firstRecord.getUserId(); Long userId = firstRecord.getUserId();
String recordType = firstRecord.getRecordType(); String recordType = firstRecord.getRecordType();
// 获取或创建用户的汇总明细 // 获取或创建用户的汇总明细
GearAttendanceSummaryDetailVo detail = summaryDetails.computeIfAbsent(userId, k -> { GearAttendanceSummaryDetailVo detail = summaryDetails.computeIfAbsent(userId, k -> {
GearAttendanceSummaryDetailVo newDetail = new GearAttendanceSummaryDetailVo(); GearAttendanceSummaryDetailVo newDetail = new GearAttendanceSummaryDetailVo();
@@ -164,12 +164,12 @@ public class GearAttendanceSummaryServiceImpl implements IGearAttendanceSummaryS
newDetail.setTravelHours(BigDecimal.ZERO); newDetail.setTravelHours(BigDecimal.ZERO);
return newDetail; return newDetail;
}); });
// 计算该类型的小时数总和 // 计算该类型的小时数总和
long totalHours = recordList.stream() long totalHours = recordList.stream()
.mapToLong(GearAttendanceRecord::getDurationHour) .mapToLong(GearAttendanceRecord::getDurationHour)
.sum(); .sum();
// 根据记录类型设置对应的小时数 // 根据记录类型设置对应的小时数
switch (recordType) { switch (recordType) {
case "attendance": case "attendance":

View File

@@ -0,0 +1,222 @@
package com.gear.oa.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.exception.ServiceException;
import com.gear.common.utils.StringUtils;
import com.gear.oa.domain.GearProduct;
import com.gear.oa.domain.GearStockIo;
import com.gear.oa.domain.GearStockIoDetail;
import com.gear.oa.domain.bo.GearStockIoDetailBo;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import com.gear.oa.mapper.GearProductMapper;
import com.gear.oa.mapper.GearStockIoDetailMapper;
import com.gear.oa.mapper.GearStockIoMapper;
import com.gear.oa.service.IGearStockIoDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 出入库单明细Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@RequiredArgsConstructor
@Service
public class GearStockIoDetailServiceImpl implements IGearStockIoDetailService {
private final GearStockIoDetailMapper baseMapper;
private final GearStockIoMapper stockIoMapper;
private final GearProductMapper productMapper;
/**
* 查询出入库单明细
*/
@Override
public GearStockIoDetailVo queryById(Long detailId){
return baseMapper.selectVoById(detailId);
}
/**
* 查询出入库单明细列表
*/
@Override
public TableDataInfo<GearStockIoDetailVo> queryPageList(GearStockIoDetailBo bo, PageQuery pageQuery) {
QueryWrapper<GearStockIoDetail> qw = buildQueryWrapperPlus(bo);
Page<GearStockIoDetailVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), qw);
return TableDataInfo.build(result);
}
/**
* 查询出入库单明细列表
*/
@Override
public List<GearStockIoDetailVo> queryList(GearStockIoDetailBo bo) {
LambdaQueryWrapper<GearStockIoDetail> qw = buildQueryWrapper(bo);
return baseMapper.selectVoList(qw);
}
private LambdaQueryWrapper<GearStockIoDetail> buildQueryWrapper(GearStockIoDetailBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<GearStockIoDetail> lqw = new LambdaQueryWrapper<>();
lqw.eq(bo.getStockIoId() != null, GearStockIoDetail::getStockIoId, bo.getStockIoId());
lqw.eq(bo.getWarehouseId() != null, GearStockIoDetail::getWarehouseId, bo.getWarehouseId());
lqw.eq(StringUtils.isNotBlank(bo.getItemType()), GearStockIoDetail::getItemType, bo.getItemType());
lqw.eq(bo.getItemId() != null, GearStockIoDetail::getItemId, bo.getItemId());
lqw.eq(bo.getQuantity() != null, GearStockIoDetail::getQuantity, bo.getQuantity());
lqw.eq(StringUtils.isNotBlank(bo.getUnit()), GearStockIoDetail::getUnit, bo.getUnit());
lqw.eq(StringUtils.isNotBlank(bo.getBatchNo()), GearStockIoDetail::getBatchNo, bo.getBatchNo());
return lqw;
}
private QueryWrapper<GearStockIoDetail> buildQueryWrapperPlus(GearStockIoDetailBo bo) {
Map<String, Object> params = bo.getParams();
QueryWrapper<GearStockIoDetail> qw = new QueryWrapper<>();
qw.eq("d.del_flag", 0); // 手动添加逻辑删除条件
qw.eq(bo.getStockIoId() != null, "d.stock_io_id", bo.getStockIoId());
qw.eq(bo.getWarehouseId() != null, "d.warehouse_id", bo.getWarehouseId());
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "d.item_type", bo.getItemType());
qw.eq(bo.getItemId() != null, "d.item_id", bo.getItemId());
qw.eq(bo.getQuantity() != null, "d.quantity", bo.getQuantity());
qw.eq(StringUtils.isNotBlank(bo.getUnit()), "d.unit", bo.getUnit());
qw.eq(StringUtils.isNotBlank(bo.getBatchNo()), "d.batch_no", bo.getBatchNo());
return qw;
}
/**
* 新增出入库单明细
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(GearStockIoDetailBo bo) {
// 检查主表状态,已审核的单据不能添加明细
GearStockIo stockIo = stockIoMapper.selectById(bo.getStockIoId());
if (stockIo != null && stockIo.getStatus() >= 2) {
throw new ServiceException("已审核的单据不能修改明细");
}
// 如果unit为空自动查item表补全新增逻辑
String unit = bo.getUnit();
if (unit == null || unit.trim().isEmpty()) {
if ("product".equals(bo.getItemType())) {
GearProduct p = productMapper.selectById(bo.getItemId());
unit = p != null ? p.getUnit() : null;
}
}
// 将获取到的unit设置回bo对象
bo.setUnit(unit);
GearStockIoDetail add = BeanUtil.toBean(bo, GearStockIoDetail.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setDetailId(add.getDetailId());
// 检查主表状态,如果有明细且状态为草稿,则自动变为待审核
if (stockIo != null && stockIo.getStatus() == 0) {
// 检查是否有明细
Long detailCount = baseMapper.selectCount(
new LambdaQueryWrapper<GearStockIoDetail>()
.eq(GearStockIoDetail::getStockIoId, bo.getStockIoId())
);
if (detailCount > 0) {
// 自动更新状态为待审核
stockIo.setStatus(1);
stockIo.setUpdateTime(new Date());
stockIoMapper.updateById(stockIo);
}
}
}
return flag;
}
/**
* 修改出入库单明细
*/
@Override
public Boolean updateByBo(GearStockIoDetailBo bo) {
// 检查主表状态,已审核的单据不能修改明细
GearStockIo stockIo = stockIoMapper.selectById(bo.getStockIoId());
if (stockIo != null && stockIo.getStatus() >= 2) {
throw new ServiceException("已审核的单据不能修改明细");
}
GearStockIoDetail update = BeanUtil.toBean(bo, GearStockIoDetail.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(GearStockIoDetail entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除出入库单明细
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
// 获取要删除的明细对应的主表ID检查状态
List<GearStockIoDetail> details = baseMapper.selectBatchIds(ids);
if (details != null && !details.isEmpty()) {
// 检查主表状态,已审核的单据不能删除明细
for (GearStockIoDetail detail : details) {
GearStockIo stockIo = stockIoMapper.selectById(detail.getStockIoId());
if (stockIo != null && stockIo.getStatus() >= 2) {
throw new ServiceException("已审核的单据不能修改明细");
}
}
// 按主表ID分组
Map<Long, List<GearStockIoDetail>> stockIoIdMap = details.stream()
.collect(java.util.stream.Collectors.groupingBy(GearStockIoDetail::getStockIoId));
boolean flag = baseMapper.deleteBatchIds(ids) > 0;
if (flag) {
// 检查每个主表是否还有其他明细
for (Long stockIoId : stockIoIdMap.keySet()) {
Long remainingCount = baseMapper.selectCount(
new LambdaQueryWrapper<GearStockIoDetail>()
.eq(GearStockIoDetail::getStockIoId, stockIoId)
);
// 如果没有明细了,状态回退为草稿
if (remainingCount == 0) {
GearStockIo stockIo = stockIoMapper.selectById(stockIoId);
if (stockIo != null && stockIo.getStatus() == 1) {
stockIo.setStatus(0);
stockIo.setUpdateTime(new Date());
stockIoMapper.updateById(stockIo);
}
}
}
}
return flag;
}
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
public List<GearStockIoDetailVo> batchQuery(GearStockIoDetailBo bo) {
//根据这个bo.getBatchNo()拿到这个批次号对应明细表的信息在根据明细表带的stockIoId拿到库存表信息
return baseMapper.batchQuery(bo.getBatchNo());
}
}

View File

@@ -0,0 +1,491 @@
package com.gear.oa.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.exception.ServiceException;
import com.gear.common.utils.StringUtils;
import com.gear.oa.domain.*;
import com.gear.oa.domain.bo.GearStockIoBo;
import com.gear.oa.domain.bo.GearStockIoDetailBo;
import com.gear.oa.domain.bo.GearStockIoWithDetailBo;
import com.gear.oa.domain.vo.GearStockIoDetailVo;
import com.gear.oa.domain.vo.GearStockIoVo;
import com.gear.oa.mapper.*;
import com.gear.oa.service.IGearStockIoService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
/**
* 出入库单主Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@RequiredArgsConstructor
@Service
public class GearStockIoServiceImpl implements IGearStockIoService {
private final GearStockIoMapper baseMapper;
private final GearStockIoDetailMapper stockIoDetailMapper;
private final GearStockMapper stockMapper;
private final GearProductMapper productMapper;
// private final GearRawMaterialMapper rawMaterialMapper;
private final GearWarehouseMapper warehouseMapper;
@Resource
private GearStockLogMapper stockLogMapper;
/**
* 查询出入库单主
*/
@Override
public GearStockIoVo queryById(Long stockIoId){
return baseMapper.selectVoById(stockIoId);
}
/**
* 查询出入库单主列表
*/
@Override
public TableDataInfo<GearStockIoVo> queryPageList(GearStockIoBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<GearStockIo> lqw = buildQueryWrapper(bo);
Page<GearStockIoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询出入库单主列表
*/
@Override
public List<GearStockIoVo> queryList(GearStockIoBo bo) {
LambdaQueryWrapper<GearStockIo> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<GearStockIo> buildQueryWrapper(GearStockIoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<GearStockIo> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getStockIoCode()), GearStockIo::getStockIoCode, bo.getStockIoCode());
lqw.eq(StringUtils.isNotBlank(bo.getIoType()), GearStockIo::getIoType, bo.getIoType());
lqw.eq(StringUtils.isNotBlank(bo.getBizType()), GearStockIo::getBizType, bo.getBizType());
lqw.eq(bo.getStatus() != null, GearStockIo::getStatus, bo.getStatus());
return lqw;
}
/**
* 新增出入库单主
*/
@Override
public Boolean insertByBo(GearStockIoBo bo) {
GearStockIo add = BeanUtil.toBean(bo, GearStockIo.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setStockIoId(add.getStockIoId());
}
return flag;
}
/**
* 修改出入库单主
*/
@Override
public Boolean updateByBo(GearStockIoBo bo) {
GearStockIo update = BeanUtil.toBean(bo, GearStockIo.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(GearStockIo entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除出入库单主
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 批量新增主表和明细
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void addWithDetail(GearStockIoWithDetailBo bo) {
// 插入主表
GearStockIo stockIo = BeanUtil.toBean(bo, GearStockIo.class);
stockIo.setParentId(bo.getParentId());
baseMapper.insert(stockIo);
// 插入明细
if (bo.getDetails() != null && !bo.getDetails().isEmpty()) {
for (GearStockIoDetailBo detailBo : bo.getDetails()) {
GearStockIoDetail detail = BeanUtil.toBean(detailBo, GearStockIoDetail.class);
detail.setStockIoId(stockIo.getStockIoId());
stockIoDetailMapper.insert(detail);
}
}
}
// @Override
// @Transactional(rollbackFor = Exception.class)
// public boolean scanInStockByBo(GearStockIoDetailBo bo) {
// String unit = bo.getUnit();
// // 如果unit为空自动查item表补全
// if (unit == null || unit.trim().isEmpty()) {
// if ("product".equals(bo.getItemType())) {
// GearProduct p = productMapper.selectById(bo.getItemId());
// unit = p != null ? p.getUnit() : null;
// } else if ("raw_material".equals(bo.getItemType())) {
// GearRawMaterial r = rawMaterialMapper.selectById(bo.getItemId());
// unit = r != null ? r.getUnit() : null;
// }
// }
// if (unit == null || unit.trim().isEmpty()) {
// throw new RuntimeException("未能获取到单位");
// }
// // 入库操作
// changeStock(bo.getWarehouseId(), bo.getItemType(), bo.getItemId(), bo.getBatchNo(), bo.getQuantity(), true, unit);
// return true;
// }
// @Override
// @Transactional(rollbackFor = Exception.class)
// public boolean scanOutStockByBo(GearStockIoDetailBo bo) {
// String unit = bo.getUnit();
// // 如果unit为空自动查item表补全
// if (unit == null || unit.trim().isEmpty()) {
// if ("product".equals(bo.getItemType())) {
// GearProduct p = productMapper.selectById(bo.getItemId());
// unit = p != null ? p.getUnit() : null;
// } else if ("raw_material".equals(bo.getItemType())) {
// GearRawMaterial r = rawMaterialMapper.selectById(bo.getItemId());
// unit = r != null ? r.getUnit() : null;
// }
// }
// if (unit == null || unit.trim().isEmpty()) {
// throw new RuntimeException("未能获取到单位");
// }
// // 出库操作
// changeStock(bo.getWarehouseId(), bo.getItemType(), bo.getItemId(), bo.getBatchNo(), bo.getQuantity(), false, unit);
// return true;
// }
// @Override
// @Transactional(rollbackFor = Exception.class)
// public boolean scanReturnStockByBo(GearStockIoBo bo) {
// // 退库操作根据主表ID查询明细列表验证退库数量执行退库
// if (bo.getStockIoId() == null) {
// throw new ServiceException("退库单ID不能为空");
// }
//
// // 1. 获取退库单主表信息
// GearStockIo returnStockIo = baseMapper.selectById(bo.getStockIoId());
// if (returnStockIo == null || returnStockIo.getParentId() == null) {
// throw new ServiceException("退库单不存在或未关联原出库单");
// }
//
// // 2. 获取退库单明细列表
// List<GearStockIoDetail> returnDetails = stockIoDetailMapper.selectList(
// Wrappers.<GearStockIoDetail>lambdaQuery().eq(GearStockIoDetail::getStockIoId, bo.getStockIoId())
// );
//
// if (returnDetails == null || returnDetails.isEmpty()) {
// throw new ServiceException("退库单明细不能为空");
// }
//
// // 3. 获取原出库单明细列表
// List<GearStockIoDetail> originalOutDetails = stockIoDetailMapper.selectList(
// Wrappers.<GearStockIoDetail>lambdaQuery().eq(GearStockIoDetail::getStockIoId, returnStockIo.getParentId())
// );
//
// if (originalOutDetails == null || originalOutDetails.isEmpty()) {
// throw new ServiceException("原出库单明细不存在");
// }
//
// // 4. 遍历退库明细,验证数量并执行退库
// for (GearStockIoDetail returnDetail : returnDetails) {
// // 查找对应的原出库明细
// GearStockIoDetail originalOutDetail = originalOutDetails.stream()
// .filter(detail -> detail.getItemType().equals(returnDetail.getItemType()) &&
// detail.getItemId().equals(returnDetail.getItemId()) &&
// detail.getWarehouseId().equals(returnDetail.getWarehouseId()))
// .findFirst()
// .orElse(null);
//
// if (originalOutDetail == null) {
// throw new ServiceException("未找到对应的原出库明细:" + returnDetail.getItemType() + "-" + returnDetail.getItemId());
// }
//
// // 验证退库数量不能超过原出库数量
// BigDecimal totalReturnedQty = baseMapper.selectList(
// Wrappers.<GearStockIo>lambdaQuery()
// .eq(GearStockIo::getParentId, returnStockIo.getParentId())
// .eq(GearStockIo::getIoType, "withdraw")
// .ne(GearStockIo::getStockIoId, returnStockIo.getStockIoId())
// ).stream()
// .flatMap(returnStockIoItem -> stockIoDetailMapper.selectList(
// Wrappers.<GearStockIoDetail>lambdaQuery()
// .eq(GearStockIoDetail::getStockIoId, returnStockIoItem.getStockIoId())
// .eq(GearStockIoDetail::getItemType, returnDetail.getItemType())
// .eq(GearStockIoDetail::getItemId, returnDetail.getItemId())
// .eq(GearStockIoDetail::getWarehouseId, returnDetail.getWarehouseId())
// ).stream())
// .map(GearStockIoDetail::getQuantity)
// .reduce(BigDecimal.ZERO, BigDecimal::add);
//
// BigDecimal remainingQty = originalOutDetail.getQuantity().subtract(totalReturnedQty);
// if (returnDetail.getQuantity().compareTo(remainingQty) > 0) {
// throw new ServiceException("退库数量超过原出库数量,物品:" + returnDetail.getItemType() + "-" + returnDetail.getItemId() +
// ",原出库数量:" + originalOutDetail.getQuantity() + ",已退库数量:" + totalReturnedQty +
// ",剩余可退数量:" + remainingQty + ",本次退库数量:" + returnDetail.getQuantity());
// }
//
// // 执行退库操作(增加库存)
// String unit = returnDetail.getUnit();
// if (unit == null || unit.trim().isEmpty()) {
// if ("product".equals(returnDetail.getItemType())) {
// GearProduct p = productMapper.selectById(returnDetail.getItemId());
// unit = p != null ? p.getUnit() : "个";
// } else if ("raw_material".equals(returnDetail.getItemType())) {
// GearRawMaterial r = rawMaterialMapper.selectById(returnDetail.getItemId());
// unit = r != null ? r.getUnit() : "个";
// }
// }
//
// // 执行入库操作(退库就是入库)
// changeStock(returnDetail.getWarehouseId(), returnDetail.getItemType(),
// returnDetail.getItemId(), returnDetail.getBatchNo(), returnDetail.getQuantity(), true, unit);
// }
// // 更新单据状态为已审核2
// returnStockIo.setStatus(2);
// return baseMapper.updateById(returnStockIo) > 0;
// }
/**
* 审核出入库/移库单,变更库存,含库存校验
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean auditStockIo(Long stockIoId) {
GearStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null || stockIo.getStatus() == null || stockIo.getStatus() != 1) {
// 只允许审核“已提交”状态的单据
throw new RuntimeException("单据不存在或状态不允许审核");
}
List<GearStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<GearStockIoDetail>lambdaQuery().eq(GearStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
throw new RuntimeException("单据明细不能为空");
}
//如果details.batchNo不相等则需要去stock表新建一条批次号不同的记录
for (GearStockIoDetail detail : details) {
String ioType = stockIo.getIoType();
if ("in".equals(ioType)) {
// 入库:目标库位库存增加
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), true, detail.getUnit());
} else if ("out".equals(ioType)) {
// 出库:目标库位库存减少
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), false, detail.getUnit());
} else if ("transfer".equals(ioType)) {
// 移库fromWarehouseId减少warehouseId增加
if (detail.getFromWarehouseId() == null) {
throw new RuntimeException("移库明细缺少源库位ID");
}
// 先减少源库位
changeStock(detail.getFromWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), false, detail.getUnit());
// 再增加目标库位
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), true, detail.getUnit());
} else {
throw new RuntimeException("未知的出入库类型");
}
}
// 更新单据状态为已审核2
stockIo.setStatus(2);
return baseMapper.updateById(stockIo) > 0;
}
/**
* 撤销出入库/移库单,库存回滚
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cancelStockIo(Long stockIoId) {
GearStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null || stockIo.getStatus() == null || stockIo.getStatus() != 2) {
throw new ServiceException("只能撤销已审核的单据");
}
List<GearStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<GearStockIoDetail>lambdaQuery().eq(GearStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
throw new ServiceException("单据明细不能为空");
}
for (GearStockIoDetail detail : details) {
String ioType = stockIo.getIoType();
if ("in".equals(ioType)) {
// 入库撤销:目标库位库存减少
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), false, detail.getUnit());
} else if ("out".equals(ioType)) {
// 出库撤销:目标库位库存增加
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), true, detail.getUnit());
} else if ("transfer".equals(ioType)) {
if (detail.getFromWarehouseId() == null) {
throw new ServiceException("移库明细缺少源库位ID");
}
// 源库位库存增加
changeStock(detail.getFromWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), true, detail.getUnit());
// 目标库位库存减少
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), false, detail.getUnit());
} else {
throw new ServiceException("未知的出入库类型");
}
}
// 更新单据状态为待审核1
stockIo.setStatus(1);
baseMapper.updateById(stockIo);
return true;
}
/**
* 根据ioType和stockIoId联查明细
*/
@Override
public List<GearStockIoDetailVo> detailByTypeAndId(String ioType, Long stockIoId) {
GearStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null) {
throw new ServiceException("单据不存在");
}
if (!ioType.equals(stockIo.getIoType())) {
throw new ServiceException("单据类型不匹配");
}
List<GearStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<GearStockIoDetail>lambdaQuery().eq(GearStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
return Collections.emptyList();
}
// 转VO
List<GearStockIoDetailVo> voList = new ArrayList<>();
for (GearStockIoDetail detail : details) {
GearStockIoDetailVo vo = new GearStockIoDetailVo();
BeanUtils.copyProperties(detail, vo);
voList.add(vo);
}
return voList;
}
/**
* 更新出入库单状态
*/
@Override
public Boolean updateStatus(Long stockIoId, Integer status) {
GearStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null) {
throw new ServiceException("单据不存在");
}
// 状态流转验证
if (stockIo.getStatus() == 0 && status == 1) {
// 草稿 -> 已提交:需要检查是否有明细
List<GearStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<GearStockIoDetail>lambdaQuery().eq(GearStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
throw new ServiceException("单据明细不能为空,无法提交");
}
} else if (stockIo.getStatus() == 1 && status == 0) {
// 已提交 -> 草稿:允许回退
} else {
throw new ServiceException("状态流转不允许");
}
stockIo.setStatus(status);
return baseMapper.updateById(stockIo) > 0;
}
/**
* 库存增减isAdd=true为增加false为减少减少时校验库存是否足够
*/
private void changeStock(Long warehouseId, String itemType, Long itemId, BigDecimal quantity, boolean isAdd, String unit) {
changeStock(warehouseId, itemType, itemId, null, quantity, isAdd, unit);
}
/**
* 库存增减支持批次号isAdd=true为增加false为减少减少时校验库存是否足够
*/
private void changeStock(Long warehouseId, String itemType, Long itemId, String batchNo, BigDecimal quantity, boolean isAdd, String unit) {
GearStock stock = null;
if (StringUtils.isNotBlank(batchNo)) {
// 如果指定了批次号,按批次号查找(应该是唯一的)
stock = stockMapper.selectOne(Wrappers.<GearStock>lambdaQuery()
.eq(GearStock::getWarehouseId, warehouseId)
.eq(GearStock::getItemType, itemType)
.eq(GearStock::getItemId, itemId)
.eq(GearStock::getBatchNo, batchNo));
} else {
// 如果没有指定批次号,查找任意一个批次(按创建时间排序取第一个)
stock = stockMapper.selectOne(Wrappers.<GearStock>lambdaQuery()
.eq(GearStock::getWarehouseId, warehouseId)
.eq(GearStock::getItemType, itemType)
.eq(GearStock::getItemId, itemId)
.orderByAsc(GearStock::getCreateTime)
.last("limit 1"));
}
if (stock == null) {
if (!isAdd) {
throw new RuntimeException("库存不足,无法出库/移库");
}
// 新增库存记录
stock = new GearStock();
stock.setWarehouseId(warehouseId);
stock.setItemType(itemType);
stock.setItemId(itemId);
stock.setBatchNo(StringUtils.isNotBlank(batchNo) ? batchNo : "B-100");
stock.setQuantity(quantity);
stock.setUnit(unit);
stockMapper.insert(stock);
} else {
// 更新现有库存
BigDecimal newQty = isAdd ? stock.getQuantity().add(quantity) : stock.getQuantity().subtract(quantity);
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("库存不足,无法出库/移库");
}
stock.setQuantity(newQty);
stockMapper.updateById(stock);
}
// 记录库存变更日志
GearStockLog log = new GearStockLog();
log.setWarehouseId(warehouseId);
log.setItemType(itemType);
log.setItemId(itemId);
// 变动数量(正=入库,负=出库)
log.setChangeQty(isAdd ? quantity : quantity.negate());
// 变动后的库存数量
log.setAfterQty(stock.getQuantity());
// 变动类型(入库/出库等)
log.setChangeType(isAdd ? "入库" : "出库");
log.setChangeTime(new Date());
log.setBatchNo(batchNo);
stockLogMapper.insert(log);
}
}

View File

@@ -0,0 +1,183 @@
package com.gear.oa.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.utils.StringUtils;
import com.gear.oa.domain.GearStockLog;
import com.gear.oa.domain.GearWarehouse;
import com.gear.oa.domain.bo.GearStockLogBo;
import com.gear.oa.domain.vo.GearStockLogVo;
import com.gear.oa.mapper.GearStockLogMapper;
import com.gear.oa.mapper.GearWarehouseMapper;
import com.gear.oa.service.IGearStockLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 库存流水Service业务层处理
*
* @author JR
* @date 2025-08-11
*/
@RequiredArgsConstructor
@Service
public class GearStockLogServiceImpl implements IGearStockLogService {
private final GearStockLogMapper baseMapper;
@Resource
private GearWarehouseMapper warehouseMapper;
/**
* 查询库存流水
*/
@Override
public GearStockLogVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 查询库存流水列表
*/
@Override
public TableDataInfo<GearStockLogVo> queryPageList(GearStockLogBo bo, PageQuery pageQuery) {
QueryWrapper<GearStockLog> lqw = buildQueryWrapperPlus(bo);
Page<GearStockLogVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询库存流水列表
*/
@Override
public List<GearStockLogVo> queryList(GearStockLogBo bo) {
LambdaQueryWrapper<GearStockLog> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private QueryWrapper<GearStockLog> buildQueryWrapperPlus(GearStockLogBo bo) {
QueryWrapper<GearStockLog> lqw = Wrappers.query();
lqw.eq("sl.del_flag", 0);
// 处理仓库ID查询支持递归查询子节点
if (bo.getWarehouseId() != null) {
List<Long> warehouseIds = getWarehouseIdsWithChildren(bo.getWarehouseId());
if (warehouseIds.size() == 1) {
lqw.eq("sl.warehouse_id", warehouseIds.get(0));
} else {
lqw.in("sl.warehouse_id", warehouseIds);
}
}
lqw.eq(bo.getItemId() != null, "sl.item_id", bo.getItemId());
lqw.eq(StringUtils.isNotBlank(bo.getItemType()), "sl.item_type", bo.getItemType());
lqw.eq(bo.getChangeQty() != null, "sl.change_qty", bo.getChangeQty());
lqw.eq(bo.getAfterQty() != null, "sl.after_qty", bo.getAfterQty());
lqw.eq(StringUtils.isNotBlank(bo.getChangeType()), "sl.change_type", bo.getChangeType());
// 根据时间区间查询
lqw.ge(bo.getStartTime() != null, "sl.change_time", bo.getStartTime());
lqw.le(bo.getEndTime() != null, "sl.change_time", bo.getEndTime());
lqw.eq(StringUtils.isNotBlank(bo.getBatchNo()), "sl.batch_no", bo.getBatchNo());
return lqw;
}
private LambdaQueryWrapper<GearStockLog> buildQueryWrapper(GearStockLogBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<GearStockLog> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getWarehouseId() != null, GearStockLog::getWarehouseId, bo.getWarehouseId());
lqw.eq(bo.getItemId() != null, GearStockLog::getItemId, bo.getItemId());
lqw.eq(StringUtils.isNotBlank(bo.getItemType()), GearStockLog::getItemType, bo.getItemType());
lqw.eq(bo.getChangeQty() != null, GearStockLog::getChangeQty, bo.getChangeQty());
lqw.eq(bo.getAfterQty() != null, GearStockLog::getAfterQty, bo.getAfterQty());
lqw.eq(StringUtils.isNotBlank(bo.getChangeType()), GearStockLog::getChangeType, bo.getChangeType());
lqw.eq(StringUtils.isNotBlank(bo.getBatchNo()), GearStockLog::getBatchNo, bo.getBatchNo());
// 根据时间区间查询
lqw.ge(bo.getStartTime() != null, GearStockLog::getChangeTime, bo.getStartTime());
lqw.le(bo.getEndTime() != null, GearStockLog::getChangeTime, bo.getEndTime());
return lqw;
}
/**
* 新增库存流水
*/
@Override
public Boolean insertByBo(GearStockLogBo bo) {
GearStockLog add = BeanUtil.toBean(bo, GearStockLog.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改库存流水
*/
@Override
public Boolean updateByBo(GearStockLogBo bo) {
GearStockLog update = BeanUtil.toBean(bo, GearStockLog.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(GearStockLog entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除库存流水
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 获取指定仓库ID及其所有子仓库ID
* @param warehouseId 仓库ID
* @return 包含当前仓库ID和所有子仓库ID的列表
*/
private List<Long> getWarehouseIdsWithChildren(Long warehouseId) {
List<Long> warehouseIds = new ArrayList<>();
warehouseIds.add(warehouseId); // 添加当前仓库ID
// 递归获取所有子仓库ID
getChildWarehouseIds(warehouseId, warehouseIds);
return warehouseIds;
}
/**
* 递归获取子仓库ID
* @param parentId 父仓库ID
* @param warehouseIds 仓库ID列表用于收集结果
*/
private void getChildWarehouseIds(Long parentId, List<Long> warehouseIds) {
// 查询直接子仓库
List<GearWarehouse> children = warehouseMapper.selectList(
Wrappers.<GearWarehouse>lambdaQuery()
.eq(GearWarehouse::getParentId, parentId)
.eq(GearWarehouse::getDelFlag, 0)
);
// 递归处理每个子仓库
for (GearWarehouse child : children) {
warehouseIds.add(child.getWarehouseId());
getChildWarehouseIds(child.getWarehouseId(), warehouseIds);
}
}
}

View File

@@ -0,0 +1,220 @@
package com.gear.oa.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.utils.StringUtils;
import com.gear.oa.domain.GearStock;
import com.gear.oa.domain.GearWarehouse;
import com.gear.oa.domain.bo.GearPurchasePlanDetailBo;
import com.gear.oa.domain.bo.GearStockBo;
import com.gear.oa.domain.vo.GearPurchasePlanDetailVo;
import com.gear.oa.domain.vo.GearStockVo;
import com.gear.oa.mapper.GearStockMapper;
import com.gear.oa.mapper.GearWarehouseMapper;
import com.gear.oa.service.IGearPurchasePlanDetailService;
import com.gear.oa.service.IGearStockService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 库存:原材料/产品与库区/库位的存放关系Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@RequiredArgsConstructor
@Service
public class GearStockServiceImpl implements IGearStockService {
private final GearStockMapper baseMapper;
private final GearWarehouseMapper warehouseMapper;
// private final IGearPurchasePlanDetailService purchasePlanDetailService;
/**
* 查询库存:原材料/产品与库区/库位的存放关系
*/
@Override
public GearStockVo queryById(Long stockId){
return baseMapper.selectVoById(stockId);
}
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
@Override
public TableDataInfo<GearStockVo> queryPageList(GearStockBo bo, PageQuery pageQuery) {
QueryWrapper<GearStock> lqw = buildQueryWrapperPlus(bo);
Page<GearStockVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw);
// fillDemandInfo(result.getRecords());
return TableDataInfo.build(result);
}
// /**
// * 填充原材料在途信息
// */
// private void fillDemandInfo(List<GearStockVo> rawMaterialList) {
// if (rawMaterialList == null || rawMaterialList.isEmpty()) {
// return;
// }
// // 为每个原材料填充信息
// for (GearStockVo vo : rawMaterialList) {
// Long rawMaterialId = vo.getItemId();
// // 查询在途量(采购计划明细)
// BigDecimal onTheWay = getOnTheWayQuantity(rawMaterialId);
// vo.setOnTheWay(onTheWay);
// }
// }
// /**
// * 获取在途量
// */
// private BigDecimal getOnTheWayQuantity(Long rawMaterialId) {
// GearPurchasePlanDetailBo bo = new GearPurchasePlanDetailBo();
// bo.setRawMaterialId(rawMaterialId);
// List<GearPurchasePlanDetailVo> list = purchasePlanDetailService.queryList(bo);
// return list.stream()
// .filter(item -> item.getStatus() != null && item.getStatus() == 1) // 在途状态
// .map(GearPurchasePlanDetailVo::getQuantity)
// .filter(qty -> qty != null)
// .reduce(BigDecimal.ZERO, BigDecimal::add);
// }
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
@Override
public List<GearStockVo> queryList(GearStockBo bo) {
LambdaQueryWrapper<GearStock> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<GearStock> buildQueryWrapper(GearStockBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<GearStock> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(bo.getItemType()), GearStock::getItemType, bo.getItemType());
lqw.eq(bo.getItemId() != null, GearStock::getItemId, bo.getItemId());
lqw.eq(bo.getQuantity() != null, GearStock::getQuantity, bo.getQuantity());
lqw.eq(StringUtils.isNotBlank(bo.getUnit()), GearStock::getUnit, bo.getUnit());
lqw.eq(StringUtils.isNotBlank(bo.getBatchNo()), GearStock::getBatchNo, bo.getBatchNo());
return lqw;
}
private QueryWrapper<GearStock> buildQueryWrapperPlus(GearStockBo bo) { // 注意:这里改用 QueryWrapper 而非 LambdaQueryWrapper
Map<String, Object> params = bo.getParams();
QueryWrapper<GearStock> qw = Wrappers.query(); // 使用普通 QueryWrapper
qw.eq("s.del_flag", 0); // 手动添加逻辑删除条件
// 处理仓库ID查询支持递归查询子节点
if (bo.getWarehouseId() != null) {
List<Long> warehouseIds = getWarehouseIdsWithChildren(bo.getWarehouseId());
if (warehouseIds.size() == 1) {
qw.eq("s.warehouse_id", warehouseIds.get(0));
} else {
qw.in("s.warehouse_id", warehouseIds);
}
}
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "s.item_type", bo.getItemType());
qw.eq(bo.getItemId() != null, "s.item_id", bo.getItemId());
qw.eq(bo.getQuantity() != null, "s.quantity", bo.getQuantity());
qw.eq(StringUtils.isNotBlank(bo.getUnit()), "s.unit", bo.getUnit());
qw.eq(StringUtils.isNotBlank(bo.getBatchNo()), "s.batch_no", bo.getBatchNo());
return qw;
}
/**
* 新增库存:原材料/产品与库区/库位的存放关系
*/
@Override
public Boolean insertByBo(GearStockBo bo) {
GearStock add = BeanUtil.toBean(bo, GearStock.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setStockId(add.getStockId());
}
return flag;
}
/**
* 修改库存:原材料/产品与库区/库位的存放关系
*/
@Override
public Boolean updateByBo(GearStockBo bo) {
GearStock update = BeanUtil.toBean(bo, GearStock.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(GearStock entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除库存:原材料/产品与库区/库位的存放关系
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
// @Override
// public BigDecimal getStockByItemId(Long rawMaterialId) {
// return baseMapper.getStockByItemId(rawMaterialId);
// }
/**
* 获取指定仓库ID及其所有子仓库ID
* @param warehouseId 仓库ID
* @return 包含当前仓库ID和所有子仓库ID的列表
*/
private List<Long> getWarehouseIdsWithChildren(Long warehouseId) {
List<Long> warehouseIds = new ArrayList<>();
warehouseIds.add(warehouseId); // 添加当前仓库ID
// 递归获取所有子仓库ID
getChildWarehouseIds(warehouseId, warehouseIds);
return warehouseIds;
}
/**
* 递归获取子仓库ID
* @param parentId 父仓库ID
* @param warehouseIds 仓库ID列表用于收集结果
*/
private void getChildWarehouseIds(Long parentId, List<Long> warehouseIds) {
// 查询直接子仓库
List<GearWarehouse> children = warehouseMapper.selectList(
Wrappers.<GearWarehouse>lambdaQuery()
.eq(GearWarehouse::getParentId, parentId)
.eq(GearWarehouse::getDelFlag, 0)
);
// 递归处理每个子仓库
for (GearWarehouse child : children) {
warehouseIds.add(child.getWarehouseId());
getChildWarehouseIds(child.getWarehouseId(), warehouseIds);
}
}
}

View File

@@ -0,0 +1,104 @@
package com.gear.oa.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.gear.common.utils.StringUtils;
import com.gear.oa.domain.GearWarehouse;
import com.gear.oa.domain.bo.GearWarehouseBo;
import com.gear.oa.domain.vo.GearWarehouseVo;
import com.gear.oa.mapper.GearWarehouseMapper;
import com.gear.oa.service.IGearWarehouseService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 仓库/库区/库位自关联Service业务层处理
*
* @author JR
* @date 2025-07-18
*/
@RequiredArgsConstructor
@Service
public class GearWarehouseServiceImpl implements IGearWarehouseService {
private final GearWarehouseMapper baseMapper;
/**
* 查询仓库/库区/库位自关联
*/
@Override
public GearWarehouseVo queryById(Long warehouseId){
return baseMapper.selectVoById(warehouseId);
}
/**
* 查询仓库/库区/库位自关联列表
*/
@Override
public List<GearWarehouseVo> queryList(GearWarehouseBo bo) {
LambdaQueryWrapper<GearWarehouse> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<GearWarehouse> buildQueryWrapper(GearWarehouseBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<GearWarehouse> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getParentId() != null, GearWarehouse::getParentId, bo.getParentId());
lqw.eq(StringUtils.isNotBlank(bo.getWarehouseCode()), GearWarehouse::getWarehouseCode, bo.getWarehouseCode());
lqw.like(StringUtils.isNotBlank(bo.getWarehouseName()), GearWarehouse::getWarehouseName, bo.getWarehouseName());
lqw.eq(bo.getWarehouseType() != null, GearWarehouse::getWarehouseType, bo.getWarehouseType());
lqw.eq(bo.getSortNo() != null, GearWarehouse::getSortNo, bo.getSortNo());
lqw.eq(bo.getIsEnabled() != null, GearWarehouse::getIsEnabled, bo.getIsEnabled());
// 新增排序SortNo升序
lqw.orderByAsc(GearWarehouse::getSortNo);
return lqw;
}
/**
* 新增仓库/库区/库位自关联
*/
@Override
public Boolean insertByBo(GearWarehouseBo bo) {
GearWarehouse add = BeanUtil.toBean(bo, GearWarehouse.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setWarehouseId(add.getWarehouseId());
}
return flag;
}
/**
* 修改仓库/库区/库位自关联
*/
@Override
public Boolean updateByBo(GearWarehouseBo bo) {
GearWarehouse update = BeanUtil.toBean(bo, GearWarehouse.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(GearWarehouse entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除仓库/库区/库位自关联
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -213,8 +213,8 @@ public class OaExpressServiceImpl implements IOaExpressService {
} }
if (expressType.equals("YD") && oaExpressVo.getStatus() == 1L) { if (expressType.equals("YD") && oaExpressVo.getStatus() == 1L) {
// 韵达快递轨迹查询 // 韵达快递轨迹查询
String result = com.gear.oa.utils.YdRouteQueryUtil.queryRoute(oaExpressVo.getExpressCode()); String result =YdRouteQueryUtil.queryRoute(oaExpressVo.getExpressCode());
OaExpressVo ydVo = YdRouteQueryUtil.parseData(result); OaExpressVo ydVo =YdRouteQueryUtil.parseData(result);
if (ydVo != null) { if (ydVo != null) {
oaExpressVo.setLastUpdateTime(ydVo.getLastUpdateTime()); oaExpressVo.setLastUpdateTime(ydVo.getLastUpdateTime());
oaExpressVo.setLastStatus(ydVo.getLastStatus()); oaExpressVo.setLastStatus(ydVo.getLastStatus());
@@ -222,7 +222,7 @@ public class OaExpressServiceImpl implements IOaExpressService {
} }
if (expressType.equals("YT") && oaExpressVo.getStatus() == 1L) { if (expressType.equals("YT") && oaExpressVo.getStatus() == 1L) {
// 圆通快递轨迹查询 // 圆通快递轨迹查询
String result = com.gear.oa.utils.YtRouteQueryUtil.queryRoute(oaExpressVo.getExpressCode()); String result = YtRouteQueryUtil.queryRoute(oaExpressVo.getExpressCode());
OaExpressVo ytVo = YtRouteQueryUtil.parseData(result); OaExpressVo ytVo = YtRouteQueryUtil.parseData(result);
if (ytVo != null) { if (ytVo != null) {
oaExpressVo.setLastUpdateTime(ytVo.getLastUpdateTime()); oaExpressVo.setLastUpdateTime(ytVo.getLastUpdateTime());

View File

@@ -0,0 +1,107 @@
<?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.gear.oa.mapper.GearStockIoDetailMapper">
<resultMap type="com.gear.oa.domain.GearStockIoDetail" id="GearStockIoDetailResult">
<result property="detailId" column="detail_id"/>
<result property="stockIoId" column="stock_io_id"/>
<result property="warehouseId" column="warehouse_id"/>
<result property="itemType" column="item_type"/>
<result property="itemId" column="item_id"/>
<result property="quantity" column="quantity"/>
<result property="unit" column="unit"/>
<result property="batchNo" column="batch_no"/>
<result property="remark" column="remark"/>
<result property="delFlag" column="del_flag"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
<result property="fromWarehouseId" column="from_warehouse_id"/>
<result property="recordType" column="record_type"/>
</resultMap>
<!-- 联查库区/库位名称的明细列表SQL直接返回Map -->
<select id="selectDetailWithWarehouseName" resultType="map">
SELECT
d.detail_id,
d.stock_io_id,
d.warehouse_id,
d.item_type,
d.item_id,
d.quantity,
d.unit,
d.batch_no,
d.remark,
d.del_flag,
d.create_time,
d.create_by,
d.update_time,
d.update_by,
d.from_warehouse_id,
d.record_type,
w1.warehouse_name AS warehouseName,
w2.warehouse_name AS fromWarehouseName
FROM gear_stock_io_detail d
LEFT JOIN gear_warehouse w1 ON d.warehouse_id = w1.warehouse_id and w1.del_flag = 0
LEFT JOIN gear_warehouse w2 ON d.from_warehouse_id = w2.warehouse_id and w2.del_flag = 0
<where>
<if test="stockIoId != null">d.stock_io_id = #{stockIoId}</if>
<!-- 其他条件可补充 -->
</where>
</select>
<!-- 分页联查库区/库位名称的明细列表SQL支持Wrapper动态条件返回Page<GearStockIoDetailVo> -->
<select id="selectVoPagePlus" resultType="com.gear.oa.domain.vo.GearStockIoDetailVo">
SELECT
d.detail_id,
d.stock_io_id,
d.warehouse_id,
d.item_type,
d.item_id,
d.quantity,
d.unit,
d.batch_no,
d.remark,
d.del_flag,
d.create_time,
d.create_by,
d.update_time,
d.update_by,
d.from_warehouse_id,
d.record_type,
w1.warehouse_name AS warehouseName,
w2.warehouse_name AS fromWarehouseName
FROM gear_stock_io_detail d
LEFT JOIN gear_warehouse w1 ON d.warehouse_id = w1.warehouse_id and w1.del_flag = 0
LEFT JOIN gear_warehouse w2 ON d.from_warehouse_id = w2.warehouse_id and w2.del_flag = 0
${ew.customSqlSegment}
</select>
<select id="batchQuery" resultType="com.gear.oa.domain.vo.GearStockIoDetailVo">
SELECT
d.detail_id,
d.stock_io_id,
d.warehouse_id,
d.item_type,
d.item_id,
d.quantity,
d.unit,
d.batch_no,
d.remark,
d.del_flag,
d.create_time,
d.create_by,
d.update_time,
d.update_by,
d.from_warehouse_id,
d.record_type,
io.stock_io_code AS stockIoCode,
io.io_type AS ioType,
io.biz_type AS bizType
FROM gear_stock_io_detail d
LEFT JOIN gear_stock_io io ON d.stock_io_id = io.stock_io_id and io.del_flag = 0
WHERE d.batch_no = #{batchNo}
</select>
</mapper>

View File

@@ -0,0 +1,23 @@
<?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.gear.oa.mapper.GearStockIoMapper">
<resultMap type="com.gear.oa.domain.GearStockIo" id="GearStockIoResult">
<result property="stockIoId" column="stock_io_id"/>
<result property="stockIoCode" column="stock_io_code"/>
<result property="ioType" column="io_type"/>
<result property="bizType" column="biz_type"/>
<result property="status" column="status"/>
<result property="parentId" column="parent_id"/>
<result property="remark" column="remark"/>
<result property="delFlag" column="del_flag"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,33 @@
<?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.gear.oa.mapper.GearStockLogMapper">
<resultMap type="com.gear.oa.domain.GearStockLog" id="GearStockLogResult">
<result property="id" column="id"/>
<result property="warehouseId" column="warehouse_id"/>
<result property="itemId" column="item_id"/>
<result property="itemType" column="item_type"/>
<result property="changeQty" column="change_qty"/>
<result property="afterQty" column="after_qty"/>
<result property="changeType" column="change_type"/>
<result property="changeTime" column="change_time"/>
<result property="batchNo" column="batch_no"/>
<result property="remark" column="remark"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
<result property="delFlag" column="del_flag"/>
</resultMap>
<select id="selectVoPagePlus" resultType="com.gear.oa.domain.vo.GearStockLogVo">
select sl.*, w.warehouse_name
from gear_stock_log sl
left join gear_warehouse w on sl.warehouse_id = w.warehouse_id and w.del_flag = 0
${ew.customSqlSegment}
</select>
</mapper>

View File

@@ -0,0 +1,41 @@
<?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.gear.oa.mapper.GearStockMapper">
<resultMap type="com.gear.oa.domain.GearStock" id="GearStockResult">
<result property="stockId" column="stock_id"/>
<result property="warehouseId" column="warehouse_id"/>
<result property="itemType" column="item_type"/>
<result property="itemId" column="item_id"/>
<result property="quantity" column="quantity"/>
<result property="unit" column="unit"/>
<result property="batchNo" column="batch_no"/>
<result property="remark" column="remark"/>
<result property="delFlag" column="del_flag"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
</resultMap>
<select id="getStockByItemId" resultType="java.math.BigDecimal">
select sum(quantity) as quantity from gear_stock
where item_id = #{rawMaterialId} and item_type = 'raw_material' and del_flag = 0
</select>
<!-- 分页联查物品名称和编码支持Wrapper动态条件返回Page<GearStockVo> -->
<select id="selectVoPagePlus" resultType="com.gear.oa.domain.vo.GearStockVo">
SELECT
s.*,
w.warehouse_name,
p.product_name AS itemName,
p.product_code AS itemCode
FROM gear_stock s
LEFT JOIN gear_product p ON s.item_type = 'product' AND s.item_id = p.product_id and p.del_flag = 0
left join gear_warehouse w on s.warehouse_id = w.warehouse_id and w.del_flag = 0
${ew.customSqlSegment}
</select>
</mapper>

View File

@@ -0,0 +1,24 @@
<?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.gear.oa.mapper.GearWarehouseMapper">
<resultMap type="com.gear.oa.domain.GearWarehouse" id="GearWarehouseResult">
<result property="warehouseId" column="warehouse_id"/>
<result property="parentId" column="parent_id"/>
<result property="warehouseCode" column="warehouse_code"/>
<result property="warehouseName" column="warehouse_name"/>
<result property="warehouseType" column="warehouse_type"/>
<result property="sortNo" column="sort_no"/>
<result property="isEnabled" column="is_enabled"/>
<result property="delFlag" column="del_flag"/>
<result property="remark" column="remark"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
</resultMap>
</mapper>