1.在这个接口里 /api/product/updateNameAndImages,添加多张图片时,并未覆盖原来的多张图片。(Y)

2.从系统导出的excel数据,在外部对excel某一列进行更改时,新增的要回传到原来的地方;并对改动的地方进行覆盖。
3.销售单管理、出库管理、发货单三个接口里面展示第三方订单编号和快递单号
4.选择多个仓库时,只要选择发货单子就会报错
5.在这个/api/split-account-deduction-log/create接口里,当传参时,如果参数 total_amount 是0,则会报错 {"code":204,"data":{},"msg":"TotalAmount不能为空"} 0是金额数字,不能当空值进行判断(T)
传递参数created_by,没有往数据表里写入
6.商品销毁的同时写入日志,也能通过读取这个日志,还原销毁的商品。传出这个新增的接口
7.新增一个不需要签名认证的分帐扣钱日志列表接口,新增一个返回字段buniness_no,并对这个字段进行模糊查询。
测试接口:/open/split-account-deduction-log/list
8.增加个新接口:首先 调用 /api/sales-order/create 创建销售订单的时候会锁定库存,
现在我需要一个解锁库存的接口,传递参数是订单编号
POST /api/sales-order/unlock-inventory // 解锁销售订单库存
/api/split-account-deduction-log/update /api/sales-order/unlock-inventory 在这两个接口里不需要签名认证
/api/sales-order/unlock-inventory 在这个接口里面返回解锁的所有商品信息
/api/split-account-deduction-log/update  在这个接口里面的status也需要更改,status没有变化
This commit is contained in:
Administrator 2026-06-24 09:41:12 +08:00
parent 4253c6ade0
commit a2ea0c3a40
25 changed files with 1550 additions and 119 deletions

View File

