| | |
| | | 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 |
| | | LocationID int |
| | | WarehouseID int |
| | | Products []*ProductInfo |
| | | OperationID int |
| | | SourceNumber string |
| | | SaleDetailsNumber string |
| | | } |
| | | |
| | | type ProductInfo struct { |
| | |
| | | } |
| | | 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{ |
| | |
| | | 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: 0, |
| | | CompanyName: "", |
| | | Comment: "crm发货申请", |
| | | LogisticCompanyId: "", |
| | | LogisticCompany: models.LogisticCompany{}, |
| | | WaybillNumber: "", |
| | | Weight: decimal.Decimal{}, |
| | | LogisticWeight: decimal.Decimal{}, |
| | | Source: "crm", |
| | | Details: details, |
| | | BaseOperationType: constvar.BaseOperationTypeOutgoing, |
| | | LocationID: output.LocationID, |
| | | 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, |
| | | } |
| | | operations = append(operations, operation) |
| | | autoCode = models.GetAutoCode(maxAutoIncr, codeStandard) |
| | |
| | | |
| | | 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(operation.LocationID)+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"), |
| | | }); err != nil { |
| | | return err |
| | | } |
| | | } |
| | | } else { |
| | | if roleCategory, cok := mapLocationRoleCategory[strconv.Itoa(operation.LocationID)+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"), |
| | | }); err != nil { |
| | | return err |
| | | } |
| | | } |
| | | } else { |
| | | if locAmount, aok := mapLocAmount[strconv.Itoa(operation.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: operation.LocationID, |
| | | ProductCategoryID: v.Product.CategoryId, |
| | | ProductId: v.ProductId, |
| | | Amount: v.Amount, |
| | | CreateDate: time.Now().Format("2006-01-02 15:04:05"), |
| | | }); 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, |
| | | }).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 := 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: internalInputDetails, |
| | | LocationID: internalInputDetails[0].ToLocationID, |
| | | OperationTypeName: "库存调拨入库", |
| | | } |
| | | 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: outputDetails[0].FromLocationID, |
| | | OperationTypeName: "库存调拨出库", |
| | | IsInternalOutput: true, |
| | | } |
| | | 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 locAmount, aok := mapLocAmount[strconv.Itoa(v.ToLocationID)+v.ProductId]; aok { |
| | | if locAmount.Amount.Equal(v.Amount) { |
| | | continue |
| | | } |
| | | if v.Amount.GreaterThan(locAmount.Amount) { |
| | | inputDetails = append(inputDetails, &models.OperationDetails{ |
| | | ProductId: v.ProductId, |
| | | Amount: v.Amount.Sub(locAmount.Amount), |
| | | FromLocationID: v.FromLocationID, |
| | | ToLocationID: v.ToLocationID, |
| | | 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: locAmount.Amount.Sub(v.Amount), |
| | | FromLocationID: v.ToLocationID, |
| | | 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 { |
| | | 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: "库存调整入库", |
| | | } |
| | | 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: "库存调整出库", |
| | | } |
| | | 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) { |
| | | oT, err := models.NewOperationTypeSearch().SetID(uint(operation.OperationTypeId)).First() |
| | | if err != nil { |
| | | return 0, err |
| | | } |
| | | targetOT, err := models.NewOperationTypeSearch().SetBaseOperationType(baseOT).SetWarehouseId(oT.WarehouseId).First() |
| | | if err != nil { |
| | | return 0, err |
| | | } |
| | | return targetOT.Id, nil |
| | | } |