Dockerfile

This commit is contained in:
2026-03-18 10:18:03 +08:00
parent 5c5dbc7420
commit b65f3439f3
189 changed files with 19027 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
// 实物库存批次DAO层
// 职责CRUD、IncrementAvailableQty原子操作(防并发超卖)、SumAvailableQtyByLocation聚合汇总
// 紧密耦合service.PrivateStock、service.Capacity(容量计算入口)
// 注意IncrementAvailableQty使用$inc+$gte条件防止并发导致库存变负
package dao
import (
"assets/consts/public"
"assets/consts/stock"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var PrivateStock = new(privateStock)
type privateStock struct {
}
// Insert 插入私域库存
func (d *privateStock) Insert(ctx context.Context, req *dto.CreatePrivateStockReq) (ids []interface{}, err error) {
var result *entity.PrivateStock
if err = utils.Struct(req, &result); err != nil {
return
}
// 设置StockType如果未指定默认为PrivateStock类型
if !g.IsEmpty(req.StockType) {
result.StockType = req.StockType
} else {
result.StockType = stock.StockLocationTypePrivateStock
}
// 获取仓库信息(非必填)
if req.WarehouseId != nil && !req.WarehouseId.IsZero() {
warehouse, err := Warehouse.GetOne(ctx, &dto.GetWarehouseReq{Id: req.WarehouseId})
if err != nil {
return nil, err
}
result.WarehouseID = req.WarehouseId
result.WarehouseCode = warehouse.WarehouseCode
result.WarehouseName = warehouse.WarehouseName
}
// 如果有库区信息
if req.ZoneId != nil && !req.ZoneId.IsZero() {
zone, err := Zone.GetOne(ctx, &dto.GetZoneReq{Id: req.ZoneId})
if err != nil {
return nil, err
}
result.ZoneID = req.ZoneId
result.ZoneCode = zone.ZoneCode
result.ZoneName = zone.ZoneName
result.ZoneType = zone.ZoneType
}
// 如果有库位信息
if req.LocationId != nil && !req.LocationId.IsZero() {
location, err := Location.GetOne(ctx, &dto.GetLocationReq{Id: req.LocationId})
if err != nil {
return nil, err
}
result.LocationID = req.LocationId
result.LocationCode = location.LocationCode
result.LocationName = location.LocationName
result.LocationType = location.LocationType
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PrivateStockCollection)
return
}
// Update 更新私域库存
func (d *privateStock) Update(ctx context.Context, req *dto.UpdatePrivateStockReq) (err error) {
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": bson.M{}}
if req.WarehouseId != nil && !req.WarehouseId.IsZero() {
warehouse, err := Warehouse.GetOne(ctx, &dto.GetWarehouseReq{Id: req.WarehouseId})
if err != nil {
return err
}
update["$set"].(bson.M)["warehouseId"] = req.WarehouseId
update["$set"].(bson.M)["warehouseCode"] = warehouse.WarehouseCode
update["$set"].(bson.M)["warehouseName"] = warehouse.WarehouseName
}
if req.ZoneId != nil && !req.ZoneId.IsZero() {
zone, err := Zone.GetOne(ctx, &dto.GetZoneReq{Id: req.ZoneId})
if err != nil {
return err
}
update["$set"].(bson.M)["zoneId"] = req.ZoneId
update["$set"].(bson.M)["zoneCode"] = zone.ZoneCode
update["$set"].(bson.M)["zoneName"] = zone.ZoneName
update["$set"].(bson.M)["zoneType"] = zone.ZoneType
}
if req.LocationId != nil && !req.LocationId.IsZero() {
location, err := Location.GetOne(ctx, &dto.GetLocationReq{Id: req.LocationId})
if err != nil {
return err
}
update["$set"].(bson.M)["locationId"] = req.LocationId
update["$set"].(bson.M)["locationCode"] = location.LocationCode
update["$set"].(bson.M)["locationName"] = location.LocationName
update["$set"].(bson.M)["locationType"] = location.LocationType
}
if req.PrivateSkuID != nil {
update["$set"].(bson.M)["privateSkuId"] = req.PrivateSkuID
}
if !g.IsEmpty(req.BatchNo) {
update["$set"].(bson.M)["batchNo"] = req.BatchNo
}
if req.BatchQty > 0 {
update["$set"].(bson.M)["batchQty"] = req.BatchQty
}
// AvailableQty已移除全量覆盖请使用IncrementAvailableQty方法进行增量更新
if req.BatchStatus != nil {
update["$set"].(bson.M)["batchStatus"] = req.BatchStatus
}
if req.StockStatus != nil {
update["$set"].(bson.M)["stockStatus"] = req.StockStatus
}
if req.SupplierID != nil {
update["$set"].(bson.M)["supplierId"] = req.SupplierID
}
if req.SupportsRecycle != nil {
update["$set"].(bson.M)["supportsRecycle"] = *req.SupportsRecycle
}
if req.ProductionDate != nil {
update["$set"].(bson.M)["productionDate"] = req.ProductionDate
}
if req.ExpiryDate != nil {
update["$set"].(bson.M)["expiryDate"] = req.ExpiryDate
}
if req.ExpiryWarningDate != nil {
update["$set"].(bson.M)["expiryWarningDate"] = req.ExpiryWarningDate
}
if !g.IsEmpty(req.PrivateCategoryPath) {
update["$set"].(bson.M)["privateCategoryPath"] = req.PrivateCategoryPath
}
if !g.IsEmpty(req.StockType) {
update["$set"].(bson.M)["stockType"] = req.StockType
}
_, err = mongo.DB().Update(ctx, filter, update, public.PrivateStockCollection)
return
}
// DeleteFake 软删除私域库存
func (d *privateStock) DeleteFake(ctx context.Context, req *dto.DeletePrivateStockReq) error {
filter := bson.M{"_id": req.Id}
_, err := mongo.DB().DeleteSoft(ctx, filter, public.PrivateStockCollection)
return err
}
// GetOne 根据ID查询私域库存
func (d *privateStock) GetOne(ctx context.Context, req *dto.GetPrivateStockReq) (res *entity.PrivateStock, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.PrivateStockCollection)
return
}
// IncrementAvailableQty 增量更新可用数量(并发安全)
// deltaQty正数表示增加入库负数表示减少出库
// 减少时自动添加 availableQty >= |deltaQty| 条件,防止并发导致库存变负
func (d *privateStock) IncrementAvailableQty(ctx context.Context, id *bson.ObjectID, deltaQty int) (err error) {
filter := bson.M{"_id": id}
if deltaQty < 0 {
filter["availableQty"] = bson.M{"$gte": -deltaQty}
}
update := bson.M{
"$inc": bson.M{
"availableQty": deltaQty,
},
}
modifiedCount, err := mongo.DB().Update(ctx, filter, update, public.PrivateStockCollection)
if err != nil {
return
}
if deltaQty < 0 && modifiedCount == 0 {
err = gerror.Newf("库存不足,无法减少%d", -deltaQty)
}
return
}
// List 查询私域库存列表
func (d *privateStock) List(ctx context.Context, req *dto.ListPrivateStockReq) (res []entity.PrivateStock, total int64, err error) {
filter := bson.M{}
if req.WarehouseId != nil && !req.WarehouseId.IsZero() {
filter["warehouseId"] = req.WarehouseId
}
if req.ZoneId != nil && !req.ZoneId.IsZero() {
filter["zoneId"] = req.ZoneId
}
if req.LocationId != nil && !req.LocationId.IsZero() {
filter["locationId"] = req.LocationId
}
if req.PrivateSkuID != nil && !req.PrivateSkuID.IsZero() {
filter["privateSkuId"] = req.PrivateSkuID
}
if req.BatchStatus != nil {
filter["batchStatus"] = req.BatchStatus
}
if req.StockStatus != nil {
filter["stockStatus"] = req.StockStatus
}
if req.SupplierID != nil && !req.SupplierID.IsZero() {
filter["supplierId"] = req.SupplierID
}
if !g.IsEmpty(req.PrivateCategoryPath) {
filter["privateCategoryPath"] = req.PrivateCategoryPath
}
if !g.IsEmpty(req.StockType) {
filter["stockType"] = req.StockType
}
// 默认排序
if len(req.OrderBy) == 0 {
req.OrderBy = []beans.OrderBy{
{Field: "createdAt", Order: beans.Desc},
}
}
total, err = mongo.DB().Find(ctx, filter, &res, public.PrivateStockCollection, req.Page, req.OrderBy)
return
}
// SumAvailableQtyByLocation 按库位ID汇总所有库存的可用数量使用MongoDB聚合管道
func (d *privateStock) SumAvailableQtyByLocation(ctx context.Context, locationId *bson.ObjectID) (totalQty int, err error) {
pipeline := bson.A{
bson.M{"$match": bson.M{
"locationId": locationId,
"isDeleted": false,
}},
bson.M{"$group": bson.M{
"_id": nil,
"totalQty": bson.M{"$sum": "$availableQty"},
}},
}
coll := mongo.GetDB().Collection(public.PrivateStockCollection)
cursor, err := coll.Aggregate(ctx, pipeline)
if err != nil {
return
}
defer cursor.Close(ctx)
var results []struct {
TotalQty int `bson:"totalQty"`
}
if err = cursor.All(ctx, &results); err != nil {
return
}
if len(results) > 0 {
totalQty = results[0].TotalQty
}
return
}