From 73b6baf6af3d88cdcb0e2df7932a9bd96b0b85c5 Mon Sep 17 00:00:00 2001
From: zhangqian <zhangqian@123.com>
Date: 星期一, 01 七月 2024 22:32:34 +0800
Subject: [PATCH] 月度统计出入库按类型汇总报表定时任务和手动跑任务接口

---
 proto/product_inventory/server.go |  309 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 307 insertions(+), 2 deletions(-)

diff --git a/proto/product_inventory/server.go b/proto/product_inventory/server.go
index c99b9f9..41e54d1 100644
--- a/proto/product_inventory/server.go
+++ b/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 {
@@ -101,7 +105,7 @@
 	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 {
@@ -171,7 +175,8 @@
 	operation.ReceiverPhone = req.Phone
 	operation.ReceiverAddr = req.Address
 	operation.Source = req.Source
-	operation.CompanyID = int(req.ClientId)
+	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 {
@@ -202,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
+}

--
Gitblit v1.8.0