liujiandao
2023-11-10 845f1b6da081aae73325e558356f9ab8a851462e
确认出库修改crm销售明细状态
8个文件已修改
1053 ■■■■■ 已修改文件
conf/config.go 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
conf/config.yaml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
controllers/operation.go 783 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
main.go 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
proto/product_inventory.proto 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
proto/product_inventory/product_inventory.pb.go 197 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
proto/product_inventory/product_inventory_grpc.pb.go 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
proto/product_inventory/server.go 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
conf/config.go
@@ -39,6 +39,7 @@
    grpcServerConf struct {
        ApsAddr string //aps服务grpc地址
        CrmAddr string //crm服务grpc地址
    }
)
@@ -67,6 +68,7 @@
    host := os.Getenv("HOST")         // 本机IP地址
    GrpcPort := os.Getenv("WMS_GRPC") // 只对外提供grpc服务,本服务不用
    apsAddr := os.Getenv("APS_GRPC")
    crmAddr := os.Getenv("CRM_GRPC")
    if len(GrpcPort) > 0 {
        WebConf.GrpcPort = GrpcPort
    }
@@ -79,6 +81,9 @@
    if len(apsAddr) > 0 {
        GrpcServerConf.ApsAddr = apsAddr
    }
    if len(crmAddr) > 0 {
        GrpcServerConf.CrmAddr = crmAddr
    }
    DBHost := os.Getenv("DB_HOST")
    DBName := os.Getenv("DB_NAME")
conf/config.yaml
@@ -25,3 +25,4 @@
  storePath: uploads/file
grpcServer:
  apsAddr: 192.168.20.119:9091
  crmAddr: 192.168.20.118:9092
