zhangqian
2024-07-01 73b6baf6af3d88cdcb0e2df7932a9bd96b0b85c5
proto/product_inventory/server.go
@@ -3,13 +3,17 @@
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 {
@@ -17,9 +21,12 @@
}
type ProductAndLocationInfo struct {
   ProductId  string          `json:"productId"`
   Amount     decimal.Decimal `json:"amount"`
   LocationId int             `json:"locationId"`
   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) {
@@ -29,12 +36,18 @@
   //查询产品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.from_location_id as location_id").
      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 {
@@ -83,8 +96,8 @@
      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.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}).
      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
@@ -92,11 +105,15 @@
   products := make([]*ProductInfo, 0)
   for _, material := range materials {
      var p ProductInfo
      p.Id = material.ID
      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
         }
      }
@@ -116,11 +133,11 @@
            cu = cu.Add(info.Amount)
         }
      }
      cu = at.Sub(cu)
      p.AvailableNumber = cu.String()
      products = append(products, &p)
   }
   resp := new(GetInventoryProductInfoResponse)
   resp.ProductList = products
   return resp, nil
}
@@ -143,23 +160,32 @@
   }
   operation.OperationTypeName = operationType.Name
   operation.OperationTypeId = operationType.Id
   location, err := models.NewLocationSearch().SetJointNames([]string{warehouse.Code}).First()
   location, err := models.NewLocationSearch().SetID(warehouse.LocationId).First()
   if err != nil {
      return nil, err
   }
   operation.FromLocationID = location.Id
   first, err := models.NewLocationSearch().SetType(int(constvar.LocationTypeCustomer)).First()
   if err != nil {
      return nil, err
   }
   operation.ToLocationID = first.Id
   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
@@ -171,7 +197,8 @@
         detail.ProductId = product.Id
         amount, _ := decimal.NewFromString(product.Amount)
         detail.Amount = amount
         details = append(details, &detail)
         detail.FromLocationID = location.Id
         detail.ToLocationID = first.Id
         newOperation.Details = append(newOperation.Details, &detail)
         operations = append(operations, &newOperation)
      }
@@ -180,3 +207,303 @@
   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
}