package product_inventory import ( "context" "errors" "fmt" "github.com/shopspring/decimal" "gorm.io/gorm" "strconv" "strings" "time" "wms/constvar" "wms/models" "wms/pkg/logx" "wms/pkg/timex" "wms/service" ) type Server struct { UnimplementedProductInventoryServiceServer } type ProductAndLocationInfo struct { ProductId string `json:"productId"` Amount decimal.Decimal `json:"amount"` LocationId int `json:"locationId"` Number string `json:"number"` WaybillNumber string `json:"waybillNumber"` Name string `json:"name"` } func (s *Server) GetInventoryProductInfo(ctx context.Context, req *GetInventoryProductInfoRequest) (*GetInventoryProductInfoResponse, error) { if req.Number == "" { return nil, errors.New("参数不能为空") } //查询产品id var details []ProductAndLocationInfo var productIds []string resp := new(GetInventoryProductInfoResponse) err := models.NewOperationDetailsSearch().Orm.Model(&models.OperationDetails{}). Select("wms_operation_details.product_id,wms_operation_details.amount,wms_operation_details.from_location_id as location_id,"+ "wms_operation.number,wms_operation.waybill_number, logistic_company.name"). Joins("left join wms_operation on wms_operation.id = wms_operation_details.operation_id"). Joins("left join logistic_company on logistic_company.id = wms_operation.logistic_company_id"). Where("wms_operation.source_number = ?", req.Number).Find(&details).Error if err != nil { return nil, err } if len(details) == 0 { return resp, nil } var locationId int for _, detail := range details { productIds = append(productIds, detail.ProductId) locationId = detail.LocationId } //查询产品信息 materials, err := models.NewMaterialSearch().SetIDs(productIds).FindNotTotal() if err != nil { return nil, err } //查询位置信息 location, err := models.NewLocationSearch().SetID(locationId).First() if err != nil { return nil, err } //根据仓库缩写查询仓库 code := strings.Split(location.JointName, "/")[0] warehouse, err := models.NewWarehouseSearch().SetCode(code).First() if err != nil { return nil, err } //统计仓库下所有位置的产品在库数量 locations, err := models.NewLocationSearch().SetJointName(code).FindNotTotal() if err != nil { return nil, err } var locationIds []int for _, l := range locations { locationIds = append(locationIds, l.Id) } amounts, err := models.NewLocationProductAmountSearch().SetProductIds(productIds).SetLocationIds(locationIds).Find() if err != nil { return nil, err } var inventory []ProductAndLocationInfo for _, productAmount := range amounts { var in ProductAndLocationInfo in.ProductId = productAmount.ProductId in.Amount = productAmount.Amount inventory = append(inventory, in) } //统计可用数量 var canUse []ProductAndLocationInfo err = models.NewOperationDetailsSearch().Orm.Model(&models.OperationDetails{}). Select("wms_operation_details.product_id, wms_operation_details.amount"). Joins("left join wms_operation on wms_operation_details.operation_id = wms_operation.id"). Where("wms_operation_details.product_id in (?)", productIds). Where("wms_operation_details.from_location_id in (?)", locationIds).Where("wms_operation.status = ?", constvar.OperationStatus_Ready). Where("wms_operation.base_operation_type in (?)", []constvar.BaseOperationType{constvar.BaseOperationTypeOutgoing, constvar.BaseOperationTypeInternal, constvar.BaseOperationTypeDisuse}). Find(&canUse).Error if err != nil { return nil, err } products := make([]*ProductInfo, 0) for _, material := range materials { var p ProductInfo p.Number = material.ID p.Name = material.Name for _, detail := range details { if material.ID == detail.ProductId { p.OrderAmount = detail.Amount.String() p.Valorem = detail.Amount.Mul(material.SalePrice).String() p.Invoice = detail.Number p.Carrier = detail.Name p.Waybill = detail.WaybillNumber break } } p.Unit = material.Unit p.SalePrice = material.SalePrice.String() p.Warehouse = warehouse.Name at := decimal.NewFromInt(0) for _, info := range inventory { if material.ID == info.ProductId { at = at.Add(info.Amount) } } p.Amount = at.String() cu := decimal.NewFromInt(0) for _, info := range canUse { if material.ID == info.ProductId { cu = cu.Add(info.Amount) } } cu = at.Sub(cu) p.AvailableNumber = cu.String() products = append(products, &p) } resp.ProductList = products return resp, nil } func (s *Server) CreateOperation(ctx context.Context, req *CreateOperationRequest) (*CreateOperationResponse, error) { var operations []*models.Operation var operation models.Operation var details []*models.OperationDetails operation.SourceNumber = req.Number operation.OperationDate = timex.TimeToString2(time.Now()) operation.Number = strconv.FormatInt(time.Now().Unix(), 10) operation.Status = constvar.OperationStatus_Ready warehouse, err := models.NewWarehouseSearch().First() if err != nil { return nil, err } operationType, err := models.NewOperationTypeSearch().SetWarehouseId(warehouse.Id).SetBaseOperationType(constvar.BaseOperationTypeOutgoing).First() if err != nil { return nil, err } operation.OperationTypeName = operationType.Name operation.OperationTypeId = operationType.Id location, err := models.NewLocationSearch().SetID(warehouse.LocationId).First() if err != nil { return nil, err } first, err := models.NewLocationSearch().SetType(int(constvar.LocationTypeCustomer)).First() if err != nil { return nil, err } operation.LocationID = location.Id operation.BaseOperationType = constvar.BaseOperationTypeOutgoing operation.ReceiverName = req.Addressee operation.ReceiverPhone = req.Phone operation.ReceiverAddr = req.Address operation.Source = req.Source operation.OperationSource = constvar.OperationSource(req.OperationSource) operation.CompanyID = strconv.FormatInt(req.ClientId, 10) operation.CompanyName = req.ClientName if req.DeliverType == 1 { for _, product := range req.ProductList { var detail models.OperationDetails detail.ProductId = product.Id amount, _ := decimal.NewFromString(product.Amount) detail.Amount = amount detail.FromLocationID = location.Id detail.ToLocationID = first.Id details = append(details, &detail) } operation.Details = details operations = append(operations, &operation) } else { for _, product := range req.ProductList { newOperation := operation var detail models.OperationDetails detail.ProductId = product.Id amount, _ := decimal.NewFromString(product.Amount) detail.Amount = amount detail.FromLocationID = location.Id detail.ToLocationID = first.Id newOperation.Details = append(newOperation.Details, &detail) operations = append(operations, &newOperation) } } err = models.NewOperationSearch().CreateBatch(operations) resp := new(CreateOperationResponse) return resp, err } type InputAndOutputDetails struct { ProductId string `json:"productId"` Amount decimal.Decimal `json:"amount"` FromLocationId int `json:"fromLocationId"` ToLocationId int `json:"toLocationId"` Number string `json:"number"` WaybillNumber string `json:"waybillNumber"` Name string `json:"name"` BaseOperationType constvar.BaseOperationType `json:"baseOperationType"` Status constvar.OperationStatus `json:"status"` CreatedAt time.Time } func (s *Server) GetOrderInputAndOutputInfo(ctx context.Context, req *GetOrderInputAndOutputInfoRequest) (*GetOrderInputAndOutputInfoResponse, error) { if req.Number == "" { return nil, errors.New("参数不能为空") } var details []InputAndOutputDetails var productIds []string resp := new(GetOrderInputAndOutputInfoResponse) search := models.NewOperationDetailsSearch().Orm.Model(&models.OperationDetails{}). Select("wms_operation_details.product_id,wms_operation_details.amount,wms_operation_details.from_location_id,wms_operation_details.to_location_id,"+ "wms_operation.number,wms_operation.waybill_number, logistic_company.name, wms_operation.base_operation_type, wms_operation.status, wms_operation.created_at"). Joins("left join wms_operation on wms_operation.id = wms_operation_details.operation_id"). Joins("left join logistic_company on logistic_company.id = wms_operation.logistic_company_id"). Where("wms_operation.sales_details_number = ?", req.Number). Where("wms_operation.base_operation_type in ?", []constvar.BaseOperationType{ constvar.BaseOperationTypeIncoming, constvar.BaseOperationTypeOutgoing}). Where("wms_operation.status in ?", []constvar.OperationStatus{constvar.OperationStatus_Ready, constvar.OperationStatus_Finish}) err := search.Find(&details).Error if err != nil { return nil, err } if len(details) == 0 { return resp, nil } var locationIds []int productInputMap := make(map[string]decimal.Decimal) productOutputMap := make(map[string]decimal.Decimal) for _, detail := range details { productIds = append(productIds, detail.ProductId) if detail.BaseOperationType == constvar.BaseOperationTypeIncoming { productInputMap[detail.ProductId] = productInputMap[detail.ProductId].Add(detail.Amount) locationIds = append(locationIds, detail.ToLocationId) //入库位置 } if detail.BaseOperationType == constvar.BaseOperationTypeOutgoing { productOutputMap[detail.ProductId] = productOutputMap[detail.ProductId].Add(detail.Amount) } } //查询产品信息 materials, err := models.NewMaterialSearch().SetIDs(productIds).FindNotTotal() if err != nil { return nil, err } locationHouseMap, _, _, err := service.GetWarehouseByLocationIds(locationIds) if err != nil { return nil, err } materialsMap := service.MaterialMap(materials) inputList := make([]*InputAndOutputInfo, 0) outputList := make([]*InputAndOutputInfo, 0) for _, detail := range details { if materialsMap[detail.ProductId] == nil { continue } material := materialsMap[detail.ProductId] var info InputAndOutputInfo info.Number = material.ID info.Name = material.Name info.OrderAmount = detail.Amount.String() info.Valorem = detail.Amount.Mul(material.SalePrice).String() info.Invoice = detail.Number info.Carrier = detail.Name info.Waybill = detail.WaybillNumber info.Unit = material.Unit info.SalePrice = material.SalePrice.String() info.Amount = detail.Amount.String() info.CreateTime = detail.CreatedAt.Format("2006-01-02 15:04") if detail.Status == constvar.OperationStatus_Finish { //是否完成 info.Status = FinishStatus_Finish } else { info.Status = FinishStatus_Ready } if detail.BaseOperationType == constvar.BaseOperationTypeIncoming && detail.Status == constvar.OperationStatus_Finish { if locationHouseMap[detail.ToLocationId] != nil { info.Warehouse = locationHouseMap[detail.ToLocationId].Name //入库仓库名 info.LocationID = int64(detail.ToLocationId) info.WareHouseID = int64(locationHouseMap[detail.ToLocationId].Id) } inputList = append(inputList, &info) } else if detail.BaseOperationType == constvar.BaseOperationTypeOutgoing { if locationHouseMap[detail.FromLocationId] != nil { info.Warehouse = locationHouseMap[detail.FromLocationId].Name //发货仓库名 info.LocationID = int64(detail.FromLocationId) info.WareHouseID = int64(locationHouseMap[detail.FromLocationId].Id) } outputList = append(outputList, &info) } } resp.InputList = inputList resp.OutputList = outputList return resp, nil } type StoreInfo struct { Name string `json:"name"` //产品名称 Number string `json:"number"` //产品编号 StoreAmount decimal.Decimal `json:"storeAmount"` //订单入库数量 AvailableAmount decimal.Decimal `json:"availableAmount"` //剩余可用数量 } type OutputSimpleInfo struct { Number string `json:"number"` //产品编号 Amount decimal.Decimal `json:"amount"` //在库数量 Status int `json:"status"` //0就绪 1完成 } func (s *Server) OrderProductOutput(ctx context.Context, req *OrderProductOutputRequest) (resp *OrderProductOutputResponse, err error) { resp = new(OrderProductOutputResponse) if req.OrderNumber == "" || len(req.Products) == 0 { return nil, errors.New("参数缺失") } orderInputAndOutputInfoResponse, err := s.GetOrderInputAndOutputInfo(ctx, &GetOrderInputAndOutputInfoRequest{ Number: req.OrderNumber, }) if err != nil { logx.Errorf("OrderProductOutput GetOrderInputAndOutputInfo err:%v, req:%v", err, req) return nil, errors.New("获取出入库信息失败") } outputList := orderInputAndOutputInfoResponse.OutputList inputList := orderInputAndOutputInfoResponse.InputList inputProductMap := make(map[string]*StoreInfo) outputProductMap := make(map[string]*OutputSimpleInfo) inputLocationAmountMap := make(map[int64]map[string]decimal.Decimal) outputLocationAmountMap := make(map[int64]map[string]decimal.Decimal) for _, v := range outputList { if req.WarehouseId != 0 && v.WareHouseID != req.WarehouseId { continue } if outputProductMap[v.Number] == nil { simpleInfo := &OutputSimpleInfo{ Number: v.Number, } amount, _ := decimal.NewFromString(v.Amount) simpleInfo.Amount = amount outputProductMap[v.Number] = simpleInfo } else { amount, _ := decimal.NewFromString(v.Amount) outputProductMap[v.Number].Amount = outputProductMap[v.Number].Amount.Add(amount) } } for _, v := range inputList { if req.WarehouseId != 0 && v.WareHouseID != req.WarehouseId { continue } if inputProductMap[v.Number] == nil { storeInfo := &StoreInfo{ Number: v.Number, Name: v.Name, } storeAmount, _ := decimal.NewFromString(v.Amount) storeInfo.StoreAmount = storeAmount storeInfo.AvailableAmount = storeAmount inputProductMap[v.Number] = storeInfo } else { storeAmount, _ := decimal.NewFromString(v.Amount) inputProductMap[v.Number].StoreAmount = inputProductMap[v.Number].StoreAmount.Add(storeAmount) inputProductMap[v.Number].AvailableAmount = inputProductMap[v.Number].StoreAmount } } for number, inputInfo := range inputProductMap { outputInfo := outputProductMap[inputInfo.Number] if outputInfo != nil { inputProductMap[number].AvailableAmount = inputProductMap[number].AvailableAmount.Sub(outputInfo.Amount) //可用数量 = 入库完成数量 - 已发货数量 } } //校验可用数量是否足够 productNeedSendAmount := make(map[string]decimal.Decimal) for _, product := range req.Products { sendAmount, _ := decimal.NewFromString(product.Amount) productNeedSendAmount[product.Number] = sendAmount if inputProductMap[product.Number] == nil { return nil, fmt.Errorf("获取入库信息失败,产品编号:%v", product.Number) } if sendAmount.GreaterThan(inputProductMap[product.Number].AvailableAmount) { return nil, fmt.Errorf("产品可用数量不足以发货,产品编号:%v", product.Number) } } LocationIDWarehouseIDMap := make(map[int64]int64) for _, output := range outputList { if req.WarehouseId != 0 && output.WareHouseID != req.WarehouseId { continue } if outputLocationAmountMap[output.LocationID] == nil { outputLocationAmountMap[output.LocationID] = make(map[string]decimal.Decimal) } outputAmount, _ := decimal.NewFromString(output.Amount) outputLocationAmountMap[output.LocationID][output.Number] = outputAmount } for _, input := range inputList { if req.WarehouseId != 0 && input.WareHouseID != req.WarehouseId { continue } LocationIDWarehouseIDMap[input.LocationID] = input.WareHouseID if inputLocationAmountMap[input.LocationID] == nil { inputLocationAmountMap[input.LocationID] = make(map[string]decimal.Decimal) } storeAmount, _ := decimal.NewFromString(input.Amount) if outputLocationAmountMap[input.LocationID] != nil { storeAmount = storeAmount.Sub(outputLocationAmountMap[input.LocationID][input.Number]) } inputLocationAmountMap[input.LocationID][input.Number] = storeAmount } productHasSendAmount := make(map[string]decimal.Decimal) //本次已发货数量 //find location ID outputInfoList := make([]*service.OutputInfo, 0) for locationID, locationProductAmounts := range inputLocationAmountMap { productInfoList := make([]*service.ProductInfo, 0) for productNumber, productAmount := range locationProductAmounts { remainAmount := productNeedSendAmount[productNumber].Sub(productHasSendAmount[productNumber]) if remainAmount.LessThanOrEqual(decimal.Zero) { continue } var locationSendAmount decimal.Decimal if productAmount.GreaterThanOrEqual(remainAmount) { locationSendAmount = remainAmount } else { locationSendAmount = productAmount } if locationSendAmount.LessThanOrEqual(decimal.Zero) { continue } productInfoList = append(productInfoList, &service.ProductInfo{ ProductID: productNumber, Amount: locationSendAmount, }) productHasSendAmount[productNumber] = productHasSendAmount[productNumber].Add(locationSendAmount) } outputInfoList = append(outputInfoList, &service.OutputInfo{ LocationID: int(locationID), WarehouseID: int(LocationIDWarehouseIDMap[locationID]), Products: productInfoList, OperationID: 0, SourceNumber: req.OrderNumber, SaleDetailsNumber: req.OrderNumber, }) } err = service.AddOutputOperations(outputInfoList) if err != nil { logx.Errorf("OrderProductOutput AddOutputOperations err:%v", err) return nil, err } resp.Code = 1 resp.Msg = "success" return resp, nil } func (s *Server) GetOutputOperationInfo(ctx context.Context, req *GetOutputOperationInfoRequest) (*GetOutputOperationInfoResponse, error) { if req.Number == "" { return nil, errors.New("参数不能为空") } result := new(GetOutputOperationInfoResponse) first, err := models.NewOperationSearch().SetSourceNumber(req.Number).SetBaseOperationType(constvar.BaseOperationTypeOutgoing).SetStatus(constvar.OperationStatus_Finish).First() if err != nil { if err == gorm.ErrRecordNotFound { return result, nil } return nil, err } details, err := models.NewOperationDetailsSearch().SetOperationId(first.Id).FindNotTotal() if err != nil { return nil, err } list := make([]*OutputProduct, 0) for _, detail := range details { var op OutputProduct op.Number = detail.ProductId op.Amount = detail.Amount.String() list = append(list, &op) } result.Products = list return result, nil }