package service
|
|
import (
|
"errors"
|
"fmt"
|
"github.com/gin-gonic/gin"
|
"github.com/mitchellh/mapstructure"
|
"github.com/shopspring/decimal"
|
"gorm.io/gorm"
|
"strconv"
|
"time"
|
"wms/constvar"
|
"wms/middleware"
|
"wms/models"
|
)
|
|
type OutputInfo struct {
|
LocationID int
|
WarehouseID int
|
Products []*ProductInfo
|
OperationID int
|
SourceNumber string
|
SaleDetailsNumber string
|
}
|
|
type ProductInfo struct {
|
ProductID string
|
Amount decimal.Decimal
|
}
|
|
func AddOutputOperations(outputList []*OutputInfo) (err error) {
|
//查找operationID
|
warehouseIds := make([]int, 0)
|
for _, output := range outputList {
|
warehouseIds = append(warehouseIds, output.WarehouseID)
|
}
|
opTypes, err := models.NewOperationTypeSearch().SetWarehouseIds(warehouseIds).SetBaseOperationType(constvar.BaseOperationTypeOutgoing).FindNotTotal()
|
if err != nil {
|
return err
|
}
|
|
autoCode, codeStandard, maxAutoIncr, err := GetAutoCode(constvar.CodeStandardType_Outgoing)
|
if err != nil {
|
return err
|
}
|
|
opTypeMap := make(map[int]*models.OperationType, len(opTypes))
|
for _, opType := range opTypes {
|
opTypeMap[opType.WarehouseId] = opType
|
}
|
operations := make([]*models.Operation, 0, len(outputList))
|
for _, output := range outputList {
|
location, err := models.NewLocationSearch().SetType(int(constvar.LocationTypeCustomer)).First()
|
if err != nil {
|
return err
|
}
|
details := make([]*models.OperationDetails, 0, len(output.Products))
|
for _, product := range output.Products {
|
details = append(details, &models.OperationDetails{
|
OperationID: 0,
|
ProductId: product.ProductID,
|
Amount: product.Amount,
|
FromLocationID: output.LocationID,
|
ToLocationID: location.Id,
|
})
|
}
|
if opTypeMap[output.WarehouseID] == nil {
|
continue
|
}
|
operation := &models.Operation{
|
Id: 0,
|
Number: autoCode,
|
SourceNumber: output.SourceNumber,
|
OperationTypeId: opTypeMap[output.WarehouseID].Id,
|
OperationTypeName: opTypeMap[output.WarehouseID].Name,
|
Status: constvar.OperationStatus_Ready,
|
OperationDate: time.Now().Format("2006-01-02 15:04:05"),
|
ContacterID: 0,
|
ContacterName: "",
|
CompanyID: "",
|
CompanyName: "",
|
Comment: "crm发货申请",
|
LogisticCompanyId: "",
|
LogisticCompany: models.LogisticCompany{},
|
WaybillNumber: "",
|
Weight: decimal.Decimal{},
|
LogisticWeight: decimal.Decimal{},
|
Source: "crm",
|
Details: details,
|
BaseOperationType: constvar.BaseOperationTypeOutgoing,
|
LocationID: output.LocationID,
|
OperationSource: constvar.OperationSourceSaleDelivery,
|
SalesDetailsNumber: output.SaleDetailsNumber,
|
WarehouseId: output.WarehouseID,
|
}
|
operations = append(operations, operation)
|
autoCode = models.GetAutoCode(maxAutoIncr, codeStandard)
|
maxAutoIncr++
|
}
|
|
return models.NewOperationSearch().CreateBatch(operations)
|
}
|
|
// FinishOperationInput 完成入库
|
func FinishOperationInput(c *gin.Context, tx *gorm.DB, operation *models.Operation, listDetails []*models.OperationDetails, mapLocAmount map[string]*models.LocationProductAmount) (err error) {
|
userInfo := middleware.GetUserInfo(c)
|
locationRoleList, err := models.NewLocationProductSearch().Find()
|
if err != nil {
|
return errors.New("获取上架规则信息失败")
|
}
|
var mapLocationRoleProduct, mapLocationRoleCategory map[string]*models.LocationProduct
|
if len(locationRoleList) > 0 {
|
mapLocationRoleProduct = make(map[string]*models.LocationProduct)
|
mapLocationRoleCategory = make(map[string]*models.LocationProduct)
|
for _, v := range locationRoleList {
|
if v.RuleType == constvar.RuleType_Product {
|
mapLocationRoleProduct[strconv.Itoa(v.AreaId)+v.ProductId] = v
|
}
|
if v.RuleType == constvar.RuleType_ProductCategory {
|
mapLocationRoleCategory[strconv.Itoa(v.AreaId)+strconv.Itoa(v.ProductCategoryID)] = v
|
}
|
}
|
}
|
|
var details []*models.OperationDetails
|
for k, v := range listDetails {
|
listDetails[k].Product.Amount = listDetails[k].Product.Amount.Add(v.Amount)
|
if err := tx.Save(&listDetails[k].Product).Error; err != nil {
|
return err
|
}
|
|
if roleProduct, ok := mapLocationRoleProduct[strconv.Itoa(operation.LocationID)+v.ProductId]; ok {
|
detail := &models.OperationDetails{
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
FromLocationID: roleProduct.AreaId,
|
ToLocationID: roleProduct.LocationId,
|
}
|
details = append(details, detail)
|
|
if locAmount, aok := mapLocAmount[strconv.Itoa(roleProduct.LocationId)+v.ProductId]; aok {
|
locAmount.Amount = locAmount.Amount.Add(v.Amount)
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(int(locAmount.ID)).Save(locAmount); err != nil {
|
return err
|
}
|
} else {
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).Create(&models.LocationProductAmount{
|
LocationId: roleProduct.LocationId,
|
ProductCategoryID: v.Product.CategoryId,
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
CreateDate: time.Now().Format("2006-01-02 15:04:05"),
|
WarehouseId: operation.WarehouseId,
|
}); err != nil {
|
return err
|
}
|
}
|
} else {
|
if roleCategory, cok := mapLocationRoleCategory[strconv.Itoa(operation.LocationID)+strconv.Itoa(v.Product.CategoryId)]; cok {
|
detail := &models.OperationDetails{
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
FromLocationID: roleCategory.AreaId,
|
ToLocationID: roleCategory.LocationId,
|
}
|
details = append(details, detail)
|
|
if locAmount, aok := mapLocAmount[strconv.Itoa(roleCategory.LocationId)+v.ProductId]; aok {
|
locAmount.Amount = locAmount.Amount.Add(v.Amount)
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(int(locAmount.ID)).Save(locAmount); err != nil {
|
return err
|
}
|
} else {
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).Create(&models.LocationProductAmount{
|
LocationId: roleCategory.LocationId,
|
ProductCategoryID: v.Product.CategoryId,
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
CreateDate: time.Now().Format("2006-01-02 15:04:05"),
|
WarehouseId: operation.WarehouseId,
|
}); err != nil {
|
return err
|
}
|
}
|
} else {
|
if locAmount, aok := mapLocAmount[strconv.Itoa(operation.LocationID)+v.ProductId]; aok {
|
locAmount.Amount = locAmount.Amount.Add(v.Amount)
|
locAmount.ID = uint(locAmount.Id)
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(int(locAmount.ID)).Save(locAmount); err != nil {
|
return err
|
}
|
} else {
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).Create(&models.LocationProductAmount{
|
LocationId: operation.LocationID,
|
ProductCategoryID: v.Product.CategoryId,
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
CreateDate: time.Now().Format("2006-01-02 15:04:05"),
|
WarehouseId: operation.WarehouseId,
|
}); err != nil {
|
return err
|
}
|
}
|
}
|
}
|
}
|
if len(details) > 0 {
|
if err := tx.Create(&models.Operation{
|
Number: operation.Number,
|
SourceNumber: operation.SourceNumber,
|
OperationTypeId: 0,
|
OperationTypeName: operation.OperationTypeName,
|
Status: constvar.OperationStatus_Finish,
|
OperationDate: operation.OperationDate,
|
ContacterID: operation.ContacterID,
|
ContacterName: operation.ContacterName,
|
CompanyID: operation.CompanyID,
|
CompanyName: operation.CompanyName,
|
Comment: operation.Comment,
|
BaseOperationType: constvar.BaseOperationTypeInternal,
|
Details: details,
|
CheckedBy: userInfo.Username,
|
WarehouseId: operation.WarehouseId,
|
}).Error; err != nil {
|
return err
|
}
|
}
|
return nil
|
|
}
|
|
// FinishOperationOutput 完成出库或报废
|
func FinishOperationOutput(tx *gorm.DB, listDetails []*models.OperationDetails, mapLocAmount map[string]*models.LocationProductAmount, originOperation *models.Operation) (err error) {
|
var internalInputDetails []*models.OperationDetails //内部调拨产生的出库验证后,生成入库单
|
for k, v := range listDetails {
|
if v.Product.Amount.LessThan(v.Amount) {
|
return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Product.Name, v.Product.Amount.String(), v.Amount.String()))
|
}
|
listDetails[k].Product.Amount = listDetails[k].Product.Amount.Sub(v.Amount)
|
if err := models.NewMaterialSearch().SetOrm(tx).Save(&listDetails[k].Product); err != nil {
|
return err
|
}
|
|
if locAmount, aok := mapLocAmount[strconv.Itoa(v.FromLocationID)+v.ProductId]; aok {
|
if locAmount.Amount.LessThan(v.Amount) {
|
return errors.New(fmt.Sprintf("产品:%v,库存:%v,出库:%v,数量不够,无法完成出库操作", v.Product.Name, locAmount.Amount.String(), v.Amount.String()))
|
}
|
locAmount.Amount = locAmount.Amount.Sub(v.Amount)
|
if err := models.NewLocationProductAmountSearch().SetOrm(tx).SetID(locAmount.Id).Save(locAmount); err != nil {
|
return err
|
}
|
} else {
|
return errors.New("当前仓库没有该产品,请先入库")
|
}
|
if v.IsInternalOutput {
|
var inputDetail models.OperationDetails
|
mapstructure.Decode(v, &inputDetail)
|
inputDetail.Id = 0
|
inputDetail.OperationID = 0
|
internalInputDetails = append(internalInputDetails, &inputDetail)
|
}
|
}
|
if len(internalInputDetails) > 0 {
|
opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeIncoming)
|
if err != nil {
|
return err
|
}
|
operation := &models.Operation{
|
OperationTypeId: opTypeId,
|
Number: strconv.FormatInt(time.Now().Unix(), 10),
|
Status: constvar.OperationStatus_Ready,
|
OperationDate: time.Now().Format("2006-01-02 15:04:05"),
|
Comment: "库存调拨入库",
|
BaseOperationType: constvar.BaseOperationTypeIncoming,
|
Details: internalInputDetails,
|
LocationID: originOperation.LocationID,
|
ToLocationID: originOperation.ToLocationID,
|
OperationTypeName: "库存调拨入库",
|
WarehouseId: originOperation.ToLocation.WarehouseId,
|
}
|
if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil {
|
return err
|
}
|
}
|
return nil
|
|
}
|
|
// FinishOperationInternal 验证内部调拨生成出库单
|
func FinishOperationInternal(tx *gorm.DB, listDetails []*models.OperationDetails, originOperation *models.Operation) (err error) {
|
var outputDetails []*models.OperationDetails
|
for _, v := range listDetails {
|
outputDetails = append(outputDetails, &models.OperationDetails{
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
FromLocationID: v.FromLocationID,
|
ToLocationID: v.ToLocationID,
|
TotalGrossWeight: v.TotalGrossWeight,
|
TotalNetWeight: v.TotalNetWeight,
|
AuxiliaryAmount: v.AuxiliaryAmount,
|
AuxiliaryUnit: v.AuxiliaryUnit,
|
Remark: v.Remark,
|
IsInternalOutput: true,
|
Cost: v.Cost,
|
SalePrice: v.SalePrice,
|
})
|
}
|
if len(outputDetails) > 0 {
|
opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeOutgoing)
|
if err != nil {
|
return err
|
}
|
operation := &models.Operation{
|
OperationTypeId: opTypeId,
|
Number: strconv.FormatInt(time.Now().Unix(), 10),
|
Status: constvar.OperationStatus_Ready,
|
OperationDate: time.Now().Format("2006-01-02 15:04:05"),
|
Comment: "库存调拨出库",
|
BaseOperationType: constvar.BaseOperationTypeOutgoing,
|
Details: outputDetails,
|
LocationID: originOperation.LocationID,
|
ToLocationID: originOperation.ToLocationID,
|
OperationTypeName: "库存调拨出库",
|
IsInternalOutput: true,
|
WarehouseId: originOperation.WarehouseId,
|
}
|
if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil {
|
return err
|
}
|
}
|
return nil
|
|
}
|
|
// FinishOperationAdjust 完成库存调整
|
// 验证后生成入库单或出库单(库存减少生成出库单,库存增加生成入库单)
|
func FinishOperationAdjust(tx *gorm.DB, listDetails []*models.OperationDetails, mapLocAmount map[string]*models.LocationProductAmount, originOperation *models.Operation) (err error) {
|
var inputDetails []*models.OperationDetails
|
var outputDetails []*models.OperationDetails
|
for _, v := range listDetails {
|
if _, aok := mapLocAmount[strconv.Itoa(v.FromLocationID)+v.ProductId]; aok {
|
if v.StockAmount.Equal(v.Amount) { //盘点数量和剩余数量一致
|
continue
|
}
|
if v.Amount.GreaterThan(v.StockAmount) {
|
inputDetails = append(inputDetails, &models.OperationDetails{
|
ProductId: v.ProductId,
|
Amount: v.Amount.Sub(v.StockAmount),
|
FromLocationID: 0,
|
ToLocationID: v.FromLocationID,
|
TotalGrossWeight: v.TotalGrossWeight,
|
TotalNetWeight: v.TotalNetWeight,
|
AuxiliaryAmount: v.AuxiliaryAmount,
|
AuxiliaryUnit: v.AuxiliaryUnit,
|
Remark: v.Remark,
|
Cost: v.Cost,
|
SalePrice: v.SalePrice,
|
})
|
} else {
|
outputDetails = append(outputDetails, &models.OperationDetails{
|
ProductId: v.ProductId,
|
Amount: v.StockAmount.Sub(v.Amount),
|
FromLocationID: v.FromLocationID,
|
ToLocationID: 0,
|
TotalGrossWeight: v.TotalGrossWeight,
|
TotalNetWeight: v.TotalNetWeight,
|
AuxiliaryAmount: v.AuxiliaryAmount,
|
AuxiliaryUnit: v.AuxiliaryUnit,
|
Remark: v.Remark,
|
Cost: v.Cost,
|
SalePrice: v.SalePrice,
|
})
|
}
|
} else {
|
inputDetails = append(inputDetails, &models.OperationDetails{
|
ProductId: v.ProductId,
|
Amount: v.Amount,
|
FromLocationID: v.FromLocationID,
|
ToLocationID: v.ToLocationID,
|
Remark: v.Remark,
|
})
|
}
|
}
|
if len(inputDetails) > 0 {
|
opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeIncoming)
|
if err != nil {
|
return err
|
}
|
operation := &models.Operation{
|
OperationTypeId: opTypeId,
|
Number: strconv.FormatInt(time.Now().Unix(), 10),
|
Status: constvar.OperationStatus_Ready,
|
OperationDate: time.Now().Format("2006-01-02 15:04:05"),
|
Comment: "库存调整入库",
|
BaseOperationType: constvar.BaseOperationTypeIncoming,
|
Details: inputDetails,
|
LocationID: inputDetails[0].FromLocationID,
|
OperationTypeName: "库存调整入库",
|
WarehouseId: originOperation.WarehouseId,
|
}
|
if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil {
|
return err
|
}
|
}
|
if len(outputDetails) > 0 {
|
opTypeId, err := GetTargetOperationTypeIdByOperation(originOperation, constvar.BaseOperationTypeOutgoing)
|
if err != nil {
|
return err
|
}
|
operation := &models.Operation{
|
OperationTypeId: opTypeId,
|
Number: strconv.FormatInt(time.Now().Unix(), 10),
|
Status: constvar.OperationStatus_Ready,
|
OperationDate: time.Now().Format("2006-01-02 15:04:05"),
|
Comment: "库存调整出库",
|
BaseOperationType: constvar.BaseOperationTypeOutgoing,
|
Details: outputDetails,
|
LocationID: outputDetails[0].FromLocationID,
|
OperationTypeName: "库存调整出库",
|
WarehouseId: originOperation.WarehouseId,
|
}
|
if err := models.NewOperationSearch().SetOrm(tx).Create(operation); err != nil {
|
return err
|
}
|
}
|
return nil
|
|
}
|
|
func GetTargetOperationTypeIdByOperation(operation *models.Operation, baseOT constvar.BaseOperationType) (operationTypeId int, err error) {
|
targetOT, err := models.NewOperationTypeSearch().SetBaseOperationType(baseOT).SetWarehouseId(operation.WarehouseId).First()
|
if err != nil {
|
return 0, err
|
}
|
return targetOT.Id, nil
|
}
|