@ -552,6 +552,25 @@ func (r *ProcessApi) CancelSalesOrder(c *gin.Context) {
systemRes.OkWithMessage("销售订单取消成功,库存已释放", c) systemRes.OkWithMessage("销售订单取消成功,库存已释放", c)
} }
// UnlockSalesOrderInventory 解锁销售订单库存
func (r *ProcessApi) UnlockSalesOrderInventory(c *gin.Context) {
var req systemReq.UnlockSalesOrderInventoryRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "解锁销售订单库存请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
resp, err := processService.UnlockSalesOrderInventory(req.SoNo, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "解锁销售订单库存异常", err, c, req)
return
}
systemRes.OkWithDetailed(resp, "库存解锁成功", c)
}
// CancelOutboundWave 取消出库波次 // CancelOutboundWave 取消出库波次
func (r *ProcessApi) CancelOutboundWave(c *gin.Context) { func (r *ProcessApi) CancelOutboundWave(c *gin.Context) {
var req systemReq.CancelOutboundWaveRequest var req systemReq.CancelOutboundWaveRequest

View File

@ -3,6 +3,7 @@ package controllers
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io"
"net/http" "net/http"
"net/url" "net/url"
"psi/constant" "psi/constant"
@ -11,6 +12,7 @@ import (
systemRes "psi/models/response" systemRes "psi/models/response"
"psi/service" "psi/service"
"psi/utils" "psi/utils"
"strconv"
"strings" "strings"
) )
@ -505,6 +507,38 @@ func parseImageFromForm(c *gin.Context) ([]string, error) {
images = append(images, imageStr) images = append(images, imageStr)
} }
// 如果解析到了数组格式,直接返回
if len(images) > 0 {
return images, nil
}
// 方式2: 尝试解析 liveimage 逗号分隔格式
liveimageStr := c.PostForm("liveimage")
if liveimageStr != "" {
// 按逗号分割
parts := strings.Split(liveimageStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
images = append(images, part)
}
}
return images, nil
}
// 方式3: 尝试解析 live_image 逗号分隔格式
liveImageStr := c.PostForm("live_image")
if liveImageStr != "" {
// 按逗号分割
parts := strings.Split(liveImageStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
images = append(images, part)
}
}
return images, nil
}
return images, nil return images, nil
} }
@ -550,16 +584,20 @@ func (r *ProductApi) UpdateProductNameAndImages(c *gin.Context) {
return return
} }
// 处理live_image参数: 如果表单未绑定,尝试从原始表单解析 // 始终从表单解析图片参数,确保能接收到空数组或新图片
if len(req.LiveImage) == 0 { image, err := parseImageFromForm(c)
image, err := parseImageFromForm(c) if err != nil {
if err != nil { systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c) return
return }
} // 如果解析到了图片,使用解析的结果;否则保留ShouldBind的结果
if len(image) > 0 || c.PostForm("live_image[0]") != "" {
req.LiveImage = image req.LiveImage = image
} }
fmt.Printf("【UpdateProductNameAndImages】最终请求参数 - ProductID: %d, Name: %s, LiveImage数量: %d, LiveImage: %+v\n",
req.ProductID, req.Name, len(req.LiveImage), req.LiveImage)
if err := productService.UpdateProductNameAndImages(req, database.GetDB(c)); err != nil { if err := productService.UpdateProductNameAndImages(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "修改商品信息异常", err, c, req) utils.FailWithRequestLog(constant.LoggerChannelWork, "修改商品信息异常", err, c, req)
return return
@ -567,3 +605,121 @@ func (r *ProductApi) UpdateProductNameAndImages(c *gin.Context) {
systemRes.OkWithMessage("修改成功", c) systemRes.OkWithMessage("修改成功", c)
} }
// ReimportProducts 将导出的Excel修改后重新导入覆盖更新或新增
func (r *ProductApi) ReimportProducts(c *gin.Context) {
warehouseIDStr := c.PostForm("warehouse_id")
if warehouseIDStr == "" {
systemRes.FailWithValidateMessage("warehouse_id不能为空", c)
return
}
warehouseID, err := strconv.ParseInt(warehouseIDStr, 10, 64)
if err != nil || warehouseID <= 0 {
systemRes.FailWithValidateMessage("warehouse_id格式错误", c)
return
}
file, _, err := c.Request.FormFile("file")
if err != nil {
systemRes.FailWithValidateMessage("请上传Excel文件", c)
return
}
defer file.Close()
fileBytes, err := io.ReadAll(file)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品回传导入-读取文件失败", err, c, nil)
return
}
result, err := productService.ReimportProducts(fileBytes, warehouseID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品回传导入异常", err, c, nil)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "导入完成",
"data": result,
})
}
// DestroyProduct 销毁商品
func (r *ProductApi) DestroyProduct(c *gin.Context) {
var req systemReq.DestroyProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销毁商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
logID, err := productService.DestroyProduct(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销毁商品异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"destroy_log_id": logID}, "商品已销毁", c)
}
// RestoreProduct 还原商品
func (r *ProductApi) RestoreProduct(c *gin.Context) {
var req systemReq.RestoreProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "还原商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := productService.RestoreProduct(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "还原商品异常", err, c, req)
return
}
systemRes.OkWithMessage("商品已还原", c)
}
// GetDestroyLogList 获取销毁日志列表
func (r *ProductApi) GetDestroyLogList(c *gin.Context) {
var req systemReq.GetDestroyLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销毁日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetDestroyLogList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销毁日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetDestroyLogDetail 获取销毁日志详情
func (r *ProductApi) GetDestroyLogDetail(c *gin.Context) {
var req systemReq.GetDestroyLogDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销毁日志详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetDestroyLogDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销毁日志详情异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}

View File

@ -17,6 +17,27 @@ type SplitAccountDeductionLogApi struct{}
var splitAccountDeductionLogService = service.SplitAccountDeductionLogService{} var splitAccountDeductionLogService = service.SplitAccountDeductionLogService{}
// GetOpenSplitAccountDeductionLogList 公开获取分账扣钱日志列表(无需签名认证)
func (r *SplitAccountDeductionLogApi) GetOpenSplitAccountDeductionLogList(c *gin.Context) {
var req systemReq.GetSplitAccountDeductionLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "公开分账扣钱日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := splitAccountDeductionLogService.GetSplitAccountDeductionLogList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "公开分账扣钱日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetSplitAccountDeductionLogList 获取分账扣钱日志列表 // GetSplitAccountDeductionLogList 获取分账扣钱日志列表
func (r *SplitAccountDeductionLogApi) GetSplitAccountDeductionLogList(c *gin.Context) { func (r *SplitAccountDeductionLogApi) GetSplitAccountDeductionLogList(c *gin.Context) {
var req systemReq.GetSplitAccountDeductionLogListRequest var req systemReq.GetSplitAccountDeductionLogListRequest
@ -79,8 +100,25 @@ func (r *SplitAccountDeductionLogApi) CreateSplitAccountDeductionLog(c *gin.Cont
return return
} }
if req.TotalAmount == nil {
systemRes.FailWithValidateMessage("TotalAmount不能为空", c)
return
}
if req.DeductionAmount == nil {
systemRes.FailWithValidateMessage("DeductionAmount不能为空", c)
return
}
if req.RemainingAmount == nil {
systemRes.FailWithValidateMessage("RemainingAmount不能为空", c)
return
}
userInfo := utils.GetUserInfo(c) userInfo := utils.GetUserInfo(c)
id, err := splitAccountDeductionLogService.CreateSplitAccountDeductionLog(req, userInfo.Username, database.GetDB(c)) createdBy := req.CreatedBy
if createdBy == "" {
createdBy = userInfo.Username
}
id, err := splitAccountDeductionLogService.CreateSplitAccountDeductionLog(req, createdBy, database.GetDB(c))
if err != nil { if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建分账扣钱日志异常", err, c, req) utils.FailWithRequestLog(constant.LoggerChannelWork, "创建分账扣钱日志异常", err, c, req)
return return

View File

@ -245,6 +245,11 @@ func Init() {
log.Fatal("ProductLog表迁移失败:", err) log.Fatal("ProductLog表迁移失败:", err)
} }
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品销毁日志表'").AutoMigrate(&models.ProductDestroyLog{})
if err != nil {
log.Fatal("ProductDestroyLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户类型表'").AutoMigrate(&models.UserType{}) err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户类型表'").AutoMigrate(&models.UserType{})
if err != nil { if err != nil {
log.Fatal("UserType表迁移失败:", err) log.Fatal("UserType表迁移失败:", err)

View File

@ -0,0 +1,32 @@
package models
import "gorm.io/datatypes"
// ProductDestroyLog 商品销毁日志表
type ProductDestroyLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:日志ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:商品ID"`
Barcode string `json:"barcode" gorm:"size:100;not null;default:'';index;comment:条码"`
ProductSnapshot datatypes.JSON `json:"product_snapshot" gorm:"type:json;not null;comment:商品快照(完整product表数据)"`
ProductBookSnapshot datatypes.JSON `json:"product_book_snapshot" gorm:"type:json;comment:书籍快照(product_book表数据)"`
InventorySnapshot datatypes.JSON `json:"inventory_snapshot" gorm:"type:json;comment:库存汇总快照"`
InventoryDetailSnapshot datatypes.JSON `json:"inventory_detail_snapshot" gorm:"type:json;comment:库存明细快照"`
DestroyedBy string `json:"destroyed_by" gorm:"size:100;not null;default:'';comment:销毁人"`
DestroyedByID int64 `json:"destroyed_by_id" gorm:"not null;default:0;comment:销毁人ID"`
DestroyedAt int64 `json:"destroyed_at" gorm:"type:bigint;not null;default:0;comment:销毁时间戳"`
RestoredBy string `json:"restored_by" gorm:"size:100;not null;default:'';comment:还原人"`
RestoredByID int64 `json:"restored_by_id" gorm:"not null;default:0;comment:还原人ID"`
RestoredAt int64 `json:"restored_at" gorm:"type:bigint;not null;default:0;comment:还原时间戳"`
Status int8 `json:"status" gorm:"type:tinyint(1);not null;default:0;comment:状态(0:已销毁,1:已还原)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除"`
}
func (ProductDestroyLog) TableName() string {
return "product_destroy_log"
}
func (ProductDestroyLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品销毁日志表'"
}

View File

@ -2,14 +2,16 @@ package request
// GetOutboundOrderListRequest 获取出库单列表请求 // GetOutboundOrderListRequest 获取出库单列表请求
type GetOutboundOrderListRequest struct { type GetOutboundOrderListRequest struct {
Page int `form:"page"` Page int `form:"page"`
PageSize int `form:"page_size"` PageSize int `form:"page_size"`
OutNo string `form:"out_no"` OutNo string `form:"out_no"`
Status int8 `form:"status"` Status int8 `form:"status"`
CustomerID int64 `form:"customer_id"` CustomerID int64 `form:"customer_id"`
WarehouseID int64 `form:"warehouse_id"` WarehouseID int64 `form:"warehouse_id"`
StartDate int64 `form:"start_date"` StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"` EndDate int64 `form:"end_date"`
AssociationOrderNo string `form:"association_order_no"`
LogisticsNo string `form:"logistics_no"`
} }
// GetOutboundOrderDetailRequest 获取出库单详情请求 // GetOutboundOrderDetailRequest 获取出库单详情请求

View File

@ -133,6 +133,11 @@ type CancelSalesOrderRequest struct {
OrderID int64 `form:"order_id" binding:"required"` // 订单ID OrderID int64 `form:"order_id" binding:"required"` // 订单ID
} }
// UnlockSalesOrderInventoryRequest 解锁销售订单库存请求
type UnlockSalesOrderInventoryRequest struct {
SoNo string `form:"so_no" binding:"required"` // 订单编号
}
type CancelOutboundWaveRequest struct { type CancelOutboundWaveRequest struct {
WaveID int64 `form:"wave_id" binding:"required"` // 波次ID WaveID int64 `form:"wave_id" binding:"required"` // 波次ID
} }

View File

@ -197,3 +197,28 @@ type UpdateProductNameAndImagesRequest struct {
Name string `form:"name"` // 商品名称(可选) Name string `form:"name"` // 商品名称(可选)
LiveImage []string `form:"live_image[]"` // 商品实拍图(可选,支持单图或多图) LiveImage []string `form:"live_image[]"` // 商品实拍图(可选,支持单图或多图)
} }
// 追加到 models/request/product.go 末尾
// DestroyProductRequest 销毁商品请求
type DestroyProductRequest struct {
ProductID int64 `form:"product_id" binding:"required"` // 商品ID
}
// RestoreProductRequest 还原商品请求
type RestoreProductRequest struct {
DestroyLogID int64 `form:"destroy_log_id" binding:"required"` // 销毁日志ID
}
// GetDestroyLogListRequest 获取销毁日志列表请求
type GetDestroyLogListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Keyword string `form:"keyword"` // 搜索条码或商品名称
Status *int8 `form:"status"` // 0:已销毁 1:已还原 nil:全部
}
// GetDestroyLogDetailRequest 获取销毁日志详情请求
type GetDestroyLogDetailRequest struct {
ID int64 `form:"id" binding:"required"` // 销毁日志ID
}

View File

@ -0,0 +1,10 @@
package request
type ProductReimportResult struct {
UpdatedCount int `json:"updated_count"`
CreatedCount int `json:"created_count"`
SkippedCount int `json:"skipped_count"`
FailCount int `json:"fail_count"`
FailDetails []string `json:"fail_details,omitempty"`
Message string `json:"message"`
}

View File

@ -2,14 +2,16 @@ package request
// GetSalesOrderListRequest 获取销售订单列表请求 // GetSalesOrderListRequest 获取销售订单列表请求
type GetSalesOrderListRequest struct { type GetSalesOrderListRequest struct {
Page int `form:"page"` Page int `form:"page"`
PageSize int `form:"page_size"` PageSize int `form:"page_size"`
SoNo string `form:"so_no"` SoNo string `form:"so_no"`
Status int8 `form:"status"` Status int8 `form:"status"`
CustomerID int64 `form:"customer_id"` CustomerID int64 `form:"customer_id"`
WarehouseID int64 `form:"warehouse_id"` WarehouseID int64 `form:"warehouse_id"`
StartDate int64 `form:"start_date"` StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"` EndDate int64 `form:"end_date"`
AssociationOrderNo string `form:"association_order_no"`
LogisticsNo string `form:"logistics_no"`
} }
// GetSalesOrderDetailRequest 获取销售订单详情请求 // GetSalesOrderDetailRequest 获取销售订单详情请求
@ -27,4 +29,12 @@ type GetSalesOrderDetailListRequest struct {
//WarehouseID int64 `form:"warehouse_id"` //WarehouseID int64 `form:"warehouse_id"`
//StartDate int64 `form:"start_date"` //StartDate int64 `form:"start_date"`
//EndDate int64 `form:"end_date"` //EndDate int64 `form:"end_date"`
SoNo string `form:"so_no"`
Status int8 `form:"status"`
CustomerID int64 `form:"customer_id"`
WarehouseID int64 `form:"warehouse_id"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
AssociationOrderNo string `form:"association_order_no"`
LogisticsNo string `form:"logistics_no"`
} }

View File

@ -2,13 +2,15 @@ package request
// GetShippingOrderListRequest 获取发货单列表请求 // GetShippingOrderListRequest 获取发货单列表请求
type GetShippingOrderListRequest struct { type GetShippingOrderListRequest struct {
Page int `form:"page" json:"page" binding:"omitempty,min=1"` Page int `form:"page" json:"page" binding:"omitempty,min=1"`
PageSize int `form:"page_size" json:"page_size" binding:"omitempty,min=1,max=100"` PageSize int `form:"page_size" json:"page_size" binding:"omitempty,min=1,max=100"`
Status int8 `form:"status" json:"status"` Status int8 `form:"status" json:"status"`
CustomerID int64 `form:"customer_id" json:"customer_id"` CustomerID int64 `form:"customer_id" json:"customer_id"`
ShippingNo string `form:"shipping_no" json:"shipping_no"` ShippingNo string `form:"shipping_no" json:"shipping_no"`
StartDate int64 `form:"start_date" json:"start_date"` StartDate int64 `form:"start_date" json:"start_date"`
EndDate int64 `form:"end_date" json:"end_date"` EndDate int64 `form:"end_date" json:"end_date"`
AssociationOrderNo string `form:"association_order_no" json:"association_order_no"`
LogisticsNo string `form:"logistics_no" json:"logistics_no"`
} }
// GetShippingOrderDetailRequest 获取发货单详情请求 // GetShippingOrderDetailRequest 获取发货单详情请求
@ -18,11 +20,13 @@ type GetShippingOrderDetailRequest struct {
// GetShippingOrderDetailListRequest 获取发货单详情列表请求 // GetShippingOrderDetailListRequest 获取发货单详情列表请求
type GetShippingOrderDetailListRequest struct { type GetShippingOrderDetailListRequest struct {
Page int `form:"page" json:"page" binding:"omitempty,min=1"` Page int `form:"page" json:"page" binding:"omitempty,min=1"`
PageSize int `form:"page_size" json:"page_size" binding:"omitempty,min=1,max=100"` PageSize int `form:"page_size" json:"page_size" binding:"omitempty,min=1,max=100"`
Status int8 `form:"status" json:"status"` Status int8 `form:"status" json:"status"`
CustomerID int64 `form:"customer_id" json:"customer_id"` CustomerID int64 `form:"customer_id" json:"customer_id"`
ShippingNo string `form:"shipping_no" json:"shipping_no"` ShippingNo string `form:"shipping_no" json:"shipping_no"`
StartDate int64 `form:"start_date" json:"start_date"` StartDate int64 `form:"start_date" json:"start_date"`
EndDate int64 `form:"end_date" json:"end_date"` EndDate int64 `form:"end_date" json:"end_date"`
AssociationOrderNo string `form:"association_order_no" json:"association_order_no"`
LogisticsNo string `form:"logistics_no" json:"logistics_no"`
} }

View File

@ -12,13 +12,14 @@ type GetSplitAccountDeductionLogListRequest struct {
// AddSplitAccountDeductionLogRequest 添加分账扣钱日志请求 // AddSplitAccountDeductionLogRequest 添加分账扣钱日志请求
type AddSplitAccountDeductionLogRequest struct { type AddSplitAccountDeductionLogRequest struct {
BusinessNo string `form:"business_no" binding:"required"` // 业务单号 BusinessNo string `form:"business_no" json:"business_no" binding:"required"` // 业务单号
ConfigID int64 `form:"config_id" binding:"required"` // 分账配置ID ConfigID int64 `form:"config_id" json:"config_id" binding:"required"` // 分账配置ID
ConfigName string `form:"config_name" binding:"required"` // 分账配置名称 ConfigName string `form:"config_name" json:"config_name" binding:"required"` // 分账配置名称
DeductionDetails string `form:"deduction_details" binding:"required"` // 扣钱详情 DeductionDetails string `form:"deduction_details" json:"deduction_details" binding:"required"` // 扣钱详情
TotalAmount float64 `form:"total_amount" binding:"required"` // 总金额 TotalAmount *float64 `form:"total_amount" json:"total_amount"` // 总金额传0为有效值
DeductionAmount float64 `form:"deduction_amount" binding:"required"` // 扣钱金额 DeductionAmount *float64 `form:"deduction_amount" json:"deduction_amount"` // 扣钱金额
RemainingAmount float64 `form:"remaining_amount" binding:"required"` // 剩余金额 RemainingAmount *float64 `form:"remaining_amount" json:"remaining_amount"` // 剩余金额
CreatedBy string `form:"created_by" json:"created_by"` // 创建人/系统
} }
// UpdateSplitAccountDeductionLogRequest 更新分账扣钱日志请求 // UpdateSplitAccountDeductionLogRequest 更新分账扣钱日志请求

View File

@ -46,13 +46,17 @@ type OutboundOrderItem struct {
WarehouseID int64 `json:"warehouse_id"` WarehouseID int64 `json:"warehouse_id"`
WarehouseName string `json:"warehouse_name"` WarehouseName string `json:"warehouse_name"`
ShopList []OutboundShopInfo `json:"shop_list"` ShopList []OutboundShopInfo `json:"shop_list"`
Status int8 `json:"status"`
StatusText string `json:"status_text"` AssociationOrderNo string `json:"association_order_no"` //第三方订单编号
Operator string `json:"operator"` LogisticsNo string `json:"logistics_no"` //快递单号
OperatorID int64 `json:"operator_id"`
Remark string `json:"remark"` Status int8 `json:"status"`
CreatedAt int64 `json:"created_at"` StatusText string `json:"status_text"`
UpdatedAt int64 `json:"updated_at"` Operator string `json:"operator"`
OperatorID int64 `json:"operator_id"`
Remark string `json:"remark"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
} }
// OutboundOrderDetailResponse 发货单详情响应 // OutboundOrderDetailResponse 发货单详情响应
@ -99,7 +103,7 @@ type OutboundOrderDetailItem struct {
} }
// ConvertOutboundOrderToItem 将发货单模型转换为响应项 // ConvertOutboundOrderToItem 将发货单模型转换为响应项
func ConvertOutboundOrderToItem(order models.OutboundOrder, customerName string, warehouseName string, shopList []OutboundShopInfo) OutboundOrderItem { func ConvertOutboundOrderToItem(order models.OutboundOrder, customerName string, warehouseName string, shopList []OutboundShopInfo, associationOrderNo string, logisticsNo string) OutboundOrderItem {
return OutboundOrderItem{ return OutboundOrderItem{
ID: order.ID, ID: order.ID,
OutboundNo: order.OutNo, OutboundNo: order.OutNo,
@ -109,13 +113,17 @@ func ConvertOutboundOrderToItem(order models.OutboundOrder, customerName string,
WarehouseID: order.WarehouseID, WarehouseID: order.WarehouseID,
WarehouseName: warehouseName, WarehouseName: warehouseName,
ShopList: shopList, ShopList: shopList,
Status: order.Status,
StatusText: GetOutboundOrderStatusText(order.Status), AssociationOrderNo: associationOrderNo,
Operator: order.Operator, LogisticsNo: logisticsNo,
OperatorID: order.OperatorID,
Remark: order.Remark, Status: order.Status,
CreatedAt: order.CreatedAt, StatusText: GetOutboundOrderStatusText(order.Status),
UpdatedAt: order.UpdatedAt, Operator: order.Operator,
OperatorID: order.OperatorID,
Remark: order.Remark,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
} }
} }

View File

@ -49,3 +49,15 @@ type ReceivingItemResponse struct {
Quantity int64 `json:"quantity"` Quantity int64 `json:"quantity"`
UnitName string `json:"unit_name"` UnitName string `json:"unit_name"`
} }
type UnlockInventoryItemResponse struct {
ProductID int64 `json:"product_id"`
ProductName string `json:"product_name"`
ProductCode string `json:"product_code"`
UnlockedQuantity int64 `json:"unlocked_quantity"`
}
type UnlockInventoryResponse struct {
SoNo string `json:"so_no"`
AssociationOrderNo string `json:"association_order_no"`
Items []UnlockInventoryItemResponse `json:"items"`
}

View File

@ -6,10 +6,10 @@ import (
) )
type ProductListResponse struct { type ProductListResponse struct {
List []ProductItem `json:"list"` List []ProductItem `json:"list"` // 商品列表
Total int64 `json:"total"` Total int64 `json:"total"` // 商品总数
Page int `json:"page"` Page int `json:"page"` // 当前页码
PageSize int `json:"pageSize"` PageSize int `json:"pageSize"` // 每页数量
LocatedCount int64 `json:"located_count"` // 已落位商品数 LocatedCount int64 `json:"located_count"` // 已落位商品数
UnlocatedCount int64 `json:"unlocated_count"` // 未落位商品数 UnlocatedCount int64 `json:"unlocated_count"` // 未落位商品数
EnabledCount int64 `json:"enabled_count"` // 启用中商品数 EnabledCount int64 `json:"enabled_count"` // 启用中商品数

View File

@ -0,0 +1,57 @@
package response
// DestroyLogItem 销毁日志列表项
type DestroyLogItem struct {
ID int64 `json:"id"` //用的这个字段
ProductID int64 `json:"product_id"`
Barcode string `json:"barcode"`
ProductName string `json:"product_name"`
DestroyedBy string `json:"destroyed_by"`
DestroyedAt int64 `json:"destroyed_at"`
RestoredBy string `json:"restored_by"`
RestoredAt int64 `json:"restored_at"`
Status int8 `json:"status"`
StatusText string `json:"status_text"`
CreatedAt int64 `json:"created_at"`
}
// DestroyLogListResponse 销毁日志列表响应
type DestroyLogListResponse struct {
List []DestroyLogItem `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
// DestroyLogDetailResponse 销毁日志详情响应
type DestroyLogDetailResponse struct {
ID int64 `json:"id"`
ProductID int64 `json:"product_id"`
Barcode string `json:"barcode"`
ProductSnapshot string `json:"product_snapshot"`
ProductBookSnapshot string `json:"product_book_snapshot"`
InventorySnapshot string `json:"inventory_snapshot"`
InventoryDetailSnapshot string `json:"inventory_detail_snapshot"`
DestroyedBy string `json:"destroyed_by"`
DestroyedByID int64 `json:"destroyed_by_id"`
DestroyedAt int64 `json:"destroyed_at"`
RestoredBy string `json:"restored_by"`
RestoredByID int64 `json:"restored_by_id"`
RestoredAt int64 `json:"restored_at"`
Status int8 `json:"status"`
StatusText string `json:"status_text"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
// GetDestroyLogStatusText 获取销毁日志状态文本
func GetDestroyLogStatusText(status int8) string {
switch status {
case 0:
return "已销毁"
case 1:
return "已还原"
default:
return "未知"
}
}

View File

@ -51,6 +51,7 @@ type SalesOrderItem struct {
SalesPerson string `json:"sales_person"` SalesPerson string `json:"sales_person"`
SalesPersonID int64 `json:"sales_person_id"` SalesPersonID int64 `json:"sales_person_id"`
Remark string `json:"remark"` Remark string `json:"remark"`
LogisticsNo string `json:"logistics_no"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
} }
@ -70,6 +71,8 @@ type SalesOrderDetailResponse struct {
StatusText string `json:"status_text"` StatusText string `json:"status_text"`
SalesPerson string `json:"sales_person"` SalesPerson string `json:"sales_person"`
SalesPersonID int64 `json:"sales_person_id"` SalesPersonID int64 `json:"sales_person_id"`
AssociationOrderNo string `json:"association_order_no"`
LogisticsNo string `json:"logistics_no"`
Remark string `json:"remark"` Remark string `json:"remark"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
@ -95,13 +98,15 @@ type SalesOrderDetailItem struct {
ReceiverName string `json:"receiver_name"` ReceiverName string `json:"receiver_name"`
ReceiverPhone string `json:"receiver_phone"` ReceiverPhone string `json:"receiver_phone"`
ReceiverAddress string `json:"receiver_address"` ReceiverAddress string `json:"receiver_address"`
LogisticsCompany string `json:"logistics_company"`
LogisticsNo string `json:"logistics_no"`
LocationCode string `json:"location_code"` LocationCode string `json:"location_code"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
} }
// ConvertSalesOrderToItem 将销售订单模型转换为响应项 // ConvertSalesOrderToItem 将销售订单模型转换为响应项
func ConvertSalesOrderToItem(order models.SalesOrder, customerName string, warehouseName string) SalesOrderItem { func ConvertSalesOrderToItem(order models.SalesOrder, customerName string, warehouseName string, logisticsNo string) SalesOrderItem {
return SalesOrderItem{ return SalesOrderItem{
ID: order.ID, ID: order.ID,
SoNo: order.SoNo, SoNo: order.SoNo,
@ -122,6 +127,7 @@ func ConvertSalesOrderToItem(order models.SalesOrder, customerName string, wareh
SalesPerson: order.SalesPerson, SalesPerson: order.SalesPerson,
SalesPersonID: order.SalesPersonID, SalesPersonID: order.SalesPersonID,
Remark: order.Remark, Remark: order.Remark,
LogisticsNo: logisticsNo,
CreatedAt: order.CreatedAt, CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt, UpdatedAt: order.UpdatedAt,
} }
@ -143,10 +149,14 @@ func ConvertSalesOrderToDetail(order models.SalesOrder, customerName string, war
StatusText: GetSalesOrderStatusText(order.Status), StatusText: GetSalesOrderStatusText(order.Status),
SalesPerson: order.SalesPerson, SalesPerson: order.SalesPerson,
SalesPersonID: order.SalesPersonID, SalesPersonID: order.SalesPersonID,
Remark: order.Remark,
CreatedAt: order.CreatedAt, AssociationOrderNo: order.AssociationOrderNo,
UpdatedAt: order.UpdatedAt, LogisticsNo: "",
Items: items,
Remark: order.Remark,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
Items: items,
} }
} }

View File

@ -66,6 +66,8 @@ type ShippingOrderItem struct {
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at"`
Remark string `json:"remark"` Remark string `json:"remark"`
ShopList []OutboundShopInfo `json:"shop_list"` ShopList []OutboundShopInfo `json:"shop_list"`
AssociationOrderNo string `json:"association_order_no"`
LogisticsNo string `json:"logistics_no"`
} }
// ShippingOrderDetailResponse 发货单详情响应 // ShippingOrderDetailResponse 发货单详情响应
@ -124,7 +126,7 @@ type ShippingOrderDetailItem struct {
} }
// ConvertShippingOrderToItem 将发货单模型转换为响应项 // ConvertShippingOrderToItem 将发货单模型转换为响应项
func ConvertShippingOrderToItem(order models.ShippingOrder, customerName string, shopList []OutboundShopInfo) ShippingOrderItem { func ConvertShippingOrderToItem(order models.ShippingOrder, customerName string, shopList []OutboundShopInfo, associationOrderNo string, logisticsNo string) ShippingOrderItem {
return ShippingOrderItem{ return ShippingOrderItem{
ID: order.ID, ID: order.ID,
ShippingNo: order.ShippingNo, ShippingNo: order.ShippingNo,
@ -140,6 +142,8 @@ func ConvertShippingOrderToItem(order models.ShippingOrder, customerName string,
CreatedAt: order.CreatedAt, CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt, UpdatedAt: order.UpdatedAt,
Remark: order.Remark, Remark: order.Remark,
AssociationOrderNo: associationOrderNo,
LogisticsNo: logisticsNo,
} }
} }

View File

@ -90,9 +90,12 @@ func initRouter() (r *gin.Engine) {
public.GET("/split-account-deduction-log/list", splitAccountDeductionLogApi.GetSplitAccountDeductionLogList) // 获取分账扣钱日志列表 public.GET("/split-account-deduction-log/list", splitAccountDeductionLogApi.GetSplitAccountDeductionLogList) // 获取分账扣钱日志列表
public.GET("/split-account-deduction-log/detail/:id", splitAccountDeductionLogApi.GetSplitAccountDeductionLogDetail) // 获取分账扣钱日志详情 public.GET("/split-account-deduction-log/detail/:id", splitAccountDeductionLogApi.GetSplitAccountDeductionLogDetail) // 获取分账扣钱日志详情
public.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账 public.GET("/open/split-account-deduction-log/list", splitAccountDeductionLogApi.GetOpenSplitAccountDeductionLogList)
public.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账 public.POST("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账扣钱日志
public.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账 public.POST("/sales-order/unlock-inventory", processApi.UnlockSalesOrderInventory) // 解锁销售订单库存
/* public.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账
public.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账
public.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账*/
} }
@ -155,9 +158,17 @@ func initRouter() (r *gin.Engine) {
auth.POST("/product/updateNameAndImages", productApi.UpdateProductNameAndImages) // 修改商品名称和实拍图 auth.POST("/product/updateNameAndImages", productApi.UpdateProductNameAndImages) // 修改商品名称和实拍图
auth.POST("/product/delete", productApi.DeleteProduct) // 删除商品 auth.POST("/product/delete", productApi.DeleteProduct) // 删除商品
auth.POST("/product/retry-out-task", productApi.RetryOutTask) // 重新出库 auth.POST("/product/retry-out-task", productApi.RetryOutTask) // 重新出库
auth.GET("/product/export", productApi.ExportProducts) // 导出商品 auth.GET("/product/export", productApi.ExportProducts) // 导出商品
auth.POST("/product/reimport", productApi.ReimportProducts) // 导出修改后回传导入
auth.POST("/product/destroy", productApi.DestroyProduct) // 销毁商品
auth.POST("/product/restore", productApi.RestoreProduct) // 还原商品
auth.GET("/product/destroy-log/list", productApi.GetDestroyLogList) // 销毁日志列表
auth.GET("/product/destroy-log/detail", productApi.GetDestroyLogDetail) // 销毁日志详情
auth.GET("/product/shop-detail", productApi.GetShopProductDetail) // 获取商品在店铺的详情 auth.GET("/product/shop-detail", productApi.GetShopProductDetail) // 获取商品在店铺的详情
// 条形码 // 条形码
auth.POST("/barcode/generate", barcodeApi.GenerateBarcode) // 生成条形码 auth.POST("/barcode/generate", barcodeApi.GenerateBarcode) // 生成条形码
@ -181,8 +192,10 @@ func initRouter() (r *gin.Engine) {
auth.GET("/outbound/detail/:id", processApi.GetOutboundDetail) // 获取出库单详情 auth.GET("/outbound/detail/:id", processApi.GetOutboundDetail) // 获取出库单详情
auth.POST("/shipping-order/create", processApi.CreateShippingOrder) // 创建发货单 auth.POST("/shipping-order/create", processApi.CreateShippingOrder) // 创建发货单
auth.POST("/shipping-order/update", processApi.UpdateShippingLogistics) // 更新发货单物流信息 auth.POST("/shipping-order/update", processApi.UpdateShippingLogistics) // 更新发货单物流信息
auth.POST("/sales-order/cancel", processApi.CancelSalesOrder) // 取消销售订单 auth.POST("/sales-order/cancel", processApi.CancelSalesOrder) // 取消销售订
auth.POST("/wave/outbound/cancel", processApi.CancelOutboundWave) // 取消出库波次 //auth.POST("/sales-order/unlock-inventory", processApi.UnlockSalesOrderInventory) // 解锁销售订单库存
auth.POST("/wave/outbound/cancel", processApi.CancelOutboundWave) // 取消出库波次
// 盘库 // 盘库
auth.POST("/stock_check/adjust", processApi.AdjustInventory) // 盘库 auth.POST("/stock_check/adjust", processApi.AdjustInventory) // 盘库
auth.POST("/stock_check/return", processApi.ReturnInventory) // 盘库退货 auth.POST("/stock_check/return", processApi.ReturnInventory) // 盘库退货
@ -251,12 +264,14 @@ func initRouter() (r *gin.Engine) {
auth.POST("/split-account-config/create", splitAccountConfigApi.CreateSplitAccountConfig) // 创建分账配置 auth.POST("/split-account-config/create", splitAccountConfigApi.CreateSplitAccountConfig) // 创建分账配置
auth.PUT("/split-account-config/update", splitAccountConfigApi.UpdateSplitAccountConfig) // 更新分账配置 auth.PUT("/split-account-config/update", splitAccountConfigApi.UpdateSplitAccountConfig) // 更新分账配置
auth.DELETE("/split-account-config/delete", splitAccountConfigApi.DeleteSplitAccountConfig) // 删除分账配置 auth.DELETE("/split-account-config/delete", splitAccountConfigApi.DeleteSplitAccountConfig) // 删除分账配置
// 分账扣钱日志管理 /*// 分账扣钱日志管理
/*auth.GET("/split-account-deduction-log/list", splitAccountDeductionLogApi.GetSplitAccountDeductionLogList) // 获取分账扣钱日志列表 auth.GET("/split-account-deduction-log/list", splitAccountDeductionLogApi.GetSplitAccountDeductionLogList) // 获取分账扣钱日志列表
auth.GET("/split-account-deduction-log/detail/:id", splitAccountDeductionLogApi.GetSplitAccountDeductionLogDetail) // 获取分账扣钱日志详情 auth.GET("/split-account-deduction-log/detail/:id", splitAccountDeductionLogApi.GetSplitAccountDeductionLogDetail) // 获取分账扣钱日志详情
auth.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账 //public.GET("/open/split-account-deduction-log/list", splitAccountDeductionLogApi.GetOpenSplitAccountDeductionLogList) // 公开获取分账扣钱日志列表(无需签名认证)*/
auth.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账
auth.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账*/ auth.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账
//auth.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账
auth.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账*/
// 产品日志管理 // 产品日志管理
auth.GET("/product_log/list", productApi.GetProductLogList) // 获取产品日志列表 auth.GET("/product_log/list", productApi.GetProductLogList) // 获取产品日志列表
auth.POST("/product_log/save", productApi.SaveProductLog) // 保存产品日志 auth.POST("/product_log/save", productApi.SaveProductLog) // 保存产品日志

View File

@ -45,6 +45,21 @@ func (s *OutboundService) GetOutboundOrderList(req systemReq.GetOutboundOrderLis
query = query.Where("outbound_order.created_at <= ?", req.EndDate) query = query.Where("outbound_order.created_at <= ?", req.EndDate)
} }
if req.AssociationOrderNo != "" {
subQuery := databaseConn.Table("outbound_order_item").
Select("outbound_order_item.out_order_id").
Joins("INNER JOIN sales_order ON outbound_order_item.sales_order_id = sales_order.id AND sales_order.is_del = 0").
Where("outbound_order_item.is_del = 0 AND sales_order.association_order_no LIKE ?", "%"+req.AssociationOrderNo+"%")
query = query.Where("outbound_order.id IN (?)", subQuery)
}
if req.LogisticsNo != "" {
subQuery := databaseConn.Table("outbound_order_item").
Select("outbound_order_item.out_order_id").
Joins("INNER JOIN sales_order_item ON outbound_order_item.sales_order_id = sales_order_item.sales_order_id AND sales_order_item.is_del = 0").
Where("outbound_order_item.is_del = 0 AND sales_order_item.logistics_no LIKE ?", "%"+req.LogisticsNo+"%")
query = query.Where("outbound_order.id IN (?)", subQuery)
}
var total int64 var total int64
if err := query.Count(&total).Error; err != nil { if err := query.Count(&total).Error; err != nil {
return nil, utils.NewError("查询总数失败") return nil, utils.NewError("查询总数失败")
@ -94,11 +109,27 @@ func (s *OutboundService) GetOutboundOrderList(req systemReq.GetOutboundOrderLis
}) })
} }
var associationOrderNos string
databaseConn.Table("outbound_order_item").
Select("GROUP_CONCAT(DISTINCT so.association_order_no SEPARATOR ', ')").
Joins("INNER JOIN sales_order so ON outbound_order_item.sales_order_id = so.id AND so.is_del = 0").
Where("outbound_order_item.out_order_id = ? AND outbound_order_item.is_del = ? AND so.association_order_no != ''", order.ID, 0).
Scan(&associationOrderNos)
var logisticsNos string
databaseConn.Table("outbound_order_item").
Select("GROUP_CONCAT(DISTINCT soi.logistics_no SEPARATOR ', ')").
Joins("INNER JOIN sales_order_item soi ON outbound_order_item.sales_order_id = soi.sales_order_id AND outbound_order_item.product_id = soi.product_id AND soi.is_del = 0").
Where("outbound_order_item.out_order_id = ? AND outbound_order_item.is_del = ? AND soi.logistics_no != ''", order.ID, 0).
Scan(&logisticsNos)
orderItems = append(orderItems, systemRes.ConvertOutboundOrderToItem( orderItems = append(orderItems, systemRes.ConvertOutboundOrderToItem(
order.OutboundOrder, order.OutboundOrder,
order.CustomerName, order.CustomerName,
order.WarehouseName, order.WarehouseName,
shopList, shopList,
associationOrderNos,
logisticsNos,
)) ))
} }

View File

@ -2062,7 +2062,7 @@ func (s *ProcessService) CreateShippingOrder(req systemReq.CreateShippingOrderRe
} }
} }
customerID := outboundOrders[0].CustomerID /*customerID := outboundOrders[0].CustomerID
warehouseID := outboundOrders[0].WarehouseID warehouseID := outboundOrders[0].WarehouseID
for i, order := range outboundOrders[1:] { for i, order := range outboundOrders[1:] {
@ -2072,6 +2072,13 @@ func (s *ProcessService) CreateShippingOrder(req systemReq.CreateShippingOrderRe
if order.WarehouseID != warehouseID { if order.WarehouseID != warehouseID {
return fmt.Errorf("所有出库单必须属于同一个仓库,订单[%s]与第一个订单仓库不一致", outboundOrders[i+1].OutNo) return fmt.Errorf("所有出库单必须属于同一个仓库,订单[%s]与第一个订单仓库不一致", outboundOrders[i+1].OutNo)
} }
}*/
customerID := outboundOrders[0].CustomerID
for i, order := range outboundOrders[1:] {
if order.CustomerID != customerID {
return fmt.Errorf("所有出库单必须属于同一个客户,订单[%s]与第一个订单客户不一致", outboundOrders[i+1].OutNo)
}
} }
for _, order := range outboundOrders { for _, order := range outboundOrders {
@ -3497,6 +3504,110 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
}) })
} }
// UnlockSalesOrderInventory 解锁销售订单库存(不取消订单)
func (s *ProcessService) UnlockSalesOrderInventory(soNo string, operator string, operatorID int64, db ...*gorm.DB) (*systemRes.UnlockInventoryResponse, error) {
databaseConn := database.OptionalDB(db...)
now := time.Now().Unix()
var unlockResp *systemRes.UnlockInventoryResponse
err := executeInTransactionWithDB(databaseConn, func(tx *gorm.DB) error {
var salesOrder models.SalesOrder
if err := tx.Where("so_no = ? AND is_del = 0", soNo).First(&salesOrder).Error; err != nil {
return fmt.Errorf("销售订单不存在: %v", err)
}
if salesOrder.Status == constant.SalesStatusShipped {
return fmt.Errorf("订单已发货,无法解锁库存")
}
if salesOrder.Status == constant.SalesStatusCancelled {
return fmt.Errorf("订单已取消,无法解锁库存")
}
if salesOrder.Status != constant.SalesStatusAllocated && salesOrder.Status != constant.SalesStatusPicking {
return fmt.Errorf("订单状态不允许解锁库存,当前状态: %s", getSalesStatusText(salesOrder.Status))
}
var orderItems []models.SalesOrderItem
if err := tx.Where("sales_order_id = ? AND is_del = 0", salesOrder.ID).Find(&orderItems).Error; err != nil {
return fmt.Errorf("查询订单明细失败: %v", err)
}
// 收集需要解锁的商品ID
var productIDs []int64
for _, item := range orderItems {
if item.AllocatedQuantity > 0 {
productIDs = append(productIDs, item.ProductID)
}
}
// 查询商品信息
var products []models.Product
productMap := make(map[int64]models.Product)
if len(productIDs) > 0 {
if err := tx.Where("id IN ? AND is_del = 0", productIDs).Find(&products).Error; err != nil {
return fmt.Errorf("查询商品信息失败: %v", err)
}
for _, p := range products {
productMap[p.ID] = p
}
}
// 解锁库存并构建响应
var responseItems []systemRes.UnlockInventoryItemResponse
for _, item := range orderItems {
if item.AllocatedQuantity > 0 {
if err := s.unlockInventory(tx, salesOrder.WarehouseID, item.ProductID, item.AllocatedQuantity, now); err != nil {
return fmt.Errorf("解锁库存失败[商品ID=%d]: %v", item.ProductID, err)
}
productName := ""
productCode := ""
if p, ok := productMap[item.ProductID]; ok {
productName = p.Name
productCode = p.Barcode
}
responseItems = append(responseItems, systemRes.UnlockInventoryItemResponse{
ProductID: item.ProductID,
ProductName: productName,
ProductCode: productCode,
UnlockedQuantity: item.AllocatedQuantity,
})
}
}
if err := tx.Model(&models.SalesOrderItem{}).
Where("sales_order_id = ? AND is_del = 0", salesOrder.ID).
Updates(map[string]interface{}{
"allocated_quantity": 0,
"updated_at": now,
}).Error; err != nil {
return fmt.Errorf("重置订单明细已分配数量失败: %v", err)
}
if err := tx.Model(&salesOrder).Updates(map[string]interface{}{
"status": constant.SalesStatusConfirmed,
"updated_at": now,
}).Error; err != nil {
return fmt.Errorf("更新销售订单状态失败: %v", err)
}
unlockResp = &systemRes.UnlockInventoryResponse{
SoNo: soNo,
AssociationOrderNo: salesOrder.AssociationOrderNo,
Items: responseItems,
}
return nil
})
if err != nil {
return nil, err
}
return unlockResp, nil
}
// processInventoryOperationForAdjustment 处理盘库调整的库存汇总操作 // processInventoryOperationForAdjustment 处理盘库调整的库存汇总操作
func (s *ProcessService) processInventoryOperationForAdjustment(tx *gorm.DB, opKey inventoryKey, locationID int64, quantity int64, orderNo string, operator string, operatorID int64, now int64, remark string) (*models.InventoryLog, error) { func (s *ProcessService) processInventoryOperationForAdjustment(tx *gorm.DB, opKey inventoryKey, locationID int64, quantity int64, orderNo string, operator string, operatorID int64, now int64, remark string) (*models.InventoryLog, error) {
@ -4076,7 +4187,19 @@ func (s *ProcessService) syncProductsToExternal(receivingOrderID, waveTaskID, us
"isbn": group.product.Barcode, "isbn": group.product.Barcode,
"book_name": group.product.Name, "book_name": group.product.Name,
"image_object": map[string]interface{}{ "image_object": map[string]interface{}{
"carousel_url_array": group.imgList, "carousel_url_array": group.imgList,
"white_background_url": []string{},
"detail_url_object": map[string]interface{}{},
"introduction_url": []string{},
"catalogue_url": []string{},
"live_shooting_url": []string{},
"other_url": []string{},
"default_image_url": func() string {
if len(group.imgList) > 0 {
return group.imgList[0]
}
return ""
}(),
}, },
}, },
"detail": map[string]interface{}{ "detail": map[string]interface{}{
@ -4133,7 +4256,19 @@ func (s *ProcessService) syncProductsToExternal(receivingOrderID, waveTaskID, us
"isbn": isbn, "isbn": isbn,
"book_name": product.Name, "book_name": product.Name,
"image_object": map[string]interface{}{ "image_object": map[string]interface{}{
"carousel_url_array": imgList, "carousel_url_array": imgList,
"white_background_url": []string{},
"detail_url_object": map[string]interface{}{},
"introduction_url": []string{},
"catalogue_url": []string{},
"live_shooting_url": []string{},
"other_url": []string{},
"default_image_url": func() string {
if len(imgList) > 0 {
return imgList[0]
}
return ""
}(),
}, },
}, },
"detail": map[string]interface{}{ "detail": map[string]interface{}{

View File

@ -29,6 +29,332 @@ type OutTaskInfo struct {
ShopList []systemRes.ShopInfo ShopList []systemRes.ShopInfo
} }
// ParseExportedExcel 解析系统导出的Excel文件
func (s *ProductService) ParseExportedExcel(fileBytes []byte) ([]map[string]string, error) {
f, err := excelize.OpenReader(bytes.NewReader(fileBytes))
if err != nil {
return nil, fmt.Errorf("读取Excel失败: %v", err)
}
defer f.Close()
sheet := f.GetSheetName(0)
rows, err := f.GetRows(sheet)
if err != nil {
return nil, fmt.Errorf("获取工作表数据失败: %v", err)
}
if len(rows) < 2 {
return nil, fmt.Errorf("表格至少需要包含表头和1行数据")
}
var result []map[string]string
for _, row := range rows[1:] {
if len(row) == 0 {
continue
}
getCol := func(idx int) string {
if idx < len(row) {
return strings.TrimSpace(row[idx])
}
return ""
}
isbn := getCol(0)
name := getCol(3)
if isbn == "" && name == "" {
continue
}
result = append(result, map[string]string{
"isbn": isbn,
"name": name,
"quantity": getCol(4),
"price": getCol(6),
"location_code": getCol(7),
"image": getCol(9),
})
}
return result, nil
}
// ReimportProducts 将导出的Excel修改后导回覆盖已有商品或新增
func (s *ProductService) ReimportProducts(fileBytes []byte, warehouseID int64, db ...*gorm.DB) (*systemReq.ProductReimportResult, error) {
databaseConn := database.OptionalDB(db...)
rows, err := s.ParseExportedExcel(fileBytes)
if err != nil {
return nil, err
}
if len(rows) == 0 {
return nil, fmt.Errorf("Excel中没有有效数据行")
}
result := &systemReq.ProductReimportResult{
FailDetails: make([]string, 0),
}
now := time.Now().Unix()
isbns := make([]string, 0, len(rows))
locationCodes := make([]string, 0)
for _, row := range rows {
if row["isbn"] != "" {
isbns = append(isbns, row["isbn"])
}
if code := row["location_code"]; code != "" {
locationCodes = append(locationCodes, code)
}
}
existingProducts := make(map[string]models.Product)
if len(isbns) > 0 {
var products []models.Product
if err := databaseConn.Where("barcode IN ? AND is_del = ?", isbns, 0).Find(&products).Error; err != nil {
return nil, fmt.Errorf("查询已有商品失败: %v", err)
}
for _, p := range products {
existingProducts[p.Barcode] = p
}
}
locationMap := make(map[string]models.Location)
if len(locationCodes) > 0 {
var locations []models.Location
if err := databaseConn.Where("code IN ? AND warehouse_id = ? AND is_del = ?", locationCodes, warehouseID, 0).Find(&locations).Error; err != nil {
return nil, fmt.Errorf("查询库位失败: %v", err)
}
for _, loc := range locations {
locationMap[loc.Code] = loc
}
}
tx := databaseConn.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
for _, row := range rows {
isbn := row["isbn"]
name := row["name"]
var locationID int64
if code := row["location_code"]; code != "" {
if loc, exists := locationMap[code]; exists {
locationID = loc.ID
} else {
newLoc := models.Location{
WarehouseID: warehouseID,
Code: code,
Type: 1,
Capacity: 255,
Status: 1,
Sort: 0,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
if err := tx.Create(&newLoc).Error; err != nil {
tx.Rollback()
return nil, fmt.Errorf("创建库位失败(code=%s): %v", code, err)
}
locationMap[code] = newLoc
locationID = newLoc.ID
}
}
salePrice := int64(0)
if row["price"] != "" {
if f, err := strconv.ParseFloat(row["price"], 64); err == nil {
salePrice = int64(f * 100)
}
}
quantity := int64(0)
if row["quantity"] != "" {
if v, err := strconv.ParseInt(row["quantity"], 10, 64); err == nil {
quantity = v
}
}
var liveImage datatypes.JSON
if row["image"] != "" {
b, _ := json.Marshal([]string{row["image"]})
liveImage = datatypes.JSON(b)
} else {
liveImage = datatypes.JSON("[]")
}
if existing, found := existingProducts[isbn]; found {
updates := map[string]interface{}{
"updated_at": now,
}
if name != "" && name != existing.Name {
updates["name"] = name
}
if salePrice > 0 && salePrice != existing.SalePrice {
updates["sale_price"] = salePrice
}
if liveImage != nil && string(liveImage) != string(existing.LiveImage) {
updates["live_image"] = liveImage
}
if locationID > 0 && locationID != existing.LocationID {
updates["location_id"] = locationID
}
if err := tx.Model(&existing).Updates(updates).Error; err != nil {
result.FailDetails = append(result.FailDetails, fmt.Sprintf("ISBN=%s 更新商品失败: %v", isbn, err))
result.FailCount++
continue
}
if quantity > 0 {
var invDetail models.InventoryDetail
err := tx.Where("product_id = ? AND warehouse_id = ? AND is_del = ?", existing.ID, warehouseID, 0).First(&invDetail).Error
if err == nil {
oldQty := invDetail.Quantity
tx.Model(&invDetail).Updates(map[string]interface{}{
"quantity": quantity,
"available_quantity": quantity - invDetail.LockedQuantity,
"updated_at": now,
})
if quantity != oldQty {
tx.Create(&models.InventoryLog{
WarehouseID: warehouseID,
LocationID: invDetail.LocationID,
ProductID: existing.ID,
BatchNo: invDetail.BatchNo,
ChangeType: constant.InventoryChangeAdjustment,
ChangeQuantity: quantity - oldQty,
BeforeQuantity: oldQty,
AfterQuantity: quantity,
RelatedOrderType: "",
RelatedOrderNo: "",
Operator: "",
OperatorID: 0,
Remark: "Excel回传覆盖",
CreatedAt: now,
IsDel: 0,
})
}
} else if err == gorm.ErrRecordNotFound && quantity > 0 {
locID := locationID
if locID == 0 {
locID = existing.LocationID
}
tx.Create(&models.InventoryDetail{
WarehouseID: warehouseID,
LocationID: locID,
ProductID: existing.ID,
BatchNo: utils.GenerateExcelImportBatchNo(),
Quantity: quantity,
LockedQuantity: 0,
AvailableQuantity: quantity,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
})
var inv models.Inventory
invErr := tx.Where("product_id = ? AND warehouse_id = ? AND is_del = ?", existing.ID, warehouseID, 0).First(&inv).Error
if invErr == nil {
tx.Model(&inv).Updates(map[string]interface{}{
"quantity": quantity,
"available_quantity": quantity - inv.LockedQuantity,
"updated_at": now,
})
} else if invErr == gorm.ErrRecordNotFound {
tx.Create(&models.Inventory{
WarehouseID: warehouseID,
ProductID: existing.ID,
BatchNo: utils.GenerateExcelImportBatchNo(),
Quantity: quantity,
LockedQuantity: 0,
AvailableQuantity: quantity,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
})
}
}
}
result.UpdatedCount++
} else {
if isbn == "" {
result.FailDetails = append(result.FailDetails, fmt.Sprintf("商品名称=%s ISBN为空跳过新增", name))
result.FailCount++
continue
}
product := models.Product{
CategoryID: 1,
StandardProductID: 1,
Name: name,
Barcode: isbn,
Price: 0,
SalePrice: salePrice,
Cost: 0,
LiveImage: liveImage,
Status: 1,
WarehouseID: warehouseID,
LocationID: locationID,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
if err := tx.Create(&product).Error; err != nil {
result.FailDetails = append(result.FailDetails, fmt.Sprintf("ISBN=%s 新增商品失败: %v", isbn, err))
result.FailCount++
continue
}
if quantity > 0 {
batchNo := utils.GenerateExcelImportBatchNo()
tx.Create(&models.Inventory{
WarehouseID: warehouseID,
ProductID: product.ID,
BatchNo: batchNo,
Quantity: quantity,
LockedQuantity: 0,
AvailableQuantity: quantity,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
})
tx.Create(&models.InventoryDetail{
WarehouseID: warehouseID,
LocationID: locationID,
ProductID: product.ID,
BatchNo: batchNo,
Quantity: quantity,
LockedQuantity: 0,
AvailableQuantity: quantity,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
})
}
result.CreatedCount++
}
}
if err := tx.Commit().Error; err != nil {
return nil, fmt.Errorf("提交事务失败: %v", err)
}
result.Message = fmt.Sprintf("导入完成:更新%d个新增%d个失败%d个", result.UpdatedCount, result.CreatedCount, result.FailCount)
if result.FailCount > 0 && len(result.FailDetails) <= 10 {
result.Message += ",失败详情:" + strings.Join(result.FailDetails, "; ")
}
return result, nil
}
// 获取商品列表 // 获取商品列表
func (s *ProductService) GetProductList(req systemReq.GetProductListRequest, db ...*gorm.DB) (*systemRes.ProductListResponse, error) { func (s *ProductService) GetProductList(req systemReq.GetProductListRequest, db ...*gorm.DB) (*systemRes.ProductListResponse, error) {
databaseConn := database.OptionalDB(db...) databaseConn := database.OptionalDB(db...)
@ -674,18 +1000,24 @@ func (s *ProductService) UpdateProductNameAndImages(req systemReq.UpdateProductN
return fmt.Errorf("查询商品失败: %v", err) return fmt.Errorf("查询商品失败: %v", err)
} }
fmt.Printf("【UpdateProductNameAndImages Service】原商品图片: %s\n", string(product.LiveImage))
updates := make(map[string]interface{}) updates := make(map[string]interface{})
hasUpdate := false hasUpdate := false
if req.Name != "" { if req.Name != "" {
updates["name"] = req.Name updates["name"] = req.Name
hasUpdate = true hasUpdate = true
fmt.Printf("【UpdateProductNameAndImages Service】更新名称: %s\n", req.Name)
} }
if len(req.LiveImage) > 0 { // 只要LiveImage不为nil,就覆盖(包括空数组)
// 注意: Gin的ShouldBind对于未传的数组字段会设为nil,对于传的空数组会设为[]string{}
if req.LiveImage != nil {
jsonBytes, _ := json.Marshal(req.LiveImage) jsonBytes, _ := json.Marshal(req.LiveImage)
updates["live_image"] = datatypes.JSON(jsonBytes) updates["live_image"] = datatypes.JSON(jsonBytes)
hasUpdate = true hasUpdate = true
fmt.Printf("【UpdateProductNameAndImages Service】覆盖图片为: %s (数量: %d)\n", string(jsonBytes), len(req.LiveImage))
} }
if !hasUpdate { if !hasUpdate {
@ -698,6 +1030,7 @@ func (s *ProductService) UpdateProductNameAndImages(req systemReq.UpdateProductN
return fmt.Errorf("更新商品失败: %v", err) return fmt.Errorf("更新商品失败: %v", err)
} }
fmt.Printf("【UpdateProductNameAndImages Service】更新成功\n")
return nil return nil
} }
@ -1903,12 +2236,9 @@ func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTas
} }
for _, imgStr := range rawImgList { for _, imgStr := range rawImgList {
urls := strings.Split(imgStr, ",") imgStr = strings.TrimSpace(imgStr)
for _, url := range urls { if imgStr != "" {
url = strings.TrimSpace(url) imgList = append(imgList, imgStr)
if url != "" {
imgList = append(imgList, url)
}
} }
} }
} }
@ -1925,7 +2255,19 @@ func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTas
"isbn": isbn, "isbn": isbn,
"book_name": product.Name, "book_name": product.Name,
"image_object": map[string]interface{}{ "image_object": map[string]interface{}{
"carousel_url_array": imgList, "carousel_url_array": imgList,
"white_background_url": []string{},
"detail_url_object": map[string]interface{}{},
"introduction_url": []string{},
"catalogue_url": []string{},
"live_shooting_url": []string{},
"other_url": []string{},
"default_image_url": func() string {
if len(imgList) > 0 {
return imgList[0]
}
return ""
}(),
}, },
}, },
"detail": map[string]interface{}{ "detail": map[string]interface{}{
@ -2043,12 +2385,9 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
} }
for _, imgStr := range rawImgList { for _, imgStr := range rawImgList {
urls := strings.Split(imgStr, ",") imgStr = strings.TrimSpace(imgStr)
for _, url := range urls { if imgStr != "" {
url = strings.TrimSpace(url) imgList = append(imgList, imgStr)
if url != "" {
imgList = append(imgList, url)
}
} }
} }
} }
@ -2058,13 +2397,10 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
} }
msgJSON, _ := json.Marshal(msgData) msgJSON, _ := json.Marshal(msgData)
// 获取库存数量
var stock int64 var stock int64
// 获取运费模板首费
var cost int64 var cost int64
if hasWaveTask { if hasWaveTask {
// 有波次任务明细:通过波次任务 -> 波次头 -> 仓库 -> 物流模板
var logistics models.Logistics var logistics models.Logistics
err := db.Table("logistics"). err := db.Table("logistics").
Select("logistics.fir_price"). Select("logistics.fir_price").
@ -2080,7 +2416,6 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
cost = int64(logistics.FirPrice * 100) cost = int64(logistics.FirPrice * 100)
stock = waveTaskDetail.PlannedQuantity stock = waveTaskDetail.PlannedQuantity
} else { } else {
// 无波次任务明细(同步商品):通过库存明细 -> 仓库 -> 物流模板
var inventoryDetail models.InventoryDetail var inventoryDetail models.InventoryDetail
if err := db.Where("product_id = ? AND is_del = ?", product.ID, 0).First(&inventoryDetail).Error; err != nil { if err := db.Where("product_id = ? AND is_del = ?", product.ID, 0).First(&inventoryDetail).Error; err != nil {
return fmt.Errorf("查询库存明细失败: %v", err) return fmt.Errorf("查询库存明细失败: %v", err)
@ -2108,7 +2443,19 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
"isbn": isbn, "isbn": isbn,
"book_name": product.Name, "book_name": product.Name,
"image_object": map[string]interface{}{ "image_object": map[string]interface{}{
"carousel_url_array": imgList, "carousel_url_array": imgList,
"white_background_url": []string{},
"detail_url_object": map[string]interface{}{},
"introduction_url": []string{},
"catalogue_url": []string{},
"live_shooting_url": []string{},
"other_url": []string{},
"default_image_url": func() string {
if len(imgList) > 0 {
return imgList[0]
}
return ""
}(),
}, },
}, },
"detail": map[string]interface{}{ "detail": map[string]interface{}{
@ -2130,7 +2477,7 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
taskID := fmt.Sprintf("%d", outTask.OutTaskID) taskID := fmt.Sprintf("%d", outTask.OutTaskID)
allBody := strings.Join(bodyList, "") // 直接无缝拼接(和服务端一致) allBody := strings.Join(bodyList, "")
signParams := map[string]string{ signParams := map[string]string{
"task_id": taskID, "task_id": taskID,
@ -2139,7 +2486,6 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
sign := utils.SignParams(signParams) sign := utils.SignParams(signParams)
// 发送请求
url := config.AppConfig.ExternalAPI.SyncTaskBodyURL url := config.AppConfig.ExternalAPI.SyncTaskBodyURL
resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign) resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign)
if err != nil { if err != nil {
@ -2248,12 +2594,9 @@ func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product
} }
for _, imgStr := range rawImgList { for _, imgStr := range rawImgList {
urls := strings.Split(imgStr, ",") imgStr = strings.TrimSpace(imgStr)
for _, url := range urls { if imgStr != "" {
url = strings.TrimSpace(url) imgList = append(imgList, imgStr)
if url != "" {
imgList = append(imgList, url)
}
} }
} }
} }
@ -2269,7 +2612,19 @@ func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product
"isbn": isbn, "isbn": isbn,
"book_name": product.Name, "book_name": product.Name,
"image_object": map[string]interface{}{ "image_object": map[string]interface{}{
"carousel_url_array": imgList, "carousel_url_array": imgList,
"white_background_url": []string{},
"detail_url_object": map[string]interface{}{},
"introduction_url": []string{},
"catalogue_url": []string{},
"live_shooting_url": []string{},
"other_url": []string{},
"default_image_url": func() string {
if len(imgList) > 0 {
return imgList[0]
}
return ""
}(),
}, },
}, },
"detail": map[string]interface{}{ "detail": map[string]interface{}{
@ -2291,7 +2646,7 @@ func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product
taskID := fmt.Sprintf("%d", outTask.OutTaskID) taskID := fmt.Sprintf("%d", outTask.OutTaskID)
allBody := strings.Join(bodyList, "") // 直接无缝拼接(和服务端一致) allBody := strings.Join(bodyList, "")
signParams := map[string]string{ signParams := map[string]string{
"task_id": taskID, "task_id": taskID,
@ -2300,7 +2655,6 @@ func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product
sign := utils.SignParams(signParams) sign := utils.SignParams(signParams)
// 发送请求
url := config.AppConfig.ExternalAPI.SyncTaskBodyURL url := config.AppConfig.ExternalAPI.SyncTaskBodyURL
resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign) resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign)
if err != nil { if err != nil {
@ -3197,3 +3551,399 @@ func (s *ProductService) getShopTypeName(shopType int8) string {
mainDB.Create(data) mainDB.Create(data)
}() }()
}*/ }*/
// 追加到 service/product.go 末尾
// DestroyProduct 销毁商品(写入快照日志 + 逻辑删除商品)
func (s *ProductService) DestroyProduct(req systemReq.DestroyProductRequest, operator string, operatorID int64, db ...*gorm.DB) (int64, error) {
databaseConn := database.OptionalDB(db...)
var product models.Product
if err := databaseConn.Where("id = ? AND is_del = ?", req.ProductID, 0).First(&product).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, fmt.Errorf("商品不存在或已被销毁")
}
return 0, fmt.Errorf("查询商品失败: %v", err)
}
now := time.Now().Unix()
productSnapshot, _ := json.Marshal(product)
var productBookSnapshot datatypes.JSON
var book models.ProductBook
if product.Barcode != "" {
tableName := models.ProductBookTableName(product.Barcode)
err := databaseConn.Table(tableName).Where("barcode = ? AND is_del = ?", product.Barcode, 0).First(&book).Error
if err == nil {
productBookSnapshot, _ = json.Marshal(book)
}
}
var inventorySnapshot datatypes.JSON
var inventory models.Inventory
inventoryIDs := make([]int64, 0)
if err := databaseConn.Model(&models.Inventory{}).
Where("product_id = ? AND is_del = ?", req.ProductID, 0).Find(&inventory).Error; err == nil && inventory.ID > 0 {
inventoryIDs = append(inventoryIDs, inventory.ID)
inventorySnapshot, _ = json.Marshal(inventory)
}
var inventoryDetailSnapshot datatypes.JSON
var inventoryDetails []models.InventoryDetail
databaseConn.Where("product_id = ? AND is_del = ?", req.ProductID, 0).Find(&inventoryDetails)
if len(inventoryDetails) > 0 {
inventoryDetailSnapshot, _ = json.Marshal(inventoryDetails)
}
destroyLog := models.ProductDestroyLog{
ProductID: req.ProductID,
Barcode: product.Barcode,
ProductSnapshot: productSnapshot,
ProductBookSnapshot: productBookSnapshot,
InventorySnapshot: inventorySnapshot,
InventoryDetailSnapshot: inventoryDetailSnapshot,
DestroyedBy: operator,
DestroyedByID: operatorID,
DestroyedAt: now,
Status: 0,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
err := executeInTransactionWithDB(databaseConn, func(tx *gorm.DB) error {
if err := tx.Create(&destroyLog).Error; err != nil {
return fmt.Errorf("写入销毁日志失败: %v", err)
}
if err := tx.Model(&models.Product{}).
Where("id = ? AND is_del = ?", req.ProductID, 0).
Updates(map[string]interface{}{
"is_del": 1,
"updated_at": now,
}).Error; err != nil {
return fmt.Errorf("删除商品失败: %v", err)
}
if err := tx.Model(&models.Inventory{}).
Where("product_id = ? AND is_del = ?", req.ProductID, 0).
Updates(map[string]interface{}{
"is_del": 1,
"updated_at": now,
}).Error; err != nil {
return fmt.Errorf("删除库存汇总失败: %v", err)
}
if err := tx.Model(&models.InventoryDetail{}).
Where("product_id = ? AND is_del = ?", req.ProductID, 0).
Updates(map[string]interface{}{
"is_del": 1,
"updated_at": now,
}).Error; err != nil {
return fmt.Errorf("删除库存明细失败: %v", err)
}
if product.Barcode != "" && len(productBookSnapshot) > 0 {
tableName := models.ProductBookTableName(product.Barcode)
if err := tx.Table(tableName).
Where("barcode = ? AND is_del = ?", product.Barcode, 0).
Updates(map[string]interface{}{
"is_del": 1,
"updated_at": now,
}).Error; err != nil {
return fmt.Errorf("删除书籍反射失败: %v", err)
}
}
for _, _ = range inventoryIDs {
tx.Create(&models.InventoryLog{
WarehouseID: inventory.WarehouseID,
LocationID: 0,
ProductID: req.ProductID,
BatchNo: inventory.BatchNo,
ChangeType: constant.InventoryChangeAdjustment,
ChangeQuantity: -inventory.Quantity,
BeforeQuantity: inventory.Quantity,
AfterQuantity: 0,
RelatedOrderType: "",
RelatedOrderNo: "",
Operator: operator,
OperatorID: operatorID,
Remark: "商品销毁",
CreatedAt: now,
IsDel: 0,
})
}
return nil
})
if err != nil {
return 0, err
}
return destroyLog.ID, nil
}
// RestoreProduct 还原商品(从销毁日志中恢复)
func (s *ProductService) RestoreProduct(req systemReq.RestoreProductRequest, operator string, operatorID int64, db ...*gorm.DB) error {
databaseConn := database.OptionalDB(db...)
var destroyLog models.ProductDestroyLog
if err := databaseConn.Where("id = ? AND is_del = ?", req.DestroyLogID, 0).First(&destroyLog).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("销毁日志不存在")
}
return fmt.Errorf("查询销毁日志失败: %v", err)
}
if destroyLog.Status == 1 {
return fmt.Errorf("该商品已还原,请勿重复操作")
}
var product models.Product
if err := json.Unmarshal(destroyLog.ProductSnapshot, &product); err != nil {
return fmt.Errorf("解析商品快照失败: %v", err)
}
product.IsDel = 0
product.UpdatedAt = time.Now().Unix()
err := executeInTransactionWithDB(databaseConn, func(tx *gorm.DB) error {
var existing models.Product
checkErr := tx.Where("barcode = ? AND is_del = ?", product.Barcode, 0).First(&existing).Error
if checkErr == nil && existing.ID != product.ID {
return fmt.Errorf("条码[%s]已存在其他商品(ID=%d),无法还原", product.Barcode, existing.ID)
}
var oldProduct models.Product
if err := tx.Where("id = ?", product.ID).First(&oldProduct).Error; err == nil {
if err := tx.Model(&models.Product{}).Where("id = ?", product.ID).Updates(map[string]interface{}{
"name": product.Name,
"appearance": product.Appearance,
"barcode": product.Barcode,
"price": product.Price,
"sale_price": product.SalePrice,
"cost": product.Cost,
"live_image": product.LiveImage,
"warehouse_id": product.WarehouseID,
"warehouse_name": product.WarehouseName,
"location_id": product.LocationID,
"location_name": product.LocationName,
"category_id": product.CategoryID,
"standard_product_id": product.StandardProductID,
"status": product.Status,
"is_del": 0,
"updated_at": product.UpdatedAt,
}).Error; err != nil {
return fmt.Errorf("恢复商品失败: %v", err)
}
} else if errors.Is(err, gorm.ErrRecordNotFound) {
product.CreatedAt = time.Now().Unix()
product.UpdatedAt = product.CreatedAt
if err := tx.Create(&product).Error; err != nil {
return fmt.Errorf("创建商品失败: %v", err)
}
} else {
return fmt.Errorf("查询商品失败: %v", err)
}
if len(destroyLog.ProductBookSnapshot) > 0 {
var book models.ProductBook
if err := json.Unmarshal(destroyLog.ProductBookSnapshot, &book); err == nil {
book.IsDel = 0
book.UpdatedAt = time.Now().Unix()
tableName := models.ProductBookTableName(book.Barcode)
var oldBook models.ProductBook
if err := tx.Table(tableName).Where("barcode = ?", book.Barcode).First(&oldBook).Error; err == nil {
tx.Table(tableName).Where("barcode = ?", book.Barcode).Updates(map[string]interface{}{
"is_del": 0,
"updated_at": book.UpdatedAt,
})
} else if errors.Is(err, gorm.ErrRecordNotFound) {
book.CreatedAt = time.Now().Unix()
book.UpdatedAt = book.CreatedAt
tx.Table(tableName).Create(&book)
}
}
}
if len(destroyLog.InventorySnapshot) > 0 {
var inventory models.Inventory
if err := json.Unmarshal(destroyLog.InventorySnapshot, &inventory); err == nil {
inventory.IsDel = 0
inventory.UpdatedAt = time.Now().Unix()
var oldInv models.Inventory
if err := tx.Where("product_id = ? AND warehouse_id = ?", inventory.ProductID, inventory.WarehouseID).First(&oldInv).Error; err == nil {
tx.Model(&oldInv).Updates(map[string]interface{}{
"is_del": 0,
"quantity": inventory.Quantity,
"updated_at": inventory.UpdatedAt,
})
} else if errors.Is(err, gorm.ErrRecordNotFound) {
inventory.CreatedAt = time.Now().Unix()
inventory.UpdatedAt = inventory.CreatedAt
tx.Create(&inventory)
}
tx.Create(&models.InventoryLog{
WarehouseID: inventory.WarehouseID,
ProductID: inventory.ProductID,
BatchNo: inventory.BatchNo,
ChangeType: constant.InventoryChangeAdjustment,
ChangeQuantity: inventory.Quantity,
BeforeQuantity: 0,
AfterQuantity: inventory.Quantity,
RelatedOrderType: "",
RelatedOrderNo: "",
Operator: operator,
OperatorID: operatorID,
Remark: "商品还原",
CreatedAt: time.Now().Unix(),
IsDel: 0,
})
}
}
if len(destroyLog.InventoryDetailSnapshot) > 0 {
var details []models.InventoryDetail
if err := json.Unmarshal(destroyLog.InventoryDetailSnapshot, &details); err == nil {
for _, d := range details {
d.IsDel = 0
d.UpdatedAt = time.Now().Unix()
var oldDetail models.InventoryDetail
if err := tx.Where("product_id = ? AND location_id = ? AND warehouse_id = ?", d.ProductID, d.LocationID, d.WarehouseID).First(&oldDetail).Error; err == nil {
tx.Model(&oldDetail).Updates(map[string]interface{}{
"is_del": 0,
"quantity": d.Quantity,
"updated_at": d.UpdatedAt,
})
} else if errors.Is(err, gorm.ErrRecordNotFound) {
d.CreatedAt = time.Now().Unix()
d.UpdatedAt = d.CreatedAt
tx.Create(&d)
}
}
}
}
now := time.Now().Unix()
tx.Model(&destroyLog).Updates(map[string]interface{}{
"status": 1,
"restored_by": operator,
"restored_by_id": operatorID,
"restored_at": now,
"updated_at": now,
})
return nil
})
return err
}
// GetDestroyLogList 获取商品销毁日志列表
func (s *ProductService) GetDestroyLogList(req systemReq.GetDestroyLogListRequest, db ...*gorm.DB) (*systemRes.DestroyLogListResponse, error) {
databaseConn := database.OptionalDB(db...)
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 || req.PageSize > 100 {
req.PageSize = 20
}
query := databaseConn.Model(&models.ProductDestroyLog{}).Where("is_del = ?", 0)
if req.Keyword != "" {
query = query.Where("barcode LIKE ?", "%"+req.Keyword+"%")
}
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, utils.NewError("查询总数失败")
}
if total == 0 {
return &systemRes.DestroyLogListResponse{
List: []systemRes.DestroyLogItem{},
Total: 0,
Page: req.Page,
PageSize: req.PageSize,
}, nil
}
var logs []models.ProductDestroyLog
offset := (req.Page - 1) * req.PageSize
if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&logs).Error; err != nil {
return nil, utils.NewError("查询销毁日志列表失败")
}
items := make([]systemRes.DestroyLogItem, 0, len(logs))
for _, log := range logs {
productName := ""
if len(log.ProductSnapshot) > 0 {
var p models.Product
if err := json.Unmarshal(log.ProductSnapshot, &p); err == nil {
productName = p.Name
}
}
items = append(items, systemRes.DestroyLogItem{
ID: log.ID,
ProductID: log.ProductID,
Barcode: log.Barcode,
ProductName: productName,
DestroyedBy: log.DestroyedBy,
DestroyedAt: log.DestroyedAt,
RestoredBy: log.RestoredBy,
RestoredAt: log.RestoredAt,
Status: log.Status,
StatusText: systemRes.GetDestroyLogStatusText(log.Status),
CreatedAt: log.CreatedAt,
})
}
return &systemRes.DestroyLogListResponse{
List: items,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}, nil
}
// GetDestroyLogDetail 获取商品销毁日志详情
func (s *ProductService) GetDestroyLogDetail(req systemReq.GetDestroyLogDetailRequest, db ...*gorm.DB) (*systemRes.DestroyLogDetailResponse, error) {
databaseConn := database.OptionalDB(db...)
var log models.ProductDestroyLog
if err := databaseConn.Where("id = ? AND is_del = ?", req.ID, 0).First(&log).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, utils.NewError("销毁日志不存在")
}
return nil, utils.NewError("查询销毁日志失败")
}
return &systemRes.DestroyLogDetailResponse{
ID: log.ID,
ProductID: log.ProductID,
Barcode: log.Barcode,
ProductSnapshot: string(log.ProductSnapshot),
ProductBookSnapshot: string(log.ProductBookSnapshot),
InventorySnapshot: string(log.InventorySnapshot),
InventoryDetailSnapshot: string(log.InventoryDetailSnapshot),
DestroyedBy: log.DestroyedBy,
DestroyedByID: log.DestroyedByID,
DestroyedAt: log.DestroyedAt,
RestoredBy: log.RestoredBy,
RestoredByID: log.RestoredByID,
RestoredAt: log.RestoredAt,
Status: log.Status,
StatusText: systemRes.GetDestroyLogStatusText(log.Status),
CreatedAt: log.CreatedAt,
UpdatedAt: log.UpdatedAt,
}, nil
}

View File

@ -48,6 +48,16 @@ func (s *SalesService) GetSalesOrderList(req systemReq.GetSalesOrderListRequest,
query = query.Where("sales_order.created_at <= ?", req.EndDate) query = query.Where("sales_order.created_at <= ?", req.EndDate)
} }
if req.AssociationOrderNo != "" {
query = query.Where("sales_order.association_order_no LIKE ?", "%"+req.AssociationOrderNo+"%")
}
if req.LogisticsNo != "" {
subQuery := databaseConn.Model(&models.SalesOrderItem{}).
Select("sales_order_id").
Where("logistics_no LIKE ? AND is_del = 0", "%"+req.LogisticsNo+"%")
query = query.Where("sales_order.id IN (?)", subQuery)
}
var total int64 var total int64
if err := query.Count(&total).Error; err != nil { if err := query.Count(&total).Error; err != nil {
return nil, utils.NewError("查询总数失败") return nil, utils.NewError("查询总数失败")
@ -76,10 +86,17 @@ func (s *SalesService) GetSalesOrderList(req systemReq.GetSalesOrderListRequest,
orderItems := make([]systemRes.SalesOrderItem, 0, len(orders)) orderItems := make([]systemRes.SalesOrderItem, 0, len(orders))
for _, order := range orders { for _, order := range orders {
var logisticsNos string
databaseConn.Table("sales_order_item").
Select("GROUP_CONCAT(DISTINCT logistics_no SEPARATOR ', ')").
Where("sales_order_id = ? AND is_del = ? AND logistics_no != ''", order.ID, 0).
Scan(&logisticsNos)
orderItems = append(orderItems, systemRes.ConvertSalesOrderToItem( orderItems = append(orderItems, systemRes.ConvertSalesOrderToItem(
order.SalesOrder, order.SalesOrder,
order.CustomerName, order.CustomerName,
order.WarehouseName, order.WarehouseName,
logisticsNos,
)) ))
} }
@ -124,6 +141,33 @@ func (s *SalesService) GetSalesOrderDetailList(req systemReq.GetSalesOrderDetail
//if req.EndDate > 0 { //if req.EndDate > 0 {
// query = query.Where("sales_order.created_at <= ?", req.EndDate) // query = query.Where("sales_order.created_at <= ?", req.EndDate)
//} //}
if req.Status > 0 {
query = query.Where("sales_order.status = ?", req.Status)
}
if req.CustomerID > 0 {
query = query.Where("sales_order.customer_id = ?", req.CustomerID)
}
if req.WarehouseID > 0 {
query = query.Where("sales_order.warehouse_id = ?", req.WarehouseID)
}
if req.SoNo != "" {
query = query.Where("sales_order.so_no LIKE ?", "%"+req.SoNo+"%")
}
if req.StartDate > 0 {
query = query.Where("sales_order.created_at >= ?", req.StartDate)
}
if req.EndDate > 0 {
query = query.Where("sales_order.created_at <= ?", req.EndDate)
}
if req.AssociationOrderNo != "" {
query = query.Where("sales_order.association_order_no LIKE ?", "%"+req.AssociationOrderNo+"%")
}
if req.LogisticsNo != "" {
subQuery := databaseConn.Model(&models.SalesOrderItem{}).
Select("sales_order_id").
Where("logistics_no LIKE ? AND is_del = 0", "%"+req.LogisticsNo+"%")
query = query.Where("sales_order.id IN (?)", subQuery)
}
var total int64 var total int64
if err := query.Count(&total).Error; err != nil { if err := query.Count(&total).Error; err != nil {
@ -205,6 +249,8 @@ func (s *SalesService) GetSalesOrderDetailList(req systemReq.GetSalesOrderDetail
ReceiverName: item.ReceiverName, ReceiverName: item.ReceiverName,
ReceiverPhone: item.ReceiverPhone, ReceiverPhone: item.ReceiverPhone,
ReceiverAddress: item.ReceiverAddress, ReceiverAddress: item.ReceiverAddress,
LogisticsCompany: item.LogisticsCompany,
LogisticsNo: item.LogisticsNo,
LocationCode: item.LocationCode, LocationCode: item.LocationCode,
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
@ -278,6 +324,8 @@ func (s *SalesService) GetSalesOrderDetail(id int64, creatorID int64, role int64
ReceiverName: item.ReceiverName, ReceiverName: item.ReceiverName,
ReceiverPhone: item.ReceiverPhone, ReceiverPhone: item.ReceiverPhone,
ReceiverAddress: item.ReceiverAddress, ReceiverAddress: item.ReceiverAddress,
LogisticsCompany: item.LogisticsCompany,
LogisticsNo: item.LogisticsNo,
LocationCode: item.LocationCode, LocationCode: item.LocationCode,
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,

View File

@ -41,6 +41,23 @@ func (s *ShippingService) GetShippingOrderList(req systemReq.GetShippingOrderLis
query = query.Where("shipping_order.created_at <= ?", req.EndDate) query = query.Where("shipping_order.created_at <= ?", req.EndDate)
} }
if req.AssociationOrderNo != "" {
subQuery := databaseConn.Table("shipping_order_item").
Select("shipping_order_item.shipping_order_id").
Joins("INNER JOIN outbound_order_item ON shipping_order_item.outbound_order_item_id = outbound_order_item.id AND outbound_order_item.is_del = 0").
Joins("INNER JOIN sales_order ON outbound_order_item.sales_order_id = sales_order.id AND sales_order.is_del = 0").
Where("shipping_order_item.is_del = 0 AND sales_order.association_order_no LIKE ?", "%"+req.AssociationOrderNo+"%")
query = query.Where("shipping_order.id IN (?)", subQuery)
}
if req.LogisticsNo != "" {
subQuery := databaseConn.Table("shipping_order_item").
Select("shipping_order_item.shipping_order_id").
Joins("INNER JOIN outbound_order_item ON shipping_order_item.outbound_order_item_id = outbound_order_item.id AND outbound_order_item.is_del = 0").
Joins("INNER JOIN sales_order_item ON outbound_order_item.sales_order_id = sales_order_item.sales_order_id AND sales_order_item.is_del = 0").
Where("shipping_order_item.is_del = 0 AND sales_order_item.logistics_no LIKE ?", "%"+req.LogisticsNo+"%")
query = query.Where("shipping_order.id IN (?)", subQuery)
}
var total int64 var total int64
if err := query.Count(&total).Error; err != nil { if err := query.Count(&total).Error; err != nil {
return nil, utils.NewError("查询总数失败") return nil, utils.NewError("查询总数失败")
@ -90,10 +107,28 @@ func (s *ShippingService) GetShippingOrderList(req systemReq.GetShippingOrderLis
}) })
} }
var associationOrderNos string
databaseConn.Table("shipping_order_item").
Select("GROUP_CONCAT(DISTINCT so.association_order_no SEPARATOR ', ')").
Joins("INNER JOIN outbound_order_item ooi ON shipping_order_item.outbound_order_item_id = ooi.id AND ooi.is_del = 0").
Joins("INNER JOIN sales_order so ON ooi.sales_order_id = so.id AND so.is_del = 0").
Where("shipping_order_item.shipping_order_id = ? AND shipping_order_item.is_del = ? AND so.association_order_no != ''", order.ID, 0).
Scan(&associationOrderNos)
var logisticsNos string
databaseConn.Table("shipping_order_item").
Select("GROUP_CONCAT(DISTINCT soi.logistics_no SEPARATOR ', ')").
Joins("INNER JOIN outbound_order_item ooi ON shipping_order_item.outbound_order_item_id = ooi.id AND ooi.is_del = 0").
Joins("INNER JOIN sales_order_item soi ON ooi.sales_order_id = soi.sales_order_id AND ooi.product_id = soi.product_id AND soi.is_del = 0").
Where("shipping_order_item.shipping_order_id = ? AND shipping_order_item.is_del = ? AND soi.logistics_no != ''", order.ID, 0).
Scan(&logisticsNos)
orderItems = append(orderItems, systemRes.ConvertShippingOrderToItem( orderItems = append(orderItems, systemRes.ConvertShippingOrderToItem(
order.ShippingOrder, order.ShippingOrder,
order.CustomerName, order.CustomerName,
shopList, shopList,
associationOrderNos,
logisticsNos,
)) ))
} }
@ -237,6 +272,23 @@ func (s *ShippingService) GetShippingOrderDetailList(req systemReq.GetShippingOr
query = query.Where("shipping_order.created_at <= ?", req.EndDate) query = query.Where("shipping_order.created_at <= ?", req.EndDate)
} }
if req.AssociationOrderNo != "" {
subQuery := databaseConn.Table("shipping_order_item").
Select("shipping_order_item.shipping_order_id").
Joins("INNER JOIN outbound_order_item ON shipping_order_item.outbound_order_item_id = outbound_order_item.id AND outbound_order_item.is_del = 0").
Joins("INNER JOIN sales_order ON outbound_order_item.sales_order_id = sales_order.id AND sales_order.is_del = 0").
Where("shipping_order_item.is_del = 0 AND sales_order.association_order_no LIKE ?", "%"+req.AssociationOrderNo+"%")
query = query.Where("shipping_order.id IN (?)", subQuery)
}
if req.LogisticsNo != "" {
subQuery := databaseConn.Table("shipping_order_item").
Select("shipping_order_item.shipping_order_id").
Joins("INNER JOIN outbound_order_item ON shipping_order_item.outbound_order_item_id = outbound_order_item.id AND outbound_order_item.is_del = 0").
Joins("INNER JOIN sales_order_item ON outbound_order_item.sales_order_id = sales_order_item.sales_order_id AND sales_order_item.is_del = 0").
Where("shipping_order_item.is_del = 0 AND sales_order_item.logistics_no LIKE ?", "%"+req.LogisticsNo+"%")
query = query.Where("shipping_order.id IN (?)", subQuery)
}
var total int64 var total int64
if err := query.Count(&total).Error; err != nil { if err := query.Count(&total).Error; err != nil {
return nil, utils.NewError("查询总数失败") return nil, utils.NewError("查询总数失败")

View File

@ -98,9 +98,9 @@ func (s *SplitAccountDeductionLogService) CreateSplitAccountDeductionLog(req sys
ConfigID: req.ConfigID, ConfigID: req.ConfigID,
ConfigName: req.ConfigName, ConfigName: req.ConfigName,
DeductionDetails: deductionDetails, DeductionDetails: deductionDetails,
TotalAmount: req.TotalAmount, TotalAmount: *req.TotalAmount,
DeductionAmount: req.DeductionAmount, DeductionAmount: *req.DeductionAmount,
RemainingAmount: req.RemainingAmount, RemainingAmount: *req.RemainingAmount,
CreatedBy: username, CreatedBy: username,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
@ -154,6 +154,8 @@ func (s *SplitAccountDeductionLogService) UpdateSplitAccountDeductionLog(req sys
updateData["remaining_amount"] = req.RemainingAmount updateData["remaining_amount"] = req.RemainingAmount
} }
updateData["status"] = req.Status
if err := databaseConn.Model(&log).Updates(updateData).Error; err != nil { if err := databaseConn.Model(&log).Updates(updateData).Error; err != nil {
return utils.NewError("更新分账扣钱日志失败: " + err.Error()) return utils.NewError("更新分账扣钱日志失败: " + err.Error())
} }