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 = int(req.ClientId)
|
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
|
}
|