| | |
| | | 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 { |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | //查询产品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 { |
| | |
| | | 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 |
| | |
| | | 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 |
| | | } |
| | | } |
| | |
| | | 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 |
| | | } |
| | |
| | | } |
| | | 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 |
| | |
| | | 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) |
| | | } |
| | |
| | | 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 |
| | | } |