Dockerfile
This commit is contained in:
201
service/procurement/purchase_inbound_service.go
Normal file
201
service/procurement/purchase_inbound_service.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"assets/consts/public"
|
||||
daoAsset "assets/dao/asset"
|
||||
daoProcurement "assets/dao/procurement"
|
||||
daoStock "assets/dao/stock"
|
||||
dtoProcurement "assets/model/dto/procurement"
|
||||
dtoStock "assets/model/dto/stock"
|
||||
serviceStock "assets/service/stock"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type purchaseInbound struct{}
|
||||
|
||||
var PurchaseInbound = new(purchaseInbound)
|
||||
|
||||
// Create 创建采购入库
|
||||
func (s *purchaseInbound) Create(ctx context.Context, req *dtoProcurement.CreatePurchaseInboundReq) (res *dtoProcurement.CreatePurchaseInboundRes, err error) {
|
||||
// 1. 查询采购订单明细
|
||||
orderItem, err := daoProcurement.PurchaseOrderItem.GetOne(ctx, req.OrderItemId)
|
||||
if err != nil {
|
||||
return nil, errors.New("采购订单明细不存在")
|
||||
}
|
||||
if g.IsEmpty(orderItem) {
|
||||
return nil, errors.New("采购订单明细不存在")
|
||||
}
|
||||
|
||||
// 2. 校验+更新入库数量(原子操作,防止并发竞态)
|
||||
// 使用$inc + $expr条件:inboundQty + delta <= passQuantity
|
||||
if err = daoProcurement.PurchaseOrderItem.IncrementInboundQty(ctx, req.OrderItemId, req.InboundQty); err != nil {
|
||||
return nil, fmt.Errorf("入库数量校验失败: %v", err)
|
||||
}
|
||||
|
||||
// 3. 生成入库单号和批次号
|
||||
inboundNo, err := s.generateIncrSequence(ctx, public.StockInboundNoKeyPrefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成入库单号失败: %v", err)
|
||||
}
|
||||
batchNo, err := s.generateIncrSequence(ctx, public.StockBatchNoKeyPrefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成批次号失败: %v", err)
|
||||
}
|
||||
|
||||
// 4. 查询关联信息
|
||||
warehouseName, zoneName, locationName := s.getStorageNames(ctx, req.WarehouseId, req.ZoneId, req.LocationId)
|
||||
privateSkuName, privateCategoryPath := s.getPrivateSkuInfo(ctx, req.PrivateSkuId, req.PrivateCategoryId)
|
||||
|
||||
// 5. 创建入库记录
|
||||
inboundReq := &dtoProcurement.CreatePurchaseInboundReq{
|
||||
OrderItemId: req.OrderItemId,
|
||||
InboundQty: req.InboundQty,
|
||||
WarehouseId: req.WarehouseId,
|
||||
ZoneId: req.ZoneId,
|
||||
LocationId: req.LocationId,
|
||||
PrivateSkuId: req.PrivateSkuId,
|
||||
PrivateCategoryId: req.PrivateCategoryId,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
ids, err := daoProcurement.PurchaseInbound.Insert(ctx, inboundReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建入库记录失败: %v", err)
|
||||
}
|
||||
|
||||
inboundId := ids[0].(bson.ObjectID)
|
||||
|
||||
// 6. 更新入库记录的关联信息
|
||||
err = s.updateInboundDetails(ctx, &inboundId, orderItem.OrderId, inboundNo, batchNo,
|
||||
warehouseName, zoneName, locationName, privateSkuName, privateCategoryPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("更新入库记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 7. 创建PrivateStock批次记录
|
||||
privateStockReq := &dtoStock.CreatePrivateStockReq{
|
||||
PrivateSkuID: req.PrivateSkuId,
|
||||
BatchNo: batchNo,
|
||||
BatchQty: req.InboundQty,
|
||||
AvailableQty: req.InboundQty,
|
||||
WarehouseId: req.WarehouseId,
|
||||
ZoneId: req.ZoneId,
|
||||
LocationId: req.LocationId,
|
||||
PrivateCategoryPath: privateCategoryPath,
|
||||
}
|
||||
privateStockIds, err := daoStock.PrivateStock.Insert(ctx, privateStockReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建库存批次失败: %v", err)
|
||||
}
|
||||
|
||||
privateStockId := privateStockIds[0].(bson.ObjectID)
|
||||
|
||||
// 8. 触发库位容量更新(异步,失败不影响入库)
|
||||
if req.LocationId != nil && !req.LocationId.IsZero() {
|
||||
if capErr := serviceStock.Capacity.UpdateLocationCapacity(ctx, req.LocationId); capErr != nil {
|
||||
g.Log().Warningf(ctx, "更新库位容量失败(不影响入库): %v", capErr)
|
||||
}
|
||||
}
|
||||
|
||||
// 9. 更新入库记录关联的库存ID
|
||||
err = s.updateInboundPrivateStockId(ctx, &inboundId, &privateStockId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("更新入库记录库存ID失败: %v", err)
|
||||
}
|
||||
|
||||
// 10. 入库数量已在步骤2原子更新,无需重复操作
|
||||
|
||||
return &dtoProcurement.CreatePurchaseInboundRes{
|
||||
Id: &inboundId,
|
||||
InboundNo: inboundNo,
|
||||
BatchNo: batchNo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetOne 获取入库详情
|
||||
func (s *purchaseInbound) GetOne(ctx context.Context, req *dtoProcurement.GetPurchaseInboundReq) (res *dtoProcurement.GetPurchaseInboundRes, err error) {
|
||||
one, err := daoProcurement.PurchaseInbound.GetOne(ctx, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = utils.Struct(one, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取入库列表
|
||||
func (s *purchaseInbound) List(ctx context.Context, req *dtoProcurement.ListPurchaseInboundReq) (res *dtoProcurement.ListPurchaseInboundRes, err error) {
|
||||
list, total, err := daoProcurement.PurchaseInbound.List(ctx, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = &dtoProcurement.ListPurchaseInboundRes{
|
||||
Total: total,
|
||||
}
|
||||
err = utils.Struct(list, &res.List)
|
||||
return
|
||||
}
|
||||
|
||||
// generateInboundNo 生成入库单号(Redis自增,按天重置)
|
||||
func (s *purchaseInbound) generateIncrSequence(ctx context.Context, keyPrefix string) (string, error) {
|
||||
seq, err := utils.IncrSequence(ctx, keyPrefix, 6, "-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
// getStorageNames 获取仓储名称
|
||||
func (s *purchaseInbound) getStorageNames(ctx context.Context, warehouseId, zoneId, locationId *bson.ObjectID) (warehouseName, zoneName, locationName string) {
|
||||
if !g.IsEmpty(warehouseId) {
|
||||
warehouse, _ := daoStock.Warehouse.GetOne(ctx, &dtoStock.GetWarehouseReq{Id: warehouseId})
|
||||
if !g.IsEmpty(warehouse) {
|
||||
warehouseName = warehouse.WarehouseName
|
||||
}
|
||||
}
|
||||
if !g.IsEmpty(zoneId) {
|
||||
zone, _ := daoStock.Zone.GetOne(ctx, &dtoStock.GetZoneReq{Id: zoneId})
|
||||
if !g.IsEmpty(zone) {
|
||||
zoneName = zone.ZoneName
|
||||
}
|
||||
}
|
||||
if !g.IsEmpty(locationId) {
|
||||
location, _ := daoStock.Location.GetOne(ctx, &dtoStock.GetLocationReq{Id: locationId})
|
||||
if !g.IsEmpty(location) {
|
||||
locationName = location.LocationName
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getPrivateSkuInfo 获取私域SKU信息
|
||||
func (s *purchaseInbound) getPrivateSkuInfo(ctx context.Context, privateSkuId, privateCategoryId *bson.ObjectID) (privateSkuName, privateCategoryPath string) {
|
||||
if !g.IsEmpty(privateSkuId) {
|
||||
privateSku, err := daoAsset.PrivateSku.GetOne(ctx, privateSkuId)
|
||||
if err == nil && !g.IsEmpty(privateSku) {
|
||||
privateSkuName = privateSku.SkuName
|
||||
}
|
||||
}
|
||||
if !g.IsEmpty(privateCategoryId) {
|
||||
privateCategory, err := daoAsset.PrivateCategory.GetOne(ctx, privateCategoryId)
|
||||
if err == nil && !g.IsEmpty(privateCategory) {
|
||||
privateCategoryPath = privateCategory.Path
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// updateInboundDetails 更新入库记录详细信息
|
||||
func (s *purchaseInbound) updateInboundDetails(ctx context.Context, inboundId, orderId *bson.ObjectID, inboundNo, batchNo, warehouseName, zoneName, locationName, privateSkuName, privateCategoryPath string) (err error) {
|
||||
return daoProcurement.PurchaseInbound.UpdateDetails(ctx, inboundId, orderId, inboundNo, batchNo,
|
||||
warehouseName, zoneName, locationName, privateSkuName, privateCategoryPath)
|
||||
}
|
||||
|
||||
// updateInboundPrivateStockId 更新入库记录关联的库存ID
|
||||
func (s *purchaseInbound) updateInboundPrivateStockId(ctx context.Context, inboundId, privateStockId *bson.ObjectID) (err error) {
|
||||
return daoProcurement.PurchaseInbound.UpdatePrivateStockId(ctx, inboundId, privateStockId)
|
||||
}
|
||||
Reference in New Issue
Block a user