mh_server/model/erp_commodity.go

617 lines
30 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
import (
"fmt"
"github.com/codinl/go-logger"
"github.com/jinzhu/gorm"
"math/rand"
"time"
)
const (
ErpOrderStateAudited = "audited" // 已审核
HavePaid = 2 // 已支付
NoPrint = 1 // 未打印
RetailTypeSale = "sale" // 零售销售订单
SysUserIdByAdmin = 1 // 系统管理员id
SysUserNameByAdmin = "系统管理员" // 系统管理员名称
SoldOut = 2 // 已售
)
// ErpStock 库存列表
type ErpStock struct {
Model
StoreId uint32 `json:"store_id" gorm:"index"` // 门店编号
StoreName string `json:"store_name"` // 门店名称
ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` // 商品id
ErpCommodityName string `json:"erp_commodity_name"` // 商品名称
ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 分类id
ErpCategoryName string `json:"erp_category_name"` // 分类名称
CommoditySerialNumber string `json:"commodity_serial_number" gorm:"index"` // 商品编码/串码
IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加)
RetailPrice float64 `json:"retail_price"` // 指导零售价
MinRetailPrice float64 `json:"min_retail_price"` // 最低零售价
Count uint32 `json:"count"` // 数量
DispatchCount uint32 `json:"dispatch_count"` // 调拨中数量(调拨中调入)
DecisionStoreId []uint32 `json:"decision_store_id" gorm:"-"` // 门店编号列表(查询进销存的时候使用)
Commodities []ErpStockCommodity `json:"commodities" gorm:"-"` //
}
// ErpStockCommodity 库存详情
type ErpStockCommodity struct {
Model
ErpStockId uint32 `json:"erp_stock_id" gorm:"index"` // 库存id
StoreId uint32 `json:"store_id" gorm:"index"` // 门店id
StoreName string `json:"store_name"` // 门店名称
ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` // 商品id
ErpCommodityName string `json:"erp_commodity_name"` // 商品名称
CommoditySerialNumber string `json:"commodity_serial_number" gorm:"index"` // 商品编号
ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 分类id
ErpCategoryName string `json:"erp_category_name"` // 分类名称
ErpBarcode string `json:"erp_barcode"` // 商品条码
IMEIType uint32 `json:"imei_type"` // 是否串码1-无串码 2-串码(系统生成) 3-串码(手动添加)
IMEI string `json:"imei"` // 商品串码
ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` // 供应商id
ErpSupplierName string `json:"erp_supplier_name"` // 供应商名称
StockTime time.Time `json:"stock_time"` // 最近入库时间
RetailPrice float64 `json:"retail_price"` // 指导零售价
MinRetailPrice float64 `json:"min_retail_price"` // 最低零售价
StaffCostPrice float64 `json:"staff_cost_price"` // 员工成本价加价加价50不是加价后的价格
WholesalePrice float64 `json:"wholesale_price"` // 指导采购价
MemberDiscount float64 `json:"member_discount"` // 会员优惠
State uint32 `json:"state"` // 状态:1-在库 2-已售 3-采购退货 4-调拨中 5-销售锁定中 6-产品出库 7-盘点出库
Count uint32 `json:"count"` // 数量
StorageType uint32 `json:"storage_type"` // 入库方式1-系统入库 2-采购入库 3-产品入库 4-盘点入库
FirstStockTime time.Time `json:"first_stock_time"` // 首次入库时间
StockSn string `json:"stock_sn"` // 库存订单编号(跟采购入库的入库编号关联)
OriginalSn string `json:"original_sn" gorm:"index"` // 首次入库订单编号(单据编号)
StockStartTime time.Time `json:"stock_start_time" gorm:"-"` // 最近入库开始时间
StockEndTime time.Time `json:"stock_end_time" gorm:"-"` // 最近入库结束时间
Age uint32 `json:"age" gorm:"-"` // 最近库龄
AllAge uint32 `json:"all_age" gorm:"-"` // 总库龄
Remark string `json:"remark"` // 备注
CategoryNumber string `json:"category_number" gorm:"-"` // 商品分类编号
}
// ErpCommodity 商品表
type ErpCommodity struct {
Model
SerialNumber string `json:"serial_number"` // 商品编号
Number uint32 `json:"number"` // 商品数量
Name string `json:"name"` // 商品名称
ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 商品分类id
ErpCategoryName string `json:"erp_category_name"` // 商品分类名称
ErpBarcode string `json:"erp_barcode"` // 商品条码
IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码(系统生成) 3-串码(手动添加)
IMEI string `json:"imei"` // 串码
ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` // 主供应商id
ErpSupplierName string `json:"erp_supplier_name"` // 主供应商名称
RetailPrice uint32 `json:"retail_price"` // 指导零售价
MinRetailPrice uint32 `json:"min_retail_price"` // 最低零售价
StaffCostPrice uint32 `json:"staff_cost_price"` // 员工成本价加价加价50不是加价后的价格
WholesalePrice uint32 `json:"wholesale_price"` // 指导采购价
Brokerage1 float64 `json:"brokerage_1"` // 销售毛利提成
Brokerage2 float64 `json:"brokerage_2"` // 员工毛利提成
MemberDiscount float64 `json:"member_discount"` // 会员优惠
Origin string `json:"origin"` // 产地
Remark string `json:"remark" gorm:"type:varchar(512)"` // 备注
Img string `json:"img"` // 图片
StopPurchase uint32 `json:"stop_purchase"` // 0-未勾选正常采购1-勾选,停止采购
ManufacturerCode string `json:"manufacturer_code"` // 厂家编码
IsSyncedToMall uint32 `json:"is_synced_to_mall"` // 是否同步到小程序商城0-否1-是
GoodsId uint32 `json:"goods_id"` // 小程序商城-商品ID
SaleStatus uint32 `json:"sale_status"` // 小程序商城-在售状态 1-在售 2-下架
ErpCategory *ErpCategory `json:"erp_category" gorm:"-"`
}
// ErpCategory 商品分类
type ErpCategory struct {
Model
Name string `json:"name"` // 名称
Priority string `json:"priority"` // 分类
Number uint32 `json:"number"` //
FullNum uint32 `json:"full_num"` //
State uint32 `json:"state"` // 1-未使用 2-使用 3-隐藏
Level uint32 `json:"level"` // 分类层级
Pid uint32 `json:"pid" gorm:"index"` //
Sort uint32 `json:"sort"` //
SubCats []ErpCategory `json:"sub_cats" gorm:"-"` // 子列表
}
// ErpOrder 零售订单表
type ErpOrder struct {
Model
BillSn string `json:"bill_sn" gorm:"index"` // 单据编号
RetailType string `json:"retail_type"` // 销售类型:sale 零售销售; rejected 零售退货
GoodsOrderId uint32 `json:"goods_order_id" gorm:"index"` // 小程序商城订单id
Uid int `json:"uid"` // 用户id
UserType int `json:"user_type"` // 会员类别1-普通会员 2-黄金会员 4-白金会员 5-黑金会员 6-尊享会员 7-黄金&尊享 8-白金&尊享 9-黑金&尊享
Tel string `json:"tel" gorm:"index"` // 客户手机号
StoreId uint32 `json:"store_id" gorm:"index"` // 门店id
StoreName string `json:"store_name"` // 门店名称
MakerId uint32 `json:"maker_id" gorm:"index"` // 制单人id
MakerName string `json:"maker_name"` // 制单人名称
MakerTime time.Time `json:"maker_time"` // 制单时间
AuditorId uint32 `json:"auditor_id" gorm:"index"` // 审核人id
AuditorName string `json:"auditor_name"` // 审核人姓名
AuditTime *time.Time `json:"audit_time"` // 审核时间
CashierList string `json:"cashier_list" gorm:"type:text"` // 付款方式存储json数据
SalesmanList string `json:"salesman_list" gorm:"type:text"` // 销售员信息存储json数据
MemberType string `json:"member_type"` // 会员类型:general 普通; member 会员
State string `json:"state" gorm:"index"` // 订单状态:un_audit 待审核; audited 已审核
TotalRetailPrice float64 `json:"total_retail_price"` // 订单总指导零售价
TotalAmount float64 `json:"total_amount"` // 订单实收金额
TotalCount int32 `json:"total_count"` // 订单商品数量
TotalSalesProfit float64 `json:"total_sales_profit"` // 订单总销售毛利
TotalStaffProfit float64 `json:"total_staff_profit"` // 订单总员工毛利
VmCount uint32 `json:"vm_count"` // 使用会员积分
SaleOrderId uint32 `json:"sale_order_id"` // 销售订单id
PayStatus uint32 `json:"pay_status"` // 支付状态 0-未创建 1-待支付; 2-已支付
IsPrint uint32 `json:"is_print"` // 是否打印小票 1-未打印 2-已打印
PrintCount uint32 `json:"print_count"` // 小票打印次数
InvoiceCode string `json:"invoice_code"` // 发票代码
InvoiceNumber string `json:"invoice_number"` // 发票编码
RejectedTotalAmount float64 `json:"rejected_total_amount" gorm:"-"` // 订单总退货金额
RejectedTotalCount uint32 `json:"rejected_total_count" gorm:"-"` // 订单总退货数量
StorePer float64 `json:"store_per"` // 门店提成订单总员工毛利X该门店设置好的提成比例保留到小数后两位多余舍去
TotalDiscount float64 `json:"total_discount"` // 订单总优惠:订单所有商品零售优惠+会员优惠+会员积分抵扣之和
BankTrxNo string `json:"bank_trx_no" gorm:"-"` // 银行流水号
Commodities []ErpOrderCommodity `json:"commodities" gorm:"-"` // 零售订单商品信息
Cashiers []ErpOrderCashier `json:"cashiers" gorm:"-"` // 收付款方式
Salesman []ErpOrderSales `json:"salesman" gorm:"-"` // 销售员信息
}
// ErpOrderSales 销售员信息
type ErpOrderSales struct {
Model
ErpOrderId uint32 `json:"erp_order_id" gorm:"index"` // 零售订单id后端生成
Uid uint32 `json:"userId" binding:"required"` // 销售员用户ID20240322更换为系统用户表主键id
Name string `json:"name"` // 销售员用户姓名
SalesProfitPer float64 `json:"sales_profit_per"` // 销售毛利提成每个商品销售毛利X其对应的提成比例后求和如果是两个销售员参与则分别除以2 ,保留到小数后两位多余舍去
StaffProfitPer float64 `json:"staff_profit_per"` // 员工毛利提成每个商品员工毛利X其对应的提成比例后求和如果是两个销售员参与则分别除以2 ,保留到小数后两位多余舍去
SalesmanPer float64 `json:"salesman_per"` // 销售员提成订单总员工毛利X该销售员设置好的提成比例如果是两个销售员参与那么两个人算出的提成均除以2保留到小数后两位多余舍去
}
// ErpOrderCashier 订单收款方式
type ErpOrderCashier struct {
CashierId uint32 `json:"cashier_id"` // 收付款方式id
Name string `json:"name"` // 收付款方式名称
Amount float64 `json:"amount"` // 金额
}
// ErpOrderCommodity 零售订单商品表
type ErpOrderCommodity struct {
Model
ErpOrderId uint32 `json:"erp_order_id" gorm:"index"` // 零售订单id后端生成
ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 分类id
ErpCategoryName string `json:"erp_category_name"` // 分类名称
ErpCommodityId uint32 `json:"erp_commodity_id" gorm:"index"` // 商品id
ErpCommodityName string `json:"erp_commodity_name"` // 商品名称
ErpSupplierId uint32 `json:"erp_supplier_id" gorm:"index"` // 主供应商id
ErpSupplierName string `json:"erp_supplier_name"` // 主供应商名称
IMEIType uint32 `json:"imei_type"` // 1-无串码 2-串码
IMEI string `json:"imei" gorm:"index"` // 串码
PresentType uint32 `json:"present_type"` // 赠送类型:1-非赠送 2-赠送
RetailPrice float64 `json:"retail_price"` // 指导零售价
SalePrice float64 `json:"sale_price"` // 零售价
Count int32 `json:"count"` // 销售数量
SaleDiscount float64 `json:"sale_discount"` // 零售优惠
MemberDiscount float64 `json:"member_discount"` // 会员优惠
VmDiscount float64 `json:"vm_discount"` // 会员积分抵扣
CouponID uint32 `json:"coupon_id"` // 优惠券ID
CouponDiscount float64 `json:"coupon_discount"` // 优惠券抵扣
CouponCode string `json:"coupon_code"` // 优惠券券码
CouponName string `json:"coupon_name"` // 优惠券名称
Amount float64 `json:"amount"` // 实际零售价
ReceivedAmount float64 `json:"received_amount"` // 商品实收金额
Remark string `json:"remark"` // 销售备注
RejectedRemark string `json:"rejected_remark"` // 退货备注
RejectedPrice float64 `json:"rejected_price"` // 退货单价
RejectedCount uint32 `json:"rejected_count"` // 退货数量
RejectedAmount float64 `json:"rejected_amount"` // 退货金额
RejectedOrderCommodityId uint32 `json:"rejected_order_commodity_id"` // 退货订单商品id
StaffCostPrice float64 `json:"staff_cost_price"` // 员工成本价加价加价50不是加价后的价格
WholesalePrice float64 `json:"wholesale_price"` // 指导采购价
SalesProfit float64 `json:"sales_profit"` // 销售毛利:实际零售价-采购单价;如果为退货订单,则为实际退货价-采购单价
StaffProfit float64 `json:"staff_profit"` // 员工毛利:实际零售价-员工成本价;如果为退货订单,则为实际退货价-员工成本价
ErpStockCommodityID string `json:"erp_stock_commodity_id"` // 库存商品表主键id
StaffPrice float64 `json:"staff_price" gorm:"-"` // 员工成本价
CommoditySerialNumber string `json:"commodity_serial_number" gorm:"-"` // 商品编号
CategoryNumber string `json:"category_number" gorm:"-"` // 商品分类编号
}
// NewErpBillSn 生成零售订单号
func NewErpBillSn() string {
nowTime := time.Now()
rand.Seed(nowTime.UnixNano())
max := 1
for {
if max > 5 {
logger.Error("create sn err")
return ""
}
random := rand.Int31n(9999) + 1000
sn := fmt.Sprintf("%s%d", nowTime.Format("060102"), random)
exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM erp_order WHERE bill_sn='%s'", sn))
if err != nil {
logger.Error("exist sn err")
}
if !exist {
return sn
}
max++
}
}
// SelectDeliveryStock 选择发货库存
// commodityId: 商品ID
// storeId: 指定门店ID0 表示不指定)
// return: 选中的库存记录
func SelectDeliveryStock(commodityId, storeId uint32) (*ErpStockCommodity, error) {
// 先校验门店
if storeId == 0 {
return nil, fmt.Errorf("无法确定最近门店,请先设置 NearestStoreId")
}
var stock ErpStockCommodity
// 1⃣ 优先指定门店
err := DB.Table("erp_stock_commodity").
Where("erp_commodity_id = ? AND store_id = ? AND state = 1 AND count > 0",
commodityId, storeId).
Order("first_stock_time ASC").
First(&stock).Error
if err == nil {
return &stock, nil
}
// 2⃣ 如果指定门店无库存,则报错(不允许跨门店)
return nil, fmt.Errorf("商品 %d 在门店 %d 无可用库存", commodityId, storeId)
}
//func SelectDeliveryStock(commodityId, storeId uint32) (*ErpStockCommodity, error) {
// var stock ErpStockCommodity
//
// // 1⃣ 优先指定门店
// if storeId > 0 {
// err := DB.Table("erp_stock_commodity").
// Where("erp_commodity_id = ? AND store_id = ? AND state = 1 AND count > 0", commodityId, storeId).
// Order("first_stock_time ASC").
// First(&stock).Error
// if err == nil {
// return &stock, nil
// }
// }
//
// // 2⃣ 其他门店,优先库龄最久
// var otherStocks []ErpStockCommodity
// err := DB.Table("erp_stock_commodity").
// Where("erp_commodity_id = ? AND state = 1 AND count > 0", commodityId).
// Order("first_stock_time ASC").
// Find(&otherStocks).Error
// if err != nil {
// return nil, err
// }
// if len(otherStocks) == 0 {
// return nil, fmt.Errorf("无可用库存")
// }
//
// // 3⃣ 多个相同库龄,随机一个
// oldestTime := otherStocks[0].FirstStockTime
// var oldestStocks []ErpStockCommodity
// for _, s := range otherStocks {
// if s.FirstStockTime.Equal(oldestTime) {
// oldestStocks = append(oldestStocks, s)
// } else {
// break
// }
// }
//
// rand.Seed(time.Now().UnixNano())
// chosen := oldestStocks[rand.Intn(len(oldestStocks))]
// return &chosen, nil
//}
//// CreateErpOrder 根据小程序订单生成ERP零售订单
//func CreateErpOrder(goodsOrder *GoodsOrder, goods *Goods) error {
// logger.Infof("enter CreateErpOrder")
// logger.Infof("goods: %+v", goods)
// logger.Infof("goodsOrder: %+v", goodsOrder)
// tx := DB.Begin()
// if tx.Error != nil {
// return tx.Error
// }
//
// defer func() {
// if r := recover(); r != nil {
// logger.Errorf("事务 panic 回滚: %v", r)
// tx.Rollback()
// }
// }()
//
// // 1. 查找用户信息
// var user User
// if err := tx.Where("uid = ?", goodsOrder.Uid).First(&user).Error; err != nil {
// logger.Infof("用户 %d 不存在", goodsOrder.Uid)
// return fmt.Errorf("用户不存在: %w", err)
// }
//
// var orderCommodities []ErpOrderCommodity
// var totalAmount, totalRetailPrice, totalSalesProfit, totalStaffProfit float64
// var totalCount int32
//
// // 遍历小程序订单的商品明细
// // 选择库存:优先指定门店 → 其他门店库龄最久
// stock, err := SelectDeliveryStock(goods.ErpCommodityId, goodsOrder.NearestStoreId)
// if err != nil {
// tx.Rollback()
// logger.Infof("商品 %d 无库存: %v", goods.ErpCommodityId, err)
// return fmt.Errorf("商品 %d 无库存: %v", goods.ErpCommodityId, err)
// }
// logger.Infof("stock: %+v", stock)
//
// // 扣减库存
// err = tx.Table("erp_stock_commodity").Where("id = ?", stock.ID).
// Updates(map[string]interface{}{
// "state": SoldOut,
// "updated_at": time.Now(),
// }).Error // 状态更新为已销售
// if err != nil {
// logger.Error("update erp_stock_commodity err:", err)
// tx.Rollback()
// return fmt.Errorf("库存扣减失败: %v", err)
// }
//
// err = tx.Table("erp_stock").Where("store_id = ? and erp_commodity_id = ? AND count > 0",
// stock.StoreId, stock.ErpCommodityId).
// Updates(map[string]interface{}{
// "count": gorm.Expr("count - ?", 1),
// "updated_at": time.Now(),
// }).Error // 库存数量-1
// if err != nil {
// logger.Error("update erp_stock err:", err)
// tx.Rollback()
// return fmt.Errorf("库存不足")
// }
//
// // 金额计算
// receivedAmount := float64(goodsOrder.Quantity) * float64(goods.PriceRm/100)
// salesProfit := receivedAmount - stock.WholesalePrice*float64(goodsOrder.Quantity)
// staffProfit := receivedAmount - (stock.WholesalePrice+stock.StaffCostPrice)*float64(goodsOrder.Quantity)
//
// orderCommodities = append(orderCommodities, ErpOrderCommodity{
// ErpCategoryId: stock.ErpCategoryId,
// ErpCategoryName: stock.ErpCategoryName,
// ErpCommodityId: stock.ErpCommodityId,
// ErpCommodityName: stock.ErpCommodityName,
// ErpSupplierId: stock.ErpSupplierId,
// ErpSupplierName: stock.ErpSupplierName,
// IMEIType: stock.IMEIType,
// IMEI: "",
// PresentType: 1,
// RetailPrice: stock.RetailPrice,
// SalePrice: float64(goods.PriceRm / 100),
// Count: int32(goodsOrder.Quantity),
// Amount: receivedAmount,
// ReceivedAmount: receivedAmount,
// StaffCostPrice: stock.StaffCostPrice,
// WholesalePrice: stock.WholesalePrice,
// SalesProfit: salesProfit,
// StaffProfit: staffProfit,
// ErpStockCommodityID: fmt.Sprintf("%d", stock.ID),
// })
// logger.Infof("orderCommodities 长度: %d", len(orderCommodities))
//
// totalAmount += receivedAmount
// totalRetailPrice += stock.RetailPrice * float64(goodsOrder.Quantity)
// totalSalesProfit += salesProfit
// totalStaffProfit += staffProfit
// totalCount += int32(goodsOrder.Quantity)
//
// nowTime := time.Now()
// // ERP订单
// order := ErpOrder{
// BillSn: NewErpBillSn(),
// GoodsOrderId: goodsOrder.OrderId,
// RetailType: RetailTypeSale,
// Uid: int(goodsOrder.Uid),
// UserType: int(user.MemberLevel),
// Tel: user.Tel,
// StoreId: stock.StoreId,
// StoreName: stock.StoreName,
// MakerId: SysUserIdByAdmin,
// MakerName: SysUserNameByAdmin,
// MakerTime: nowTime,
// AuditorId: SysUserIdByAdmin,
// AuditorName: SysUserNameByAdmin,
// AuditTime: &nowTime,
// State: ErpOrderStateAudited,
// TotalRetailPrice: totalRetailPrice,
// TotalAmount: totalAmount,
// TotalCount: totalCount,
// TotalSalesProfit: totalSalesProfit,
// TotalStaffProfit: totalStaffProfit,
// PayStatus: HavePaid, // 已支付
// IsPrint: NoPrint, // 未打印
// PrintCount: 0,
// }
//
// if err = tx.Create(&order).Error; err != nil {
// logger.Infof("新建erp零售订单失败: %v", err)
// tx.Rollback()
// return fmt.Errorf("新建erp零售订单失败: %v", err)
// }
//
// // 绑定订单ID
// for i := range orderCommodities {
// orderCommodities[i].ErpOrderId = order.ID
// if err = tx.Create(&orderCommodities[i]).Error; err != nil {
// logger.Errorf("新建erp零售订单商品信息失败: %v", err)
// tx.Rollback()
// return fmt.Errorf("新建erp零售订单商品信息失败: %v", err)
// }
// }
//
// if err = tx.Commit().Error; err != nil {
// logger.Infof("提交事务失败: %v", err)
// return fmt.Errorf("提交事务失败: %v", err)
// }
//
// logger.Infof("leave CreateErpOrder")
// return nil
//}
// CreateErpOrder 根据小程序订单生成ERP零售订单支持多商品
func CreateErpOrder(goodsOrder *GoodsOrder, commodities []GoodsOrderCommodity) error {
logger.Infof("enter CreateErpOrder")
tx := DB.Begin()
if tx.Error != nil {
return tx.Error
}
defer func() {
if r := recover(); r != nil {
logger.Errorf("事务 panic 回滚: %v", r)
tx.Rollback()
}
}()
// 1. 查找用户信息
var user User
if err := tx.Where("uid = ?", goodsOrder.Uid).First(&user).Error; err != nil {
logger.Infof("用户 %d 不存在", goodsOrder.Uid)
return fmt.Errorf("用户不存在: %w", err)
}
var orderCommodities []ErpOrderCommodity
var totalAmount, totalRetailPrice, totalSalesProfit, totalStaffProfit float64
var totalCount int32
for _, commodity := range commodities {
var goods Goods
err := NewGoodsQuerySet(tx).GoodsIdEq(commodity.GoodsId).One(&goods)
if err != nil {
logger.Errorf("商品不存在: %d, err: %v", commodity.GoodsId, err)
tx.Rollback()
return err
}
// 选择库存
stock, err := SelectDeliveryStock(goods.ErpCommodityId, goodsOrder.NearestStoreId)
if err != nil {
tx.Rollback()
logger.Infof("商品 %d 无库存: %v", goods.ErpCommodityId, err)
return fmt.Errorf("商品 %d 无库存: %v", goods.ErpCommodityId, err)
}
// 扣减库存
err = tx.Table("erp_stock_commodity").Where("id = ?", stock.ID).
Updates(map[string]interface{}{"state": SoldOut, "updated_at": time.Now()}).Error
if err != nil {
tx.Rollback()
return fmt.Errorf("erp_stock_commodity 扣减库存失败: %v", err)
}
err = tx.Table("erp_stock").Where("store_id = ? AND erp_commodity_id = ? AND count > 0",
stock.StoreId, stock.ErpCommodityId).Updates(map[string]interface{}{
"count": gorm.Expr("count - ?", commodity.Quantity),
"updated_at": time.Now(),
}).Error
if err != nil {
tx.Rollback()
return fmt.Errorf("erp_stock 扣减库存失败: %v", err)
}
// 金额计算
receivedAmount := float64(commodity.Quantity) * float64(goods.PriceRm/100)
salesProfit := receivedAmount - stock.WholesalePrice*float64(commodity.Quantity)
staffProfit := receivedAmount - (stock.WholesalePrice+stock.StaffCostPrice)*float64(commodity.Quantity)
orderCommodities = append(orderCommodities, ErpOrderCommodity{
ErpCategoryId: stock.ErpCategoryId,
ErpCategoryName: stock.ErpCategoryName,
ErpCommodityId: stock.ErpCommodityId,
ErpCommodityName: stock.ErpCommodityName,
ErpSupplierId: stock.ErpSupplierId,
ErpSupplierName: stock.ErpSupplierName,
IMEIType: stock.IMEIType,
IMEI: "",
PresentType: 1,
RetailPrice: stock.RetailPrice,
SalePrice: float64(goods.PriceRm / 100),
Count: int32(commodity.Quantity),
Amount: receivedAmount,
ReceivedAmount: receivedAmount,
StaffCostPrice: stock.StaffCostPrice,
WholesalePrice: stock.WholesalePrice,
SalesProfit: salesProfit,
StaffProfit: staffProfit,
ErpStockCommodityID: fmt.Sprintf("%d", stock.ID),
})
totalAmount += receivedAmount
totalRetailPrice += stock.RetailPrice * float64(commodity.Quantity)
totalSalesProfit += salesProfit
totalStaffProfit += staffProfit
totalCount += int32(commodity.Quantity)
}
// 创建ERP订单
nowTime := time.Now()
order := ErpOrder{
BillSn: NewErpBillSn(),
GoodsOrderId: goodsOrder.OrderId,
RetailType: RetailTypeSale,
Uid: int(goodsOrder.Uid),
UserType: int(user.MemberLevel),
Tel: user.Tel,
StoreId: goodsOrder.NearestStoreId,
StoreName: goodsOrder.NearestStoreName,
MakerId: SysUserIdByAdmin,
MakerName: SysUserNameByAdmin,
MakerTime: nowTime,
AuditorId: SysUserIdByAdmin,
AuditorName: SysUserNameByAdmin,
AuditTime: &nowTime,
State: ErpOrderStateAudited,
TotalRetailPrice: totalRetailPrice,
TotalAmount: totalAmount,
TotalCount: totalCount,
TotalSalesProfit: totalSalesProfit,
TotalStaffProfit: totalStaffProfit,
PayStatus: HavePaid,
IsPrint: NoPrint,
PrintCount: 0,
}
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
return fmt.Errorf("创建ERP订单失败: %v", err)
}
// 绑定商品明细
for i := range orderCommodities {
orderCommodities[i].ErpOrderId = order.ID
if err := tx.Create(&orderCommodities[i]).Error; err != nil {
tx.Rollback()
return fmt.Errorf("创建ERP商品明细失败: %v", err)
}
}
if err := tx.Commit().Error; err != nil {
return fmt.Errorf("提交事务失败: %v", err)
}
logger.Infof("leave CreateErpOrder")
return nil
}