package service import ( "errors" "fmt" "github.com/gin-gonic/gin" "github.com/mitchellh/mapstructure" "github.com/shopspring/decimal" "gorm.io/gorm" "strconv" "time" "wms/constvar" "wms/middleware" "wms/models" ) type OutputInfo struct { LocationID int WarehouseID int Products []*ProductInfo OperationID int SourceNumber string SaleDetailsNumber string } type ProductInfo struct { ProductID string Amount decimal.Decimal } func AddOutputOperations(outputList []*OutputInfo) (err error) { //查找operationID warehouseIds := make([]int, 0) for _, output := range outputList { warehouseIds = append(warehouseIds, output.WarehouseID) } opTypes, err := models.NewOperationTypeSearch().SetWarehouseIds(warehouseIds).SetBaseOperationType(constvar.BaseOperationTypeOutgoing).FindNotTotal() if err != nil { return err } autoCode, codeStandard, maxAutoIncr, err := GetAutoCode(constvar.CodeStandardType_Outgoing) if err != nil { return err } opTypeMap := make(map[int]*models.OperationType, len(opTypes)) for _, opType := range opTypes { opTypeMap[opType.WarehouseId] = opType } operations := make([]*models.Operation, 0, len(outputList)) for _, output := range outputList { location, err := models.NewLocationSearch().SetType(int(constvar.LocationTypeCustomer)).First() if err != nil { return err } details := make([]*models.OperationDetails, 0, len(output.Products)) for _, product := range output.Products { details = append(details, &models.OperationDetails{ OperationID: 0, ProductId: product.ProductID, Amount: product.Amount, FromLocationID: output.LocationID, ToLocationID: location.Id, }) } if opTypeMap[output.WarehouseID] == nil { continue } operation := &models.Operation{ Id: 0, Number: autoCode, SourceNumber: output.SourceNumber, OperationTypeId: opTypeMap[output.WarehouseID].Id, OperationTypeName: opTypeMap[output.WarehouseID].Name, Status: constvar.OperationStatus_Ready, OperationDate: time.Now().Format("2006-01-02 15:04:05"), ContacterID: 0, ContacterName: "", CompanyID: "", CompanyName: "", Comment: "crm发货申请", LogisticCompanyId: "", LogisticCompany: models.LogisticCompany{}, WaybillNumber: "", Weight: decimal.Decimal{}, LogisticWeight: decimal.Decimal{}, Source: "crm", Details: details, BaseOperationType: constvar.BaseOperationTypeOutgoing, LocationID: output.LocationID, OperationSource: constvar.OperationSourceSaleDelivery, SalesDetailsNumber: output.SaleDetailsNumber, WarehouseId: output.WarehouseID, } operations = append(operations, operation) autoCode = models.GetAutoCode(maxAutoIncr, codeStandard) maxAutoIncr++ } return models.NewOperationSearch().CreateBatch(operations) } // FinishOperationInput 完成入库 func FinishOperationInput(c *gin.Context, tx *gorm.DB, operation *models.Operation, listDetails []*models.OperationDetails, mapLocAmount map[string]*models.LocationProductAmount) (err error) { userInfo := middleware.GetUserInfo(c) locationRoleList, err := models.NewLocationProductSearch().Find() if err != nil { return errors.New("获取上架规则信息失败") } var mapLocationRoleProduct, mapLocationRoleCategory map[string]*models.LocationProduct if len(locationRoleList) > 0 { mapLocationRoleProduct = make(map[string]*models.LocationProduct) mapLocationRoleCategory = make(map[string]*models.LocationProduct) for _, v := range locationRoleList { if v.RuleType == constvar.RuleType_Product { mapLocationRoleProduct[strconv.Itoa(v.AreaId)+v.ProductId] = v } if v.RuleType == constvar.RuleType_ProductCategory { mapLocationRoleCategory[strconv.Itoa(v.AreaId)+strconv.Itoa(v.ProductCategoryID)] = v } } } var details []*models.OperationDetails for k, v := range listDetails { listDetails[k].Product.Amount = listDetails[k].Product.Amount.Add(v.Amount) if err := tx.Save(&listDetails[k].Product).Error; err != nil { return err } if roleProduct, ok := mapLocationRoleProduct[strconv.Itoa(v.ToLocationID)+v.ProductId]; ok { detail := &models.OperationDetails{ ProductId: v.ProductId, Amount: v.Amount, FromLocationID: roleProduct.AreaId, ToLocationID: roleProduct.LocationId, } details = append(details, detail) if locAmount, aok := mapLocAmount[strconv.Itoa(roleProduct.LocationId)+v.ProductId]; aok { locAmount.Amount = locAmount.Amount.Add(v.Amount) if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(int(locAmount.ID)).Save(locAmount); err != nil { return err } } else { if err := models.NewLocationProductAmountSearch().SetOrm(tx).Create(&models.LocationProductAmount{ LocationId: roleProduct.LocationId, ProductCategoryID: v.Product.CategoryId, ProductId: v.ProductId, Amount: v.Amount, CreateDate: time.Now().Format("2006-01-02 15:04:05"), WarehouseId: operation.WarehouseId, }); err != nil { return err } } } else { if roleCategory, cok := mapLocationRoleCategory[strconv.Itoa(v.ToLocationID)+strconv.Itoa(v.Product.CategoryId)]; cok { detail := &models.OperationDetails{ ProductId: v.ProductId, Amount: v.Amount, FromLocationID: roleCategory.AreaId, ToLocationID: roleCategory.LocationId, } details = append(details, detail) if locAmount, aok := mapLocAmount[strconv.Itoa(roleCategory.LocationId)+v.ProductId]; aok { locAmount.Amount = locAmount.Amount.Add(v.Amount) if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(int(locAmount.ID)).Save(locAmount); err != nil { return err } } else { if err := models.NewLocationProductAmountSearch().SetOrm(tx).Create(&models.LocationProductAmount{ LocationId: roleCategory.LocationId, ProductCategoryID: v.Product.CategoryId, ProductId: v.ProductId, Amount: v.Amount, CreateDate: time.Now().Format("2006-01-02 15:04:05"), WarehouseId: operation.WarehouseId, }); err != nil { return err } } } else { if locAmount, aok := mapLocAmount[strconv.Itoa(v.ToLocationID)+v.ProductId]; aok { locAmount.Amount = locAmount.Amount.Add(v.Amount) locAmount.ID = uint(locAmount.Id) if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(int(locAmount.ID)).Save(locAmount); err != nil { return err } } else { if err := models.NewLocationProductAmountSearch().SetOrm(tx).Create(&models.LocationProductAmount{ LocationId: v.ToLocationID, ProductCategoryID: v.Product.CategoryId, ProductId: v.ProductId, Amount: v.Amount, CreateDate: time.Now().Format("2006-01-02 15:04:05"), WarehouseId: operation.WarehouseId, }); err != nil { return err } } } } } if len(details) > 0 { if err := tx.Create(&models.Operation{ Number: operation.Number, SourceNumber: operation.SourceNumber, OperationTypeId: 0, OperationTypeName: operation.OperationTypeName, Status: constvar.OperationStatus_Finish, OperationDate: operation.OperationDate, ContacterID: operation.ContacterID, ContacterName: operation.ContacterName, CompanyID: operation.CompanyID, CompanyName: operation.CompanyName, Comment: operation.Comment, BaseOperationType: constvar.BaseOperationTypeInternal, Details: details, CheckedBy: userInfo.Username, WarehouseId: operation.WarehouseId, }).Error; err != nil { return err } } return nil } // FinishOperationOutput 完成出库或报废 func FinishOperationOutput(tx *gorm.DB, listDetails []*models.OperationDetails, mapLocAmount map[string]*models.LocationProductAmount, originOperation *models.Operation) (err error) { var internalInputDetails []*models.OperationDetails //内部调拨产生的出库验证后,生成入库单 for k, v := range listDetails { if v.Product.Amount.LessThan(v.Amount) { return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Product.Name, v.Product.Amount.String(), v.Amount.String())) } listDetails[k].Product.Amount = listDetails[k].Product.Amount.Sub(v.Amount) if err := models.NewMaterialSearch().SetOrm(tx).Save(&listDetails[k].Product); err != nil { return err } if locAmount, aok := mapLocAmount[strconv.Itoa(v.FromLocationID)+v.ProductId]; aok { if locAmount.Amount.LessThan(v.Amount) { return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Product.Name, locAmount.Amount.String(), v.Amount.String())) } locAmount.Amount = locAmount.Amount.Sub(v.Amount) if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(locAmount.Id).Save(locAmount); err != nil { return err } } else { return errors.New("当前仓库没有该产品,请先入库") } if v.IsInternalOutput { var inputDetail models.OperationDetails mapstructure.Decode(v, &inputDetail) inputDetail.Id = 0 inputDetail.OperationID = 0 internalInputDetails = append(internalInputDetails, &inputDetail) } } if len(internalInputDetails) > 0 { opTypeId, err := GetTargetOperationTypeIdByWarehouseId(originOperation.ToLocation.WarehouseId, constvar.BaseOperationTypeIncoming) if err != nil { return err } operation := &models.Operation{ OperationTypeId: opTypeId, Number: strconv.FormatInt(time.Now().Unix(), 10), Status: constvar.OperationStatus_Ready, OperationDate: time.Now().Format("2006-01-02 15:04:05"), Comment: "库存调拨入库", BaseOperationType: constvar.BaseOperationTypeIncoming, Details: internalInputDetails, LocationID: originOperation.LocationID, ToLocationID: originOperation.ToLocationID, OperationTypeName: "库存调拨入库", WarehouseId: originOperation.ToLocation.WarehouseId, } if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil { return err } } return nil } // FinishOperationInternal 验证内部调拨生成出库单 func FinishOperationInternal(tx *gorm.DB, listDetails []*models.OperationDetails, originOperation *models.Operation) (err error) { var outputDetails []*models.OperationDetails for _, v := range listDetails { outputDetails = append(outputDetails, &models.OperationDetails{ ProductId: v.ProductId, Amount: v.Amount, FromLocationID: v.FromLocationID, ToLocationID: v.ToLocationID, TotalGrossWeight: v.TotalGrossWeight, TotalNetWeight: v.TotalNetWeight, AuxiliaryAmount: v.AuxiliaryAmount, AuxiliaryUnit: v.AuxiliaryUnit, Remark: v.Remark, IsInternalOutput: true, Cost: v.Cost, SalePrice: v.SalePrice, }) } if len(outputDetails) > 0 { opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeOutgoing) if err != nil { return err } operation := &models.Operation{ OperationTypeId: opTypeId, Number: strconv.FormatInt(time.Now().Unix(), 10), Status: constvar.OperationStatus_Ready, OperationDate: time.Now().Format("2006-01-02 15:04:05"), Comment: "库存调拨出库", BaseOperationType: constvar.BaseOperationTypeOutgoing, Details: outputDetails, LocationID: originOperation.LocationID, ToLocationID: originOperation.ToLocationID, OperationTypeName: "库存调拨出库", IsInternalOutput: true, WarehouseId: originOperation.WarehouseId, } if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil { return err } } return nil } // FinishOperationAdjust 完成库存调整 // 验证后生成入库单或出库单(库存减少生成出库单,库存增加生成入库单) func FinishOperationAdjust(tx *gorm.DB, listDetails []*models.OperationDetails, mapLocAmount map[string]*models.LocationProductAmount, originOperation *models.Operation) (err error) { var inputDetails []*models.OperationDetails var outputDetails []*models.OperationDetails for _, v := range listDetails { if _, aok := mapLocAmount[strconv.Itoa(v.FromLocationID)+v.ProductId]; aok { if v.StockAmount.Equal(v.Amount) { //盘点数量和剩余数量一致 continue } if v.Amount.GreaterThan(v.StockAmount) { inputDetails = append(inputDetails, &models.OperationDetails{ ProductId: v.ProductId, Amount: v.Amount.Sub(v.StockAmount), FromLocationID: 0, ToLocationID: v.FromLocationID, TotalGrossWeight: v.TotalGrossWeight, TotalNetWeight: v.TotalNetWeight, AuxiliaryAmount: v.AuxiliaryAmount, AuxiliaryUnit: v.AuxiliaryUnit, Remark: v.Remark, Cost: v.Cost, SalePrice: v.SalePrice, }) } else { outputDetails = append(outputDetails, &models.OperationDetails{ ProductId: v.ProductId, Amount: v.StockAmount.Sub(v.Amount), FromLocationID: v.FromLocationID, ToLocationID: 0, TotalGrossWeight: v.TotalGrossWeight, TotalNetWeight: v.TotalNetWeight, AuxiliaryAmount: v.AuxiliaryAmount, AuxiliaryUnit: v.AuxiliaryUnit, Remark: v.Remark, Cost: v.Cost, SalePrice: v.SalePrice, }) } } else { inputDetails = append(inputDetails, &models.OperationDetails{ ProductId: v.ProductId, Amount: v.Amount, FromLocationID: v.FromLocationID, ToLocationID: v.ToLocationID, Remark: v.Remark, }) } } if len(inputDetails) > 0 { opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeIncoming) if err != nil { return err } operation := &models.Operation{ OperationTypeId: opTypeId, Number: strconv.FormatInt(time.Now().Unix(), 10), Status: constvar.OperationStatus_Ready, OperationDate: time.Now().Format("2006-01-02 15:04:05"), Comment: "库存调整入库", BaseOperationType: constvar.BaseOperationTypeIncoming, Details: inputDetails, LocationID: inputDetails[0].FromLocationID, OperationTypeName: "库存调整入库", WarehouseId: originOperation.WarehouseId, } if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil { return err } } if len(outputDetails) > 0 { opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeOutgoing) if err != nil { return err } operation := &models.Operation{ OperationTypeId: opTypeId, Number: strconv.FormatInt(time.Now().Unix(), 10), Status: constvar.OperationStatus_Ready, OperationDate: time.Now().Format("2006-01-02 15:04:05"), Comment: "库存调整出库", BaseOperationType: constvar.BaseOperationTypeOutgoing, Details: outputDetails, LocationID: outputDetails[0].FromLocationID, OperationTypeName: "库存调整出库", WarehouseId: originOperation.WarehouseId, } if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil { return err } } return nil } func GetTargetOperationTypeIdByOperation(operation *models.Operation, baseOT constvar.BaseOperationType) (operationTypeId int, err error) { targetOT, err := models.NewOperationTypeSearch().SetBaseOperationType(baseOT).SetWarehouseId(operation.WarehouseId).First() if err != nil { return 0, err } return targetOT.Id, nil } func GetTargetOperationTypeIdByWarehouseId(warehouseId int, baseOT constvar.BaseOperationType) (operationTypeId int, err error) { if warehouseId == 0 { return 0, errors.New("warehouseId miss") } targetOT, err := models.NewOperationTypeSearch().SetBaseOperationType(baseOT).SetWarehouseId(warehouseId).First() if err != nil { return 0, err } return targetOT.Id, nil }