controllers/operation.go
@@ -1,23 +1,22 @@
package controllers
import (
    "encoding/json"
    "errors"
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/shopspring/decimal"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "gorm.io/gorm"
    "os"
    "sort"
    "strconv"
    "time"
    "wms/conf"
    "wms/constvar"
    "wms/extend/code"
    "wms/extend/util"
    "wms/models"
    "wms/opa"
    "wms/pkg/logx"
    "wms/pkg/structx"
    "wms/proto/product_inventory"
    "wms/request"
)
@@ -308,380 +307,412 @@
//    @Success    200    {object}    util.Response    "成功"
//    @Router        /api-wms/v1/operation/finish/{id} [put]
func (slf OperationController) Finish(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        util.ResponseFormat(c, code.RequestParamError, "错误的id值")
        return
    }
    if id == 0 {
        util.ResponseFormat(c, code.RequestParamError, "id为0")
        return
    }
    operation, err := models.NewOperationSearch().SetPreload(true).SetID(id).First()
    if err != nil {
        util.ResponseFormat(c, code.RequestParamError, "未找到相关出入库信息:"+err.Error())
        return
    }
    if operation.Status != constvar.OperationStatus_Ready {
        util.ResponseFormat(c, code.RequestError, "该出入库信息无法完成")
        return
    }
    if err := models.WithTransaction(func(tx *gorm.DB) error {
        if err := models.NewOperationSearch().SetOrm(tx).SetID(id).Update(&models.Operation{Status: constvar.OperationStatus_Finish}); err != nil {
            return err
        }
        var listProdtId []string
        var listProdt []*models.Material
        mapProdt := make(map[string]decimal.Decimal)
        listDetails, err := models.NewOperationDetailsSearch().SetOperationId(operation.Id).FindAll()
        if err != nil {
            return err
        }
        for _, v := range listDetails {
            listProdtId = append(listProdtId, v.ProductId)
            mapProdt[v.ProductId] = v.Amount
        }
        if err := models.NewMaterialSearch().Orm.Where("id IN ?", listProdtId).Find(&listProdt).Error; err != nil {
            return err
        }
        if operation.BaseOperationType == constvar.BaseOperationTypeIncoming {
            var operationInputs []*models.Operation
            for k, v := range listProdt {
                value, ok := mapProdt[v.ID]
                if !ok {
                    return errors.New("产品种类异常")
                }
                listProdt[k].Amount = listProdt[k].Amount.Add(value)
                if err := tx.Save(listProdt[k]).Error; err != nil {
                    return err
                }
                locationRule, err := models.NewLocationProductSearch().SetProductId(v.ID).SetAreaId(operation.ToLocationID).First()
                if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                    return err
                }
                if err == nil {
                    operationTransfer := &models.Operation{
                        Number:            operation.Number,
                        SourceNumber:      operation.SourceNumber,
                        OperationTypeId:   0,
                        OperationTypeName: operation.OperationTypeName,
                        Status:            constvar.OperationStatus_Finish,
                        FromLocationID:    locationRule.AreaId,
                        ToLocationID:      locationRule.LocationId,
                        OperationDate:     operation.OperationDate,
                        ContacterID:       operation.ContacterID,
                        ContacterName:     operation.ContacterName,
                        CompanyID:         operation.CompanyID,
                        CompanyName:       operation.CompanyName,
                        Comment:           operation.Comment,
                        BaseOperationType: constvar.BaseOperationTypeInternal,
                        Details: []*models.OperationDetails{
                            {
                                ProductId: v.ID,
                                Amount:    value,
                            },
                        },
                    }
                    operationInputs = append(operationInputs, operationTransfer)
                    //if err := tx.Create(&operationTransfer).Error; err != nil {
                    //    return err
                    //}
                    //TODO:出入库的finish和报废的finish都要增加对location_product_amount表数量的更新,因为此表有ProductCategory字段,所以operation_details表中要增加ProductCategoryId字段
                    locAmount, err := models.NewLocationProductAmountSearch().
                        SetProductId(v.ID).
                        SetLocationId(locationRule.LocationId).
                        First()
                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                        return err
                    }
                    locAmount.LocationId = locationRule.LocationId
                    locAmount.ProductId = v.ID
                    locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
                    locAmount.Amount = locAmount.Amount.Add(value)
                    if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
                        return res.Error
                    }
                } else {
                    locationRule, err = models.NewLocationProductSearch().SetProductCategoryId(v.CategoryId).SetAreaId(operation.ToLocationID).First()
                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                        return err
                    }
                    if err == nil {
                        operationTransfer := &models.Operation{
                            Number:            operation.Number,
                            SourceNumber:      operation.SourceNumber,
                            OperationTypeId:   0,
                            OperationTypeName: operation.OperationTypeName,
                            Status:            constvar.OperationStatus_Finish,
                            FromLocationID:    locationRule.AreaId,
                            ToLocationID:      locationRule.LocationId,
                            OperationDate:     operation.OperationDate,
                            ContacterID:       operation.ContacterID,
                            ContacterName:     operation.ContacterName,
                            CompanyID:         operation.CompanyID,
                            CompanyName:       operation.CompanyName,
                            Comment:           operation.Comment,
                            BaseOperationType: constvar.BaseOperationTypeInternal,
                            Details: []*models.OperationDetails{
                                {
                                    ProductId: v.ID,
                                    Amount:    value,
                                },
                            },
                        }
                        operationInputs = append(operationInputs, operationTransfer)
                        //if err := tx.Create(&operationTransfer).Error; err != nil {
                        //    return err
                        //}
                        //TODO:出入库的finish和报废的finish都要增加对location_product_amount表数量的更新,因为此表有ProductCategory字段,所以operation_details表中要增加ProductCategoryId字段
                        locAmount, err := models.NewLocationProductAmountSearch().
                            SetProductId(v.ID).
                            SetLocationId(locationRule.LocationId).
                            First()
                        if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                            return err
                        }
                        locAmount.LocationId = locationRule.LocationId
                        locAmount.ProductId = v.ID
                        locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
                        locAmount.Amount = locAmount.Amount.Add(value)
                        if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
                            return res.Error
                        }
                    } else {
                        //TODO:出入库的finish和报废的finish都要增加对location_product_amount表数量的更新,因为此表有ProductCategory字段,所以operation_details表中要增加ProductCategoryId字段
                        locAmount, err := models.NewLocationProductAmountSearch().
                            SetProductId(v.ID).
                            SetLocationId(operation.ToLocationID).
                            First()
                        if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                            return err
                        }
                        locAmount.LocationId = operation.ToLocationID
                        locAmount.ProductId = v.ID
                        locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
                        locAmount.Amount = locAmount.Amount.Add(value)
                        if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
                            return res.Error
                        }
                    }
                }
            }
            if len(operationInputs) > 0 {
                if err := tx.Create(&operationInputs).Error; err != nil {
                    return err
                }
            }
        }
        if operation.BaseOperationType == constvar.BaseOperationTypeOutgoing {
            for k, v := range listProdt {
                value, ok := mapProdt[v.ID]
                if !ok {
                    return errors.New("产品种类异常")
                }
                //todo 演示测试数据
                data, err := os.ReadFile("conf/input.json")
                if err != nil {
                    return errors.New("文件读取失败")
                }
                m := make(map[string]interface{})
                err = json.Unmarshal(data, &m)
                if err != nil {
                    return errors.New("格式转换失败")
                }
                if opa.OpaCheck(c, m, "operation") {
                    if v.Amount.LessThan(value) {
                        return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Name, v.Amount.String(), value.String()))
                    }
                }
                listProdt[k].Amount = listProdt[k].Amount.Sub(value)
                if err := tx.Save(listProdt[k]).Error; err != nil {
                    return err
                }
                locAmount, res := models.NewLocationProductAmountSearch().
                    SetProductId(v.ID).
                    SetLocationId(operation.ToLocationID).
                    FirstRes()
                if res.Error != nil {
                    return err
                }
                if locAmount.Amount.LessThan(value) {
                    return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Name, v.Amount.String(), value.String()))
                }
                locAmount.Amount = locAmount.Amount.Sub(value)
                if err := models.NewLocationProductAmountSearch().SetID(locAmount.Id).Update(locAmount); err != nil {
                    return err
                }
            }
        }
        if operation.BaseOperationType == constvar.BaseOperationTypeInternal {
            var operationInputs []*models.Operation
            for _, v := range listProdt {
                value, ok := mapProdt[v.ID]
                if !ok {
                    return errors.New("产品种类异常")
                }
                //listProdt[k].Amount = listProdt[k].Amount.Add(value)
                //if err := tx.Save(listProdt[k]).Error; err != nil {
                //    return err
                //}
                fromLocAmount, res := models.NewLocationProductAmountSearch().
                    SetProductId(v.ID).
                    SetLocationId(operation.FromLocationID).
                    FirstRes()
                if res.Error != nil {
                    return err
                }
                if fromLocAmount.Amount.LessThan(value) {
                    return errors.New(fmt.Sprintf("产品:%v,库存:%v,调拨:%v,数量不够,无法完成调拨操作", v.Name, v.Amount.String(), value.String()))
                }
                fromLocAmount.Amount = fromLocAmount.Amount.Sub(value)
                if err := models.NewLocationProductAmountSearch().SetID(fromLocAmount.Id).Update(fromLocAmount); err != nil {
                    return err
                }
                locationRule, err := models.NewLocationProductSearch().SetProductId(v.ID).SetAreaId(operation.ToLocationID).First()
                if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                    return err
                }
                if err == nil {
                    operationTransfer := &models.Operation{
                        Number:            operation.Number,
                        SourceNumber:      operation.SourceNumber,
                        OperationTypeId:   0,
                        OperationTypeName: operation.OperationTypeName,
                        Status:            constvar.OperationStatus_Finish,
                        FromLocationID:    locationRule.AreaId,
                        ToLocationID:      locationRule.LocationId,
                        OperationDate:     operation.OperationDate,
                        ContacterID:       operation.ContacterID,
                        ContacterName:     operation.ContacterName,
                        CompanyID:         operation.CompanyID,
                        CompanyName:       operation.CompanyName,
                        Comment:           operation.Comment,
                        BaseOperationType: constvar.BaseOperationTypeInternal,
                        Details: []*models.OperationDetails{
                            {
                                ProductId: v.ID,
                                Amount:    value,
                            },
                        },
                    }
                    operationInputs = append(operationInputs, operationTransfer)
                    //if err := tx.Create(&operationTransfer).Error; err != nil {
                    //    return err
                    //}
                    locAmount, err := models.NewLocationProductAmountSearch().
                        SetProductId(v.ID).
                        SetLocationId(locationRule.LocationId).
                        First()
                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                        return err
                    }
                    locAmount.LocationId = locationRule.LocationId
                    locAmount.ProductId = v.ID
                    locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
                    locAmount.Amount = locAmount.Amount.Add(value)
                    if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
                        return res.Error
                    }
                } else {
                    locationRule, err = models.NewLocationProductSearch().SetProductCategoryId(v.CategoryId).SetAreaId(operation.ToLocationID).First()
                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                        return err
                    }
                    if err == nil {
                        operationTransfer := &models.Operation{
                            Number:            operation.Number,
                            SourceNumber:      operation.SourceNumber,
                            OperationTypeId:   0,
                            OperationTypeName: operation.OperationTypeName,
                            Status:            constvar.OperationStatus_Finish,
                            FromLocationID:    locationRule.AreaId,
                            ToLocationID:      locationRule.LocationId,
                            OperationDate:     operation.OperationDate,
                            ContacterID:       operation.ContacterID,
                            ContacterName:     operation.ContacterName,
                            CompanyID:         operation.CompanyID,
                            CompanyName:       operation.CompanyName,
                            Comment:           operation.Comment,
                            BaseOperationType: constvar.BaseOperationTypeInternal,
                            Details: []*models.OperationDetails{
                                {
                                    ProductId: v.ID,
                                    Amount:    value,
                                },
                            },
                        }
                        operationInputs = append(operationInputs, operationTransfer)
                        //if err := tx.Create(&operationTransfer).Error; err != nil {
                        //    return err
                        //}
                        locAmount, err := models.NewLocationProductAmountSearch().
                            SetProductId(v.ID).
                            SetLocationId(locationRule.LocationId).
                            First()
                        if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                            return err
                        }
                        locAmount.LocationId = locationRule.LocationId
                        locAmount.ProductId = v.ID
                        locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
                        locAmount.Amount = locAmount.Amount.Add(value)
                        if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
                            return res.Error
                        }
                    } else {
                        locAmount, err := models.NewLocationProductAmountSearch().
                            SetProductId(v.ID).
                            SetLocationId(operation.ToLocationID).
                            First()
                        if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
                            return err
                        }
                        locAmount.LocationId = operation.ToLocationID
                        locAmount.ProductId = v.ID
                        locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
                        locAmount.Amount = locAmount.Amount.Add(value)
                        if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
                            return res.Error
                        }
                    }
                }
            }
            if len(operationInputs) > 0 {
                if err := tx.Create(&operationInputs).Error; err != nil {
                    return err
                }
            }
        }
        return nil
    }); err != nil {
        util.ResponseFormat(c, code.RequestError, err.Error())
        return
    }
    //id, err := strconv.Atoi(c.Param("id"))
    //if err != nil {
    //    util.ResponseFormat(c, code.RequestParamError, "错误的id值")
    //    return
    //}
    //if id == 0 {
    //    util.ResponseFormat(c, code.RequestParamError, "id为0")
    //    return
    //}
    //operation, err := models.NewOperationSearch().SetPreload(true).SetID(id).First()
    //if err != nil {
    //    util.ResponseFormat(c, code.RequestParamError, "未找到相关出入库信息:"+err.Error())
    //    return
    //}
    //if operation.Status != constvar.OperationStatus_Ready {
    //    util.ResponseFormat(c, code.RequestError, "该出入库信息无法完成")
    //    return
    //}
    //if err := models.WithTransaction(func(tx *gorm.DB) error {
    //
    //    if err := models.NewOperationSearch().SetOrm(tx).SetID(id).Update(&models.Operation{Status: constvar.OperationStatus_Finish}); err != nil {
    //        return err
    //    }
    //
    //    var listProdtId []string
    //    var listProdt []*models.Material
    //    mapProdt := make(map[string]decimal.Decimal)
    //    listDetails, err := models.NewOperationDetailsSearch().SetOperationId(operation.Id).FindAll()
    //    if err != nil {
    //        return err
    //    }
    //    for _, v := range listDetails {
    //        listProdtId = append(listProdtId, v.ProductId)
    //        mapProdt[v.ProductId] = v.Amount
    //    }
    //
    //    if err := models.NewMaterialSearch().Orm.Where("id IN ?", listProdtId).Find(&listProdt).Error; err != nil {
    //        return err
    //    }
    //
    //    if operation.BaseOperationType == constvar.BaseOperationTypeIncoming {
    //        var operationInputs []*models.Operation
    //        for k, v := range listProdt {
    //            value, ok := mapProdt[v.ID]
    //            if !ok {
    //                return errors.New("产品种类异常")
    //            }
    //
    //            listProdt[k].Amount = listProdt[k].Amount.Add(value)
    //            if err := tx.Save(listProdt[k]).Error; err != nil {
    //                return err
    //            }
    //
    //            locationRule, err := models.NewLocationProductSearch().SetProductId(v.ID).SetAreaId(operation.ToLocationID).First()
    //            if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                return err
    //            }
    //
    //            if err == nil {
    //                operationTransfer := &models.Operation{
    //                    Number:            operation.Number,
    //                    SourceNumber:      operation.SourceNumber,
    //                    OperationTypeId:   0,
    //                    OperationTypeName: operation.OperationTypeName,
    //                    Status:            constvar.OperationStatus_Finish,
    //                    FromLocationID:    locationRule.AreaId,
    //                    ToLocationID:      locationRule.LocationId,
    //                    OperationDate:     operation.OperationDate,
    //                    ContacterID:       operation.ContacterID,
    //                    ContacterName:     operation.ContacterName,
    //                    CompanyID:         operation.CompanyID,
    //                    CompanyName:       operation.CompanyName,
    //                    Comment:           operation.Comment,
    //                    BaseOperationType: constvar.BaseOperationTypeInternal,
    //                    Details: []*models.OperationDetails{
    //                        {
    //                            ProductId: v.ID,
    //                            Amount:    value,
    //                        },
    //                    },
    //                }
    //                operationInputs = append(operationInputs, operationTransfer)
    //
    //                //if err := tx.Create(&operationTransfer).Error; err != nil {
    //                //    return err
    //                //}
    //
    //                //TODO:出入库的finish和报废的finish都要增加对location_product_amount表数量的更新,因为此表有ProductCategory字段,所以operation_details表中要增加ProductCategoryId字段
    //                locAmount, err := models.NewLocationProductAmountSearch().
    //                    SetProductId(v.ID).
    //                    SetLocationId(locationRule.LocationId).
    //                    First()
    //                if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                    return err
    //                }
    //                locAmount.LocationId = locationRule.LocationId
    //                locAmount.ProductId = v.ID
    //                locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
    //                locAmount.Amount = locAmount.Amount.Add(value)
    //                if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
    //                    return res.Error
    //                }
    //            } else {
    //                locationRule, err = models.NewLocationProductSearch().SetProductCategoryId(v.CategoryId).SetAreaId(operation.ToLocationID).First()
    //                if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                    return err
    //                }
    //                if err == nil {
    //                    operationTransfer := &models.Operation{
    //                        Number:            operation.Number,
    //                        SourceNumber:      operation.SourceNumber,
    //                        OperationTypeId:   0,
    //                        OperationTypeName: operation.OperationTypeName,
    //                        Status:            constvar.OperationStatus_Finish,
    //                        FromLocationID:    locationRule.AreaId,
    //                        ToLocationID:      locationRule.LocationId,
    //                        OperationDate:     operation.OperationDate,
    //                        ContacterID:       operation.ContacterID,
    //                        ContacterName:     operation.ContacterName,
    //                        CompanyID:         operation.CompanyID,
    //                        CompanyName:       operation.CompanyName,
    //                        Comment:           operation.Comment,
    //                        BaseOperationType: constvar.BaseOperationTypeInternal,
    //                        Details: []*models.OperationDetails{
    //                            {
    //                                ProductId: v.ID,
    //                                Amount:    value,
    //                            },
    //                        },
    //                    }
    //                    operationInputs = append(operationInputs, operationTransfer)
    //
    //                    //if err := tx.Create(&operationTransfer).Error; err != nil {
    //                    //    return err
    //                    //}
    //
    //                    //TODO:出入库的finish和报废的finish都要增加对location_product_amount表数量的更新,因为此表有ProductCategory字段,所以operation_details表中要增加ProductCategoryId字段
    //                    locAmount, err := models.NewLocationProductAmountSearch().
    //                        SetProductId(v.ID).
    //                        SetLocationId(locationRule.LocationId).
    //                        First()
    //                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                        return err
    //                    }
    //                    locAmount.LocationId = locationRule.LocationId
    //                    locAmount.ProductId = v.ID
    //                    locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
    //                    locAmount.Amount = locAmount.Amount.Add(value)
    //                    if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
    //                        return res.Error
    //                    }
    //                } else {
    //                    //TODO:出入库的finish和报废的finish都要增加对location_product_amount表数量的更新,因为此表有ProductCategory字段,所以operation_details表中要增加ProductCategoryId字段
    //                    locAmount, err := models.NewLocationProductAmountSearch().
    //                        SetProductId(v.ID).
    //                        SetLocationId(operation.ToLocationID).
    //                        First()
    //                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                        return err
    //                    }
    //                    locAmount.LocationId = operation.ToLocationID
    //                    locAmount.ProductId = v.ID
    //                    locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
    //                    locAmount.Amount = locAmount.Amount.Add(value)
    //                    if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
    //                        return res.Error
    //                    }
    //                }
    //            }
    //        }
    //        if len(operationInputs) > 0 {
    //            if err := tx.Create(&operationInputs).Error; err != nil {
    //                return err
    //            }
    //        }
    //
    //    }
    //
    //    if operation.BaseOperationType == constvar.BaseOperationTypeOutgoing {
    //        for k, v := range listProdt {
    //            value, ok := mapProdt[v.ID]
    //            if !ok {
    //                return errors.New("产品种类异常")
    //            }
    //            //todo 演示测试数据
    //            data, err := os.ReadFile("conf/input.json")
    //            if err != nil {
    //                return errors.New("文件读取失败")
    //            }
    //            m := make(map[string]interface{})
    //            err = json.Unmarshal(data, &m)
    //            if err != nil {
    //                return errors.New("格式转换失败")
    //            }
    //            if opa.OpaCheck(c, m, "operation") {
    //                if v.Amount.LessThan(value) {
    //                    return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Name, v.Amount.String(), value.String()))
    //                }
    //            }
    //            listProdt[k].Amount = listProdt[k].Amount.Sub(value)
    //            if err := tx.Save(listProdt[k]).Error; err != nil {
    //                return err
    //            }
    //            locAmount, res := models.NewLocationProductAmountSearch().
    //                SetProductId(v.ID).
    //                SetLocationId(operation.ToLocationID).
    //                FirstRes()
    //            if res.Error != nil {
    //                return err
    //            }
    //            if locAmount.Amount.LessThan(value) {
    //                return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Name, v.Amount.String(), value.String()))
    //            }
    //            locAmount.Amount = locAmount.Amount.Sub(value)
    //            if err := models.NewLocationProductAmountSearch().SetID(locAmount.Id).Update(locAmount); err != nil {
    //                return err
    //            }
    //        }
    //    }
    //
    //    if operation.BaseOperationType == constvar.BaseOperationTypeInternal {
    //        var operationInputs []*models.Operation
    //        for _, v := range listProdt {
    //            value, ok := mapProdt[v.ID]
    //            if !ok {
    //                return errors.New("产品种类异常")
    //            }
    //            //listProdt[k].Amount = listProdt[k].Amount.Add(value)
    //            //if err := tx.Save(listProdt[k]).Error; err != nil {
    //            //    return err
    //            //}
    //            fromLocAmount, res := models.NewLocationProductAmountSearch().
    //                SetProductId(v.ID).
    //                SetLocationId(operation.FromLocationID).
    //                FirstRes()
    //            if res.Error != nil {
    //                return err
    //            }
    //            if fromLocAmount.Amount.LessThan(value) {
    //                return errors.New(fmt.Sprintf("产品:%v,库存:%v,调拨:%v,数量不够,无法完成调拨操作", v.Name, v.Amount.String(), value.String()))
    //            }
    //            fromLocAmount.Amount = fromLocAmount.Amount.Sub(value)
    //            if err := models.NewLocationProductAmountSearch().SetID(fromLocAmount.Id).Update(fromLocAmount); err != nil {
    //                return err
    //            }
    //
    //            locationRule, err := models.NewLocationProductSearch().SetProductId(v.ID).SetAreaId(operation.ToLocationID).First()
    //            if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                return err
    //            }
    //
    //            if err == nil {
    //                operationTransfer := &models.Operation{
    //                    Number:            operation.Number,
    //                    SourceNumber:      operation.SourceNumber,
    //                    OperationTypeId:   0,
    //                    OperationTypeName: operation.OperationTypeName,
    //                    Status:            constvar.OperationStatus_Finish,
    //                    FromLocationID:    locationRule.AreaId,
    //                    ToLocationID:      locationRule.LocationId,
    //                    OperationDate:     operation.OperationDate,
    //                    ContacterID:       operation.ContacterID,
    //                    ContacterName:     operation.ContacterName,
    //                    CompanyID:         operation.CompanyID,
    //                    CompanyName:       operation.CompanyName,
    //                    Comment:           operation.Comment,
    //                    BaseOperationType: constvar.BaseOperationTypeInternal,
    //                    Details: []*models.OperationDetails{
    //                        {
    //                            ProductId: v.ID,
    //                            Amount:    value,
    //                        },
    //                    },
    //                }
    //                operationInputs = append(operationInputs, operationTransfer)
    //
    //                //if err := tx.Create(&operationTransfer).Error; err != nil {
    //                //    return err
    //                //}
    //
    //                locAmount, err := models.NewLocationProductAmountSearch().
    //                    SetProductId(v.ID).
    //                    SetLocationId(locationRule.LocationId).
    //                    First()
    //                if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                    return err
    //                }
    //                locAmount.LocationId = locationRule.LocationId
    //                locAmount.ProductId = v.ID
    //                locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
    //                locAmount.Amount = locAmount.Amount.Add(value)
    //                if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
    //                    return res.Error
    //                }
    //            } else {
    //                locationRule, err = models.NewLocationProductSearch().SetProductCategoryId(v.CategoryId).SetAreaId(operation.ToLocationID).First()
    //                if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                    return err
    //                }
    //                if err == nil {
    //                    operationTransfer := &models.Operation{
    //                        Number:            operation.Number,
    //                        SourceNumber:      operation.SourceNumber,
    //                        OperationTypeId:   0,
    //                        OperationTypeName: operation.OperationTypeName,
    //                        Status:            constvar.OperationStatus_Finish,
    //                        FromLocationID:    locationRule.AreaId,
    //                        ToLocationID:      locationRule.LocationId,
    //                        OperationDate:     operation.OperationDate,
    //                        ContacterID:       operation.ContacterID,
    //                        ContacterName:     operation.ContacterName,
    //                        CompanyID:         operation.CompanyID,
    //                        CompanyName:       operation.CompanyName,
    //                        Comment:           operation.Comment,
    //                        BaseOperationType: constvar.BaseOperationTypeInternal,
    //                        Details: []*models.OperationDetails{
    //                            {
    //                                ProductId: v.ID,
    //                                Amount:    value,
    //                            },
    //                        },
    //                    }
    //                    operationInputs = append(operationInputs, operationTransfer)
    //
    //                    //if err := tx.Create(&operationTransfer).Error; err != nil {
    //                    //    return err
    //                    //}
    //
    //                    locAmount, err := models.NewLocationProductAmountSearch().
    //                        SetProductId(v.ID).
    //                        SetLocationId(locationRule.LocationId).
    //                        First()
    //                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                        return err
    //                    }
    //                    locAmount.LocationId = locationRule.LocationId
    //                    locAmount.ProductId = v.ID
    //                    locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
    //                    locAmount.Amount = locAmount.Amount.Add(value)
    //                    if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
    //                        return res.Error
    //                    }
    //                } else {
    //                    locAmount, err := models.NewLocationProductAmountSearch().
    //                        SetProductId(v.ID).
    //                        SetLocationId(operation.ToLocationID).
    //                        First()
    //                    if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
    //                        return err
    //                    }
    //                    locAmount.LocationId = operation.ToLocationID
    //                    locAmount.ProductId = v.ID
    //                    locAmount.CreateDate = time.Now().Format("2006-01-02 15:04:05")
    //                    locAmount.Amount = locAmount.Amount.Add(value)
    //                    if res := models.NewLocationProductAmountSearch().Orm.Where("id=?", locAmount.ID).Save(locAmount); res.Error != nil {
    //                        return res.Error
    //                    }
    //                }
    //            }
    //        }
    //        if len(operationInputs) > 0 {
    //            if err := tx.Create(&operationInputs).Error; err != nil {
    //                return err
    //            }
    //        }
    //    }
    //    return nil
    //}); err != nil {
    //    util.ResponseFormat(c, code.RequestError, err.Error())
    //    return
    //}
    //if operation.SourceNumber != "" {
    //    go UpdateSalesDetailStatus(c, operation.SourceNumber)
    //}
    UpdateSalesDetailStatus(c, c.Param("id"))
    util.ResponseFormat(c, code.Success, "操作成功")
}
var ProductInventoryServiceConn *grpc.ClientConn
func InitProductInventoryServiceConn() {
    var err error
    ProductInventoryServiceConn, err = grpc.Dial(conf.GrpcServerConf.CrmAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        logx.Errorf("grpc dial product service error: %v", err.Error())
        return
    }
}
func CloseProductInventoryServiceConn() {
    if ProductInventoryServiceConn != nil {
        ProductInventoryServiceConn.Close()
    }
}
func UpdateSalesDetailStatus(ctx *gin.Context, number string) {
    client := product_inventory.NewProductInventoryServiceClient(ProductInventoryServiceConn)
    _, err := client.UpdateSalesDetailStatus(ctx, &product_inventory.UpdateSalesDetailStatusRequest{
        Number:            number,
        SalesDetailStatus: "已出库",
    })
    if err != nil {
        logx.Errorf("grpc dial UpdateSalesDetailStatus service error: %v", err)
    }
}
// ListTransfer
// @Tags      入库/出库
// @Summary   库存调拨列表
main.go
@@ -45,6 +45,7 @@
    go shutdown(server)
    //启动grpc客户端
    go controllers.InitInventoryOrderServiceConn()
    go controllers.InitProductInventoryServiceConn()
    //启动grpc服务
    go func() {
        ln, err := net.Listen("tcp", ":"+conf.WebConf.GrpcPort)
@@ -76,6 +77,7 @@
    defer cancel()
    controllers.CloseInventoryOrderServiceConn()
    controllers.CloseProductInventoryServiceConn()
    // 关闭HTTP服务器
    if err := server.Shutdown(ctx); err != nil {
        logx.Infof("服务优雅退出失败: %v", err)
proto/product_inventory.proto
@@ -5,6 +5,7 @@
service productInventoryService {
  rpc CreateOperation(CreateOperationRequest) returns(CreateOperationResponse) {}
  rpc GetInventoryProductInfo(GetInventoryProductInfoRequest) returns (GetInventoryProductInfoResponse) {}
  rpc UpdateSalesDetailStatus(UpdateSalesDetailStatusRequest) returns (UpdateSalesDetailStatusResponse) {}
}
message CreateOperationRequest{
@@ -51,4 +52,16 @@
  int32   Code = 1;
  string  Msg = 2;
  repeated ProductInfo ProductList = 3;
}
//------------------------------------------------------------
message UpdateSalesDetailStatusRequest {
  string Number = 1;//明细单编码
  string SalesDetailStatus = 2;
}
message UpdateSalesDetailStatusResponse{
  int32   Code = 1;
  string  Msg = 2;
}
proto/product_inventory/product_inventory.pb.go
@@ -462,6 +462,116 @@
    return nil
}
type UpdateSalesDetailStatusRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields
    Number            string `protobuf:"bytes,1,opt,name=Number,proto3" json:"Number,omitempty"` //明细单编码
    SalesDetailStatus string `protobuf:"bytes,2,opt,name=SalesDetailStatus,proto3" json:"SalesDetailStatus,omitempty"`
}
func (x *UpdateSalesDetailStatusRequest) Reset() {
    *x = UpdateSalesDetailStatusRequest{}
    if protoimpl.UnsafeEnabled {
        mi := &file_product_inventory_proto_msgTypes[6]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}
func (x *UpdateSalesDetailStatusRequest) String() string {
    return protoimpl.X.MessageStringOf(x)
}
func (*UpdateSalesDetailStatusRequest) ProtoMessage() {}
func (x *UpdateSalesDetailStatusRequest) ProtoReflect() protoreflect.Message {
    mi := &file_product_inventory_proto_msgTypes[6]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}
// Deprecated: Use UpdateSalesDetailStatusRequest.ProtoReflect.Descriptor instead.
func (*UpdateSalesDetailStatusRequest) Descriptor() ([]byte, []int) {
    return file_product_inventory_proto_rawDescGZIP(), []int{6}
}
func (x *UpdateSalesDetailStatusRequest) GetNumber() string {
    if x != nil {
        return x.Number
    }
    return ""
}
func (x *UpdateSalesDetailStatusRequest) GetSalesDetailStatus() string {
    if x != nil {
        return x.SalesDetailStatus
    }
    return ""
}
type UpdateSalesDetailStatusResponse struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields
    Code int32  `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
    Msg  string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"`
}
func (x *UpdateSalesDetailStatusResponse) Reset() {
    *x = UpdateSalesDetailStatusResponse{}
    if protoimpl.UnsafeEnabled {
        mi := &file_product_inventory_proto_msgTypes[7]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}
func (x *UpdateSalesDetailStatusResponse) String() string {
    return protoimpl.X.MessageStringOf(x)
}
func (*UpdateSalesDetailStatusResponse) ProtoMessage() {}
func (x *UpdateSalesDetailStatusResponse) ProtoReflect() protoreflect.Message {
    mi := &file_product_inventory_proto_msgTypes[7]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}
// Deprecated: Use UpdateSalesDetailStatusResponse.ProtoReflect.Descriptor instead.
func (*UpdateSalesDetailStatusResponse) Descriptor() ([]byte, []int) {
    return file_product_inventory_proto_rawDescGZIP(), []int{7}
}
func (x *UpdateSalesDetailStatusResponse) GetCode() int32 {
    if x != nil {
        return x.Code
    }
    return 0
}
func (x *UpdateSalesDetailStatusResponse) GetMsg() string {
    if x != nil {
        return x.Msg
    }
    return ""
}
var File_product_inventory_proto protoreflect.FileDescriptor
var file_product_inventory_proto_rawDesc = []byte{
@@ -520,21 +630,38 @@
    0x67, 0x12, 0x2e, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4c, 0x69, 0x73, 0x74,
    0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,
    0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4c, 0x69, 0x73,
    0x74, 0x32, 0xc1, 0x01, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x76,
    0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x46, 0x0a,
    0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
    0x12, 0x17, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
    0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x43, 0x72, 0x65, 0x61,
    0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
    0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65,
    0x6e, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f,
    0x12, 0x1f, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x50,
    0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
    0x74, 0x1a, 0x20, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79,
    0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,
    0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x15, 0x5a, 0x13, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75,
    0x63, 0x74, 0x5f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72,
    0x6f, 0x74, 0x6f, 0x33,
    0x74, 0x22, 0x66, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x61, 0x6c, 0x65, 0x73,
    0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75,
    0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20,
    0x01, 0x28, 0x09, 0x52, 0x06, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x53,
    0x61, 0x6c, 0x65, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
    0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x44, 0x65, 0x74,
    0x61, 0x69, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x47, 0x0a, 0x1f, 0x55, 0x70, 0x64,
    0x61, 0x74, 0x65, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x53, 0x74,
    0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04,
    0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x43, 0x6f, 0x64, 0x65,
    0x12, 0x10, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x4d,
    0x73, 0x67, 0x32, 0xa1, 0x02, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e,
    0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x46,
    0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
    0x6e, 0x12, 0x17, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74,
    0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x43, 0x72, 0x65,
    0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,
    0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76,
    0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66,
    0x6f, 0x12, 0x1f, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79,
    0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65,
    0x73, 0x74, 0x1a, 0x20, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72,
    0x79, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70,
    0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
    0x53, 0x61, 0x6c, 0x65, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75,
    0x73, 0x12, 0x1f, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x44,
    0x65, 0x74, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
    0x73, 0x74, 0x1a, 0x20, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x61, 0x6c, 0x65, 0x73,
    0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70,
    0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x15, 0x5a, 0x13, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x64,
    0x75, 0x63, 0x74, 0x5f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70,
    0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -549,7 +676,7 @@
    return file_product_inventory_proto_rawDescData
}
var file_product_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_product_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_product_inventory_proto_goTypes = []interface{}{
    (*CreateOperationRequest)(nil),          // 0: CreateOperationRequest
    (*InventoryProduct)(nil),                // 1: InventoryProduct
@@ -557,16 +684,20 @@
    (*GetInventoryProductInfoRequest)(nil),  // 3: GetInventoryProductInfoRequest
    (*ProductInfo)(nil),                     // 4: ProductInfo
    (*GetInventoryProductInfoResponse)(nil), // 5: GetInventoryProductInfoResponse
    (*UpdateSalesDetailStatusRequest)(nil),  // 6: UpdateSalesDetailStatusRequest
    (*UpdateSalesDetailStatusResponse)(nil), // 7: UpdateSalesDetailStatusResponse
}
var file_product_inventory_proto_depIdxs = []int32{
    1, // 0: CreateOperationRequest.ProductList:type_name -> InventoryProduct
    4, // 1: GetInventoryProductInfoResponse.ProductList:type_name -> ProductInfo
    0, // 2: productInventoryService.CreateOperation:input_type -> CreateOperationRequest
    3, // 3: productInventoryService.GetInventoryProductInfo:input_type -> GetInventoryProductInfoRequest
    2, // 4: productInventoryService.CreateOperation:output_type -> CreateOperationResponse
    5, // 5: productInventoryService.GetInventoryProductInfo:output_type -> GetInventoryProductInfoResponse
    4, // [4:6] is the sub-list for method output_type
    2, // [2:4] is the sub-list for method input_type
    6, // 4: productInventoryService.UpdateSalesDetailStatus:input_type -> UpdateSalesDetailStatusRequest
    2, // 5: productInventoryService.CreateOperation:output_type -> CreateOperationResponse
    5, // 6: productInventoryService.GetInventoryProductInfo:output_type -> GetInventoryProductInfoResponse
    7, // 7: productInventoryService.UpdateSalesDetailStatus:output_type -> UpdateSalesDetailStatusResponse
    5, // [5:8] is the sub-list for method output_type
    2, // [2:5] is the sub-list for method input_type
    2, // [2:2] is the sub-list for extension type_name
    2, // [2:2] is the sub-list for extension extendee
    0, // [0:2] is the sub-list for field type_name
@@ -650,6 +781,30 @@
                return nil
            }
        }
        file_product_inventory_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*UpdateSalesDetailStatusRequest); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
        file_product_inventory_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*UpdateSalesDetailStatusResponse); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
    }
    type x struct{}
    out := protoimpl.TypeBuilder{
@@ -657,7 +812,7 @@
            GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
            RawDescriptor: file_product_inventory_proto_rawDesc,
            NumEnums:      0,
            NumMessages:   6,
            NumMessages:   8,
            NumExtensions: 0,
            NumServices:   1,
        },
proto/product_inventory/product_inventory_grpc.pb.go
@@ -20,6 +20,7 @@
type ProductInventoryServiceClient interface {
    CreateOperation(ctx context.Context, in *CreateOperationRequest, opts ...grpc.CallOption) (*CreateOperationResponse, error)
    GetInventoryProductInfo(ctx context.Context, in *GetInventoryProductInfoRequest, opts ...grpc.CallOption) (*GetInventoryProductInfoResponse, error)
    UpdateSalesDetailStatus(ctx context.Context, in *UpdateSalesDetailStatusRequest, opts ...grpc.CallOption) (*UpdateSalesDetailStatusResponse, error)
}
type productInventoryServiceClient struct {
@@ -48,12 +49,22 @@
    return out, nil
}
func (c *productInventoryServiceClient) UpdateSalesDetailStatus(ctx context.Context, in *UpdateSalesDetailStatusRequest, opts ...grpc.CallOption) (*UpdateSalesDetailStatusResponse, error) {
    out := new(UpdateSalesDetailStatusResponse)
    err := c.cc.Invoke(ctx, "/productInventoryService/UpdateSalesDetailStatus", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}
// ProductInventoryServiceServer is the server API for ProductInventoryService service.
// All implementations must embed UnimplementedProductInventoryServiceServer
// for forward compatibility
type ProductInventoryServiceServer interface {
    CreateOperation(context.Context, *CreateOperationRequest) (*CreateOperationResponse, error)
    GetInventoryProductInfo(context.Context, *GetInventoryProductInfoRequest) (*GetInventoryProductInfoResponse, error)
    UpdateSalesDetailStatus(context.Context, *UpdateSalesDetailStatusRequest) (*UpdateSalesDetailStatusResponse, error)
    mustEmbedUnimplementedProductInventoryServiceServer()
}
@@ -66,6 +77,9 @@
}
func (UnimplementedProductInventoryServiceServer) GetInventoryProductInfo(context.Context, *GetInventoryProductInfoRequest) (*GetInventoryProductInfoResponse, error) {
    return nil, status.Errorf(codes.Unimplemented, "method GetInventoryProductInfo not implemented")
}
func (UnimplementedProductInventoryServiceServer) UpdateSalesDetailStatus(context.Context, *UpdateSalesDetailStatusRequest) (*UpdateSalesDetailStatusResponse, error) {
    return nil, status.Errorf(codes.Unimplemented, "method UpdateSalesDetailStatus not implemented")
}
func (UnimplementedProductInventoryServiceServer) mustEmbedUnimplementedProductInventoryServiceServer() {
}
@@ -117,6 +131,24 @@
    return interceptor(ctx, in, info, handler)
}
func _ProductInventoryService_UpdateSalesDetailStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(UpdateSalesDetailStatusRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(ProductInventoryServiceServer).UpdateSalesDetailStatus(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/productInventoryService/UpdateSalesDetailStatus",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(ProductInventoryServiceServer).UpdateSalesDetailStatus(ctx, req.(*UpdateSalesDetailStatusRequest))
    }
    return interceptor(ctx, in, info, handler)
}
// ProductInventoryService_ServiceDesc is the grpc.ServiceDesc for ProductInventoryService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -132,6 +164,10 @@
            MethodName: "GetInventoryProductInfo",
            Handler:    _ProductInventoryService_GetInventoryProductInfo_Handler,
        },
        {
            MethodName: "UpdateSalesDetailStatus",
            Handler:    _ProductInventoryService_UpdateSalesDetailStatus_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "product_inventory.proto",
proto/product_inventory/server.go
@@ -17,9 +17,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) {
@@ -31,8 +34,10 @@
    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.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
@@ -102,6 +107,9 @@
            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
            }
        }