Compare commits
6 Commits
27a28823bb
...
9d9a94aac6
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d9a94aac6 | |||
| 58fa463934 | |||
| cd360c8866 | |||
| ca5a652334 | |||
| c1ae74e703 | |||
| a2ea0c3a40 |
@ -12,10 +12,10 @@ database:
|
|||||||
encrypt_key: "0123456789abcdef0123456789abcdef"
|
encrypt_key: "0123456789abcdef0123456789abcdef"
|
||||||
|
|
||||||
task_database:
|
task_database:
|
||||||
host: nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com
|
host: 36.212.7.35
|
||||||
port: "26247"
|
port: "3306"
|
||||||
user: root
|
user: zhishu
|
||||||
password: Long6166@@
|
password: KSwx1MDcRW
|
||||||
name: task
|
name: task
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
@ -16,6 +16,7 @@ type LogisticsApi struct{}
|
|||||||
|
|
||||||
var logisticsService = service.LogisticsService{}
|
var logisticsService = service.LogisticsService{}
|
||||||
|
|
||||||
|
// GetLogisticsList 获取物流模板列表
|
||||||
// GetLogisticsList 获取物流模板列表
|
// GetLogisticsList 获取物流模板列表
|
||||||
func (r *LogisticsApi) GetLogisticsList(c *gin.Context) {
|
func (r *LogisticsApi) GetLogisticsList(c *gin.Context) {
|
||||||
var req request.QueryLogisticsRequest
|
var req request.QueryLogisticsRequest
|
||||||
@ -24,6 +25,10 @@ func (r *LogisticsApi) GetLogisticsList(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从JWT中获取当前用户的about_id
|
||||||
|
userInfo := utils.GetUserInfo(c)
|
||||||
|
req.AboutID = userInfo.AboutID
|
||||||
|
|
||||||
list, total, err := logisticsService.GetLogisticsList(req, database.GetDB(c))
|
list, total, err := logisticsService.GetLogisticsList(req, database.GetDB(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询物流模板列表异常", err, c, req)
|
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询物流模板列表异常", err, c, req)
|
||||||
|
|||||||
@ -552,6 +552,31 @@ 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)
|
||||||
|
|
||||||
|
aboutID := req.AboutId
|
||||||
|
if aboutID == 0 {
|
||||||
|
aboutID = userInfo.AboutID
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := processService.UnlockSalesOrderInventory(aboutID, req.AssociationOrderNo, userInfo.Username, userInfo.ID)
|
||||||
|
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
|
||||||
|
|||||||
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -246,6 +246,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)
|
||||||
@ -271,6 +276,21 @@ func Init() {
|
|||||||
log.Fatal("Config表迁移失败:", err)
|
log.Fatal("Config表迁移失败:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统计表'").AutoMigrate(&models.Statist{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Statist表迁移失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仪表盘每日统计表'").AutoMigrate(&models.DashboardDailyStat{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("DashboardDailyStat表迁移失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户每日统计表'").AutoMigrate(&models.UserDailyStat{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("UserDailyStat表迁移失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化商品表
|
// 初始化商品表
|
||||||
InitProductBookTables()
|
InitProductBookTables()
|
||||||
// 初始化默认管理员账号
|
// 初始化默认管理员账号
|
||||||
|
|||||||
5
main.go
5
main.go
@ -5,6 +5,7 @@ import (
|
|||||||
"psi/config"
|
"psi/config"
|
||||||
"psi/database"
|
"psi/database"
|
||||||
router "psi/routes"
|
router "psi/routes"
|
||||||
|
"psi/service"
|
||||||
"psi/utils"
|
"psi/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,6 +38,10 @@ func main() {
|
|||||||
// ocr.StartService()
|
// ocr.StartService()
|
||||||
//}()
|
//}()
|
||||||
|
|
||||||
|
// 启动统计任务调度器
|
||||||
|
scheduler := service.NewStatistTaskScheduler()
|
||||||
|
go scheduler.Start()
|
||||||
|
log.Println("统计任务调度器已启动")
|
||||||
// 设置路由并启动服务器
|
// 设置路由并启动服务器
|
||||||
log.Printf("[4/4] 启动服务器,端口: %s...", config.AppConfig.Server.Port)
|
log.Printf("[4/4] 启动服务器,端口: %s...", config.AppConfig.Server.Port)
|
||||||
router.Run()
|
router.Run()
|
||||||
|
|||||||
29
models/DashboardDailyStat.go
Normal file
29
models/DashboardDailyStat.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// DashboardDailyStat 仪表盘每日统计表
|
||||||
|
type DashboardDailyStat struct {
|
||||||
|
ID int64 `json:"id" gorm:"primarykey;comment:ID"`
|
||||||
|
StatDate int64 `json:"stat_date" gorm:"type:bigint;not null;default:0;comment:统计日期(YYYYMMDD格式)"`
|
||||||
|
TotalReceivingNum int64 `json:"total_receiving_num" gorm:"not null;default:0;comment:总入库次数"`
|
||||||
|
TotalOutboundNum int64 `json:"total_outbound_num" gorm:"not null;default:0;comment:总出库次数"`
|
||||||
|
TotalSalesCount int64 `json:"total_sales_count" gorm:"not null;default:0;comment:总销售订单数"`
|
||||||
|
ProductTotal int64 `json:"product_total" gorm:"not null;default:0;comment:商品总量"`
|
||||||
|
InventoryTotal int64 `json:"inventory_total" gorm:"not null;default:0;comment:库存总量"`
|
||||||
|
TodayInbound int64 `json:"today_inbound" gorm:"not null;default:0;comment:今日入库次数"`
|
||||||
|
TodayOutbound int64 `json:"today_outbound" gorm:"not null;default:0;comment:今日出库次数"`
|
||||||
|
YesterdayInbound int64 `json:"yesterday_inbound" gorm:"not null;default:0;comment:昨日入库次数"`
|
||||||
|
YesterdayOutbound int64 `json:"yesterday_outbound" gorm:"not null;default:0;comment:昨日出库次数"`
|
||||||
|
TodaySalesCount int64 `json:"today_sales_count" gorm:"not null;default:0;comment:今日销售订单数"`
|
||||||
|
YesterdaySalesCount int64 `json:"yesterday_sales_count" gorm:"not null;default:0;comment:昨日销售订单数"`
|
||||||
|
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:逻辑删除标记(0:未删除,1:已删除)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DashboardDailyStat) TableName() string {
|
||||||
|
return "dashboard_daily_stat"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DashboardDailyStat) TableOptions() string {
|
||||||
|
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仪表盘每日统计表'"
|
||||||
|
}
|
||||||
23
models/UserDailyStat.go
Normal file
23
models/UserDailyStat.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// UserDailyStat 用户每日统计表
|
||||||
|
type UserDailyStat struct {
|
||||||
|
ID int64 `json:"id" gorm:"primarykey;comment:ID"`
|
||||||
|
UserID int64 `json:"user_id" gorm:"not null;default:0;comment:用户ID"`
|
||||||
|
UserName string `json:"user_name" gorm:"type:varchar(50);not null;default:'';comment:用户姓名"`
|
||||||
|
StatDate int64 `json:"stat_date" gorm:"type:bigint;not null;default:0;comment:统计日期(YYYYMMDD格式)"`
|
||||||
|
ReceivingNum int64 `json:"receiving_num" gorm:"not null;default:0;comment:入库次数"`
|
||||||
|
OutboundNum int64 `json:"outbound_num" gorm:"not null;default:0;comment:出库次数"`
|
||||||
|
SalesCount int64 `json:"sales_count" gorm:"not null;default:0;comment:销售订单数"`
|
||||||
|
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:逻辑删除标记(0:未删除,1:已删除)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UserDailyStat) TableName() string {
|
||||||
|
return "user_daily_stat"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UserDailyStat) TableOptions() string {
|
||||||
|
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户每日统计表'"
|
||||||
|
}
|
||||||
32
models/product_destroy_log.go
Normal file
32
models/product_destroy_log.go
Normal 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='商品销毁日志表'"
|
||||||
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
package request
|
package request
|
||||||
|
|
||||||
type QueryLogisticsRequest struct {
|
type QueryLogisticsRequest struct {
|
||||||
Keyword string `form:"keyword"`
|
Keyword string `form:"keyword"` // 物流模板名称
|
||||||
Status *int `form:"status"`
|
Status *int `form:"status"` // 物流模板状态
|
||||||
Page int `form:"page,default=1"`
|
Page int `form:"page,default=1"` // 页码
|
||||||
PageSize int `form:"page_size,default=10"`
|
PageSize int `form:"page_size,default=10"` // 每页数量
|
||||||
|
AboutID int64 `form:"-"` // 租户ID,从JWT中获取,不参与表单绑定
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateLogisticsRequest struct {
|
type CreateLogisticsRequest struct {
|
||||||
|
|||||||
@ -10,6 +10,8 @@ type GetOutboundOrderListRequest 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"`
|
||||||
|
AssociationOrderNo string `form:"association_order_no"`
|
||||||
|
LogisticsNo string `form:"logistics_no"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOutboundOrderDetailRequest 获取出库单详情请求
|
// GetOutboundOrderDetailRequest 获取出库单详情请求
|
||||||
|
|||||||
@ -133,6 +133,12 @@ type CancelSalesOrderRequest struct {
|
|||||||
OrderID int64 `form:"order_id" binding:"required"` // 订单ID
|
OrderID int64 `form:"order_id" binding:"required"` // 订单ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnlockSalesOrderInventoryRequest 解锁销售订单库存请求
|
||||||
|
type UnlockSalesOrderInventoryRequest struct {
|
||||||
|
AboutId int64 `form:"about_id"` // 租户ID(用于确定分库)
|
||||||
|
AssociationOrderNo string `form:"association_order_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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||
10
models/request/product_reimport.go
Normal file
10
models/request/product_reimport.go
Normal 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"`
|
||||||
|
}
|
||||||
@ -10,6 +10,8 @@ type GetSalesOrderListRequest 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"`
|
||||||
|
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ type GetShippingOrderListRequest struct {
|
|||||||
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 获取发货单详情请求
|
||||||
@ -25,4 +27,6 @@ type GetShippingOrderDetailListRequest struct {
|
|||||||
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 更新分账扣钱日志请求
|
||||||
|
|||||||
@ -46,6 +46,10 @@ 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"`
|
||||||
|
|
||||||
|
AssociationOrderNo string `json:"association_order_no"` //第三方订单编号
|
||||||
|
LogisticsNo string `json:"logistics_no"` //快递单号
|
||||||
|
|
||||||
Status int8 `json:"status"`
|
Status int8 `json:"status"`
|
||||||
StatusText string `json:"status_text"`
|
StatusText string `json:"status_text"`
|
||||||
Operator string `json:"operator"`
|
Operator string `json:"operator"`
|
||||||
@ -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,6 +113,10 @@ func ConvertOutboundOrderToItem(order models.OutboundOrder, customerName string,
|
|||||||
WarehouseID: order.WarehouseID,
|
WarehouseID: order.WarehouseID,
|
||||||
WarehouseName: warehouseName,
|
WarehouseName: warehouseName,
|
||||||
ShopList: shopList,
|
ShopList: shopList,
|
||||||
|
|
||||||
|
AssociationOrderNo: associationOrderNo,
|
||||||
|
LogisticsNo: logisticsNo,
|
||||||
|
|
||||||
Status: order.Status,
|
Status: order.Status,
|
||||||
StatusText: GetOutboundOrderStatusText(order.Status),
|
StatusText: GetOutboundOrderStatusText(order.Status),
|
||||||
Operator: order.Operator,
|
Operator: order.Operator,
|
||||||
|
|||||||
@ -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"`
|
||||||
|
}
|
||||||
|
|||||||
@ -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"` // 启用中商品数
|
||||||
|
|||||||
57
models/response/product_destroy.go
Normal file
57
models/response/product_destroy.go
Normal 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 "未知"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,6 +149,10 @@ 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,
|
||||||
|
|
||||||
|
AssociationOrderNo: order.AssociationOrderNo,
|
||||||
|
LogisticsNo: "",
|
||||||
|
|
||||||
Remark: order.Remark,
|
Remark: order.Remark,
|
||||||
CreatedAt: order.CreatedAt,
|
CreatedAt: order.CreatedAt,
|
||||||
UpdatedAt: order.UpdatedAt,
|
UpdatedAt: order.UpdatedAt,
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,12 @@ type DashboardStatistResponse struct {
|
|||||||
TotalOutboundCount int64 `json:"total_outbound_count"` // 总出库次数
|
TotalOutboundCount int64 `json:"total_outbound_count"` // 总出库次数
|
||||||
TotalSaleCount int64 `json:"total_sale_count"` // 今日商品总数
|
TotalSaleCount int64 `json:"total_sale_count"` // 今日商品总数
|
||||||
UserStats []UserStatItem `json:"user_stats"` // 个人统计数据
|
UserStats []UserStatItem `json:"user_stats"` // 个人统计数据
|
||||||
|
ProductTotal int64 `json:"product_total"` // 商品总量
|
||||||
|
InventoryTotal int64 `json:"inventory_total"` // 库存量
|
||||||
|
TodayInbound int64 `json:"today_inbound"` // 今日入库
|
||||||
|
TodayOutbound int64 `json:"today_outbound"` // 今日出库
|
||||||
|
YesterdayInbound int64 `json:"yesterday_inbound"` // 昨日入库
|
||||||
|
YesterdayOutbound int64 `json:"yesterday_outbound"` // 昨日出库
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserStatItem 个人统计项
|
// UserStatItem 个人统计项
|
||||||
|
|||||||
@ -92,10 +92,15 @@ 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.GET("/open/split-account-deduction-log/list", splitAccountDeductionLogApi.GetOpenSplitAccountDeductionLogList)
|
||||||
|
public.POST("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账扣钱日志
|
||||||
|
public.POST("/sales-order/unlock-inventory", processApi.UnlockSalesOrderInventory) // 解锁销售订单库存
|
||||||
public.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账
|
public.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账
|
||||||
public.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账
|
public.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账
|
||||||
public.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账
|
public.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账
|
||||||
|
|
||||||
|
public.GET("/logistics/list-c", logisticsApi.GetLogisticsList) // 获取物流模板列表
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sign := api.Group("")
|
sign := api.Group("")
|
||||||
@ -170,6 +175,14 @@ func initRouter() (r *gin.Engine) {
|
|||||||
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) // 生成条形码
|
||||||
@ -193,7 +206,9 @@ 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("/sales-order/unlock-inventory", processApi.UnlockSalesOrderInventory) // 解锁销售订单库存
|
||||||
|
|
||||||
auth.POST("/wave/outbound/cancel", processApi.CancelOutboundWave) // 取消出库波次
|
auth.POST("/wave/outbound/cancel", processApi.CancelOutboundWave) // 取消出库波次
|
||||||
// 盘库
|
// 盘库
|
||||||
auth.POST("/stock_check/adjust", processApi.AdjustInventory) // 盘库
|
auth.POST("/stock_check/adjust", processApi.AdjustInventory) // 盘库
|
||||||
@ -263,12 +278,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) // 保存产品日志
|
||||||
|
|||||||
55
service/StatistTaskScheduler.go
Normal file
55
service/StatistTaskScheduler.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatistTaskScheduler 统计任务调度器
|
||||||
|
type StatistTaskScheduler struct {
|
||||||
|
taskService *StatistTaskService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatistTaskScheduler 创建统计任务调度器
|
||||||
|
func NewStatistTaskScheduler() *StatistTaskScheduler {
|
||||||
|
return &StatistTaskScheduler{
|
||||||
|
taskService: &StatistTaskService{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动定时任务调度器
|
||||||
|
func (s *StatistTaskScheduler) Start() {
|
||||||
|
log.Println("统计任务调度器启动")
|
||||||
|
|
||||||
|
// 立即执行一次生成昨天的统计数据
|
||||||
|
go func() {
|
||||||
|
if err := s.taskService.GenerateDailyStat(); err != nil {
|
||||||
|
log.Printf("生成每日统计失败: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 每天凌晨2点执行生成前一天的统计数据
|
||||||
|
ticker := time.NewTicker(1 * time.Hour) // 每小时检查一次是否到了凌晨2点
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
var lastRunDate string
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
now := time.Now()
|
||||||
|
currentDateStr := now.Format("2006-01-02")
|
||||||
|
hour := now.Hour()
|
||||||
|
|
||||||
|
// 如果是凌晨2点且今天还没运行过
|
||||||
|
if hour == 2 && currentDateStr != lastRunDate {
|
||||||
|
go func(date string) {
|
||||||
|
log.Printf("开始执行每日统计任务,日期: %s", date)
|
||||||
|
if err := s.taskService.GenerateDailyStat(); err != nil {
|
||||||
|
log.Printf("生成每日统计失败: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("每日统计任务执行成功,日期: %s", date)
|
||||||
|
}
|
||||||
|
}(currentDateStr)
|
||||||
|
lastRunDate = currentDateStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
259
service/StatistTaskService.go
Normal file
259
service/StatistTaskService.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"psi/database"
|
||||||
|
"psi/models"
|
||||||
|
"psi/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatistTaskService struct{}
|
||||||
|
|
||||||
|
// GenerateDailyStat 生成每日统计数据(定时任务调用)
|
||||||
|
// GenerateDailyStat 生成每日统计数据(定时任务调用)
|
||||||
|
// GenerateDailyStat 生成每日统计数据(定时任务调用)
|
||||||
|
func (s *StatistTaskService) GenerateDailyStat(db ...*gorm.DB) error {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
yesterday := now.AddDate(0, 0, -1)
|
||||||
|
statDateStr := yesterday.Format("20060102")
|
||||||
|
var statDate int64
|
||||||
|
if _, err := fmt.Sscanf(statDateStr, "%d", &statDate); err != nil {
|
||||||
|
return fmt.Errorf("日期格式化失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
yesterdayStart := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location()).Unix()
|
||||||
|
yesterdayEnd := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 23, 59, 59, 0, yesterday.Location()).Unix()
|
||||||
|
|
||||||
|
mainDB := database.DB
|
||||||
|
if len(db) > 0 && db[0] != nil {
|
||||||
|
mainDB = db[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var aboutIDs []int64
|
||||||
|
if err := mainDB.Model(&models.Employee{}).Where("deleted_at = ?", 0).Distinct().Pluck("about_id", &aboutIDs).Error; err != nil {
|
||||||
|
return fmt.Errorf("查询租户列表失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(aboutIDs) == 0 {
|
||||||
|
utils.InfoLog("work", map[string]interface{}{
|
||||||
|
"source": "定时任务-生成每日统计",
|
||||||
|
"stat_date": statDate,
|
||||||
|
"message": "没有找到任何租户",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, aboutID := range aboutIDs {
|
||||||
|
if aboutID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantDB, err := database.GetTenantDB(aboutID)
|
||||||
|
if err != nil {
|
||||||
|
utils.ErrorLog("work", map[string]interface{}{
|
||||||
|
"source": "定时任务-生成每日统计",
|
||||||
|
"about_id": aboutID,
|
||||||
|
"error": fmt.Sprintf("获取租户数据库失败: %v", err),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := tenantDB.Begin()
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err := s.generateDashboardDailyStat(tx, statDate, yesterdayStart, yesterdayEnd)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
utils.ErrorLog("work", map[string]interface{}{
|
||||||
|
"source": "定时任务-生成每日统计",
|
||||||
|
"about_id": aboutID,
|
||||||
|
"stat_date": statDate,
|
||||||
|
"error": fmt.Sprintf("生成全局统计失败: %v", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.generateUserDailyStat(tx, aboutID, statDate, yesterdayStart, yesterdayEnd); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
utils.ErrorLog("work", map[string]interface{}{
|
||||||
|
"source": "定时任务-生成每日统计",
|
||||||
|
"about_id": aboutID,
|
||||||
|
"stat_date": statDate,
|
||||||
|
"error": fmt.Sprintf("生成用户统计失败: %v", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
utils.ErrorLog("work", map[string]interface{}{
|
||||||
|
"source": "定时任务-生成每日统计",
|
||||||
|
"about_id": aboutID,
|
||||||
|
"stat_date": statDate,
|
||||||
|
"error": fmt.Sprintf("提交事务失败: %v", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.InfoLog("work", map[string]interface{}{
|
||||||
|
"source": "定时任务-生成每日统计",
|
||||||
|
"about_id": aboutID,
|
||||||
|
"stat_date": statDate,
|
||||||
|
"message": fmt.Sprintf("成功生成%s的统计数据", yesterday.Format("2006-01-02")),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDashboardDailyStat 生成全局每日统计
|
||||||
|
func (s *StatistTaskService) generateDashboardDailyStat(tx *gorm.DB, statDate int64, startDate, endDate int64) (*models.DashboardDailyStat, error) {
|
||||||
|
var existingStat models.DashboardDailyStat
|
||||||
|
err := tx.Where("stat_date = ? AND is_del = ?", statDate, 0).First(&existingStat).Error
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if err := tx.Model(&models.DashboardDailyStat{}).Where("id = ?", existingStat.ID).Update("is_del", 1).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalReceivingNum, totalOutboundNum int64
|
||||||
|
tx.Model(&models.Statist{}).
|
||||||
|
Where("stat_date <= ? AND is_del = ?", endDate, 0).
|
||||||
|
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||||||
|
Row().Scan(&totalReceivingNum, &totalOutboundNum)
|
||||||
|
|
||||||
|
var todayInbound, todayOutbound int64
|
||||||
|
tx.Model(&models.Statist{}).
|
||||||
|
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", startDate, endDate, 0).
|
||||||
|
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||||||
|
Row().Scan(&todayInbound, &todayOutbound)
|
||||||
|
|
||||||
|
beforeYesterdayStart := time.Date(time.Unix(startDate, 0).Year(), time.Unix(startDate, 0).Month(), time.Unix(startDate, 0).Day()-1, 0, 0, 0, 0, time.Unix(startDate, 0).Location()).Unix()
|
||||||
|
beforeYesterdayEnd := time.Date(time.Unix(startDate, 0).Year(), time.Unix(startDate, 0).Month(), time.Unix(startDate, 0).Day()-1, 23, 59, 59, 0, time.Unix(startDate, 0).Location()).Unix()
|
||||||
|
var yesterdayInbound, yesterdayOutbound int64
|
||||||
|
tx.Model(&models.Statist{}).
|
||||||
|
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", beforeYesterdayStart, beforeYesterdayEnd, 0).
|
||||||
|
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||||||
|
Row().Scan(&yesterdayInbound, &yesterdayOutbound)
|
||||||
|
|
||||||
|
var totalSalesCount int64
|
||||||
|
tx.Model(&models.SalesOrder{}).
|
||||||
|
Where("created_at <= ? AND is_del = ?", endDate, 0).
|
||||||
|
Count(&totalSalesCount)
|
||||||
|
|
||||||
|
var todaySalesCount int64
|
||||||
|
tx.Model(&models.SalesOrder{}).
|
||||||
|
Where("created_at >= ? AND created_at <= ? AND is_del = ?", startDate, endDate, 0).
|
||||||
|
Count(&todaySalesCount)
|
||||||
|
|
||||||
|
var yesterdaySalesCount int64
|
||||||
|
tx.Model(&models.SalesOrder{}).
|
||||||
|
Where("created_at >= ? AND created_at <= ? AND is_del = ?", beforeYesterdayStart, beforeYesterdayEnd, 0).
|
||||||
|
Count(&yesterdaySalesCount)
|
||||||
|
|
||||||
|
var productTotal int64
|
||||||
|
tx.Model(&models.Product{}).
|
||||||
|
Where("is_del = ?", 0).
|
||||||
|
Count(&productTotal)
|
||||||
|
|
||||||
|
var inventoryTotal int64
|
||||||
|
tx.Model(&models.Inventory{}).
|
||||||
|
Where("is_del = ?", 0).
|
||||||
|
Select("COALESCE(SUM(quantity), 0)").
|
||||||
|
Row().Scan(&inventoryTotal)
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
dashboardStat := &models.DashboardDailyStat{
|
||||||
|
StatDate: statDate,
|
||||||
|
TotalReceivingNum: totalReceivingNum,
|
||||||
|
TotalOutboundNum: totalOutboundNum,
|
||||||
|
TotalSalesCount: totalSalesCount,
|
||||||
|
ProductTotal: productTotal,
|
||||||
|
InventoryTotal: inventoryTotal,
|
||||||
|
TodayInbound: todayInbound,
|
||||||
|
TodayOutbound: todayOutbound,
|
||||||
|
YesterdayInbound: yesterdayInbound,
|
||||||
|
YesterdayOutbound: yesterdayOutbound,
|
||||||
|
TodaySalesCount: todaySalesCount,
|
||||||
|
YesterdaySalesCount: yesterdaySalesCount,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
IsDel: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Create(dashboardStat).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboardStat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateUserDailyStat 生成用户每日统计
|
||||||
|
func (s *StatistTaskService) generateUserDailyStat(tx *gorm.DB, aboutID int64, statDate int64, startDate, endDate int64) error {
|
||||||
|
type UserStatGroup struct {
|
||||||
|
UserID int64 `gorm:"column:user_id"`
|
||||||
|
UserName string `gorm:"column:user_name"`
|
||||||
|
ReceivingNum int64 `gorm:"column:receiving_num"`
|
||||||
|
OutboundNum int64 `gorm:"column:outbound_num"`
|
||||||
|
SalesCount int64 `gorm:"column:sales_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var userStats []UserStatGroup
|
||||||
|
tx.Table("employee").
|
||||||
|
Select(`
|
||||||
|
employee.id as user_id,
|
||||||
|
employee.username as user_name,
|
||||||
|
COALESCE((SELECT SUM(receiving_num) FROM statist WHERE create_by = employee.id AND stat_date >= ? AND stat_date <= ? AND is_del = ?), 0) as receiving_num,
|
||||||
|
COALESCE((SELECT SUM(outbound_num) FROM statist WHERE create_by = employee.id AND stat_date >= ? AND stat_date <= ? AND is_del = ?), 0) as outbound_num,
|
||||||
|
COALESCE((SELECT COUNT(*) FROM sales_order WHERE create_by = employee.id AND created_at >= ? AND created_at <= ? AND is_del = ?), 0) as sales_count
|
||||||
|
`, startDate, endDate, 0, startDate, endDate, 0, startDate, endDate, 0).
|
||||||
|
Where("employee.about_id = ? AND employee.is_del = ?", aboutID, 0).
|
||||||
|
Scan(&userStats)
|
||||||
|
|
||||||
|
if len(userStats) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
for _, userStat := range userStats {
|
||||||
|
var existingStat models.UserDailyStat
|
||||||
|
err := tx.Where("user_id = ? AND stat_date = ? AND is_del = ?", userStat.UserID, statDate, 0).First(&existingStat).Error
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if err := tx.Model(&models.UserDailyStat{}).Where("id = ?", existingStat.ID).Update("is_del", 1).Error; err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newStat := &models.UserDailyStat{
|
||||||
|
UserID: userStat.UserID,
|
||||||
|
UserName: userStat.UserName,
|
||||||
|
StatDate: statDate,
|
||||||
|
ReceivingNum: userStat.ReceivingNum,
|
||||||
|
OutboundNum: userStat.OutboundNum,
|
||||||
|
SalesCount: userStat.SalesCount,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
IsDel: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Create(newStat).Error; err != nil {
|
||||||
|
utils.ErrorLog("work", map[string]interface{}{
|
||||||
|
"source": "生成用户统计",
|
||||||
|
"user_id": userStat.UserID,
|
||||||
|
"error": fmt.Sprintf("创建用户统计记录失败: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -94,9 +94,15 @@ func (s *EmployeeService) Login(req systemReq.LoginRequest, userType string, c *
|
|||||||
var employeeLevel models.EmployeeLevel
|
var employeeLevel models.EmployeeLevel
|
||||||
levelResult := database.DB.Where("emp_id = ? AND is_del = ?", employee.ID, 0).First(&employeeLevel)
|
levelResult := database.DB.Where("emp_id = ? AND is_del = ?", employee.ID, 0).First(&employeeLevel)
|
||||||
if levelResult.Error == nil {
|
if levelResult.Error == nil {
|
||||||
|
// 如果等级为5(至尊版),将max_num设置为999(不限制)
|
||||||
|
maxNum := employeeLevel.MaxNum
|
||||||
|
if employeeLevel.Level == 5 {
|
||||||
|
maxNum = 999
|
||||||
|
}
|
||||||
|
|
||||||
levelInfo = &systemRes.EmployeeLevelInfo{
|
levelInfo = &systemRes.EmployeeLevelInfo{
|
||||||
Level: employeeLevel.Level, // 等级
|
Level: employeeLevel.Level, // 等级
|
||||||
MaxNum: employeeLevel.MaxNum, // 最大数量
|
MaxNum: maxNum, // 最大数量(等级5时为999)
|
||||||
ExpireTime: employeeLevel.ExpireTime, // 到期时间
|
ExpireTime: employeeLevel.ExpireTime, // 到期时间
|
||||||
PayTime: employeeLevel.PayTime, // 支付时间
|
PayTime: employeeLevel.PayTime, // 支付时间
|
||||||
}
|
}
|
||||||
@ -286,8 +292,11 @@ func (s *EmployeeService) AddEmployee(req systemReq.AddEmployeeRequest) (*system
|
|||||||
return nil, utils.NewError("主账号已过期,无法创建子账号")
|
return nil, utils.NewError("主账号已过期,无法创建子账号")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查max_num限制,如果max_num为0表示没权限
|
// 检查max_num限制
|
||||||
if parentEmployeeLevel.MaxNum > 0 {
|
// 如果等级为5(至尊版)或max_num为999/-1,表示不限制子账号数量
|
||||||
|
if parentEmployeeLevel.Level == 5 || parentEmployeeLevel.MaxNum == 999 || parentEmployeeLevel.MaxNum == -1 {
|
||||||
|
// 至尊版或不限制模式,无需检查子账号数量
|
||||||
|
} else if parentEmployeeLevel.MaxNum > 0 {
|
||||||
var currentSubCount int64
|
var currentSubCount int64
|
||||||
database.DB.Model(&models.Employee{}).Where("fid = ? AND deleted_at = 0", req.Fid).Count(¤tSubCount)
|
database.DB.Model(&models.Employee{}).Where("fid = ? AND deleted_at = 0", req.Fid).Count(¤tSubCount)
|
||||||
|
|
||||||
@ -302,7 +311,6 @@ func (s *EmployeeService) AddEmployee(req systemReq.AddEmployeeRequest) (*system
|
|||||||
return nil, utils.NewError("子账号的到期时间不能超过主账号的到期时间")
|
return nil, utils.NewError("子账号的到期时间不能超过主账号的到期时间")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPassword, err := utils.HashPassword(req.Password)
|
hashedPassword, err := utils.HashPassword(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, utils.NewError("密码加密失败")
|
return nil, utils.NewError("密码加密失败")
|
||||||
@ -516,9 +524,9 @@ func GetLevelConfig(level int8) interface{} {
|
|||||||
},
|
},
|
||||||
5: {
|
5: {
|
||||||
Level: 5, // 等级
|
Level: 5, // 等级
|
||||||
MaxNum: 25, // 最大数量
|
MaxNum: 999, // 最大数量(999表示不限制)
|
||||||
Price: 24500, // 价格
|
Price: 24500, // 价格
|
||||||
LevelName: "至尊版(25个子账号,245元/30天)", // 等级名称
|
LevelName: "至尊版(不限制子账号数量,245元/30天)", // 等级名称
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// 获取所有等级配置
|
// 获取所有等级配置
|
||||||
@ -610,9 +618,17 @@ func (s *EmployeeService) SetEmployeeLevel(req systemReq.SetEmployeeLevelRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录开通日志
|
// 记录开通日志
|
||||||
operationRemark := fmt.Sprintf("开通%s(等级%d),可创建子账号数量上限:%d,价格:%d元,时长:%d个月,到期时间:%s",
|
/*operationRemark := fmt.Sprintf("开通%s(等级%d),可创建子账号数量上限:%d,价格:%d元,时长:%d个月,到期时间:%s",
|
||||||
levelConfig.LevelName, levelConfig.Level, levelConfig.MaxNum, (levelConfig.Price*req.Duration)/100, req.Duration,
|
levelConfig.LevelName, levelConfig.Level, levelConfig.MaxNum, (levelConfig.Price*req.Duration)/100, req.Duration,
|
||||||
|
time.Unix(expireTime, 0).Format("2006-01-02"))*/
|
||||||
|
maxNumText := "不限制"
|
||||||
|
if levelConfig.MaxNum > 0 {
|
||||||
|
maxNumText = fmt.Sprintf("%d", levelConfig.MaxNum)
|
||||||
|
}
|
||||||
|
operationRemark := fmt.Sprintf("开通%s(等级%d),可创建子账号数量上限:%s,价格:%d元,时长:%d个月,到期时间:%s",
|
||||||
|
levelConfig.LevelName, levelConfig.Level, maxNumText, (levelConfig.Price*req.Duration)/100, req.Duration,
|
||||||
time.Unix(expireTime, 0).Format("2006-01-02"))
|
time.Unix(expireTime, 0).Format("2006-01-02"))
|
||||||
|
|
||||||
if err := s.createEmployeeLevelLog(req.EmpId, 1, oldLevel, levelConfig.Level, oldMaxNum, levelConfig.MaxNum, oldExpireTime, expireTime, levelConfig.Price*req.Duration, req.PayTime, operatorID, operatorName, operationRemark); err != nil {
|
if err := s.createEmployeeLevelLog(req.EmpId, 1, oldLevel, levelConfig.Level, oldMaxNum, levelConfig.MaxNum, oldExpireTime, expireTime, levelConfig.Price*req.Duration, req.PayTime, operatorID, operatorName, operationRemark); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -658,8 +674,19 @@ func (s *EmployeeService) SetEmployeeLevel(req systemReq.SetEmployeeLevelRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录升级日志
|
// 记录升级日志
|
||||||
operationRemark := fmt.Sprintf("等级从%d升级到%d(%s),子账号数量上限从%d调整为%d,价格:%d元,时长:%d个月,新到期时间:%s",
|
/*operationRemark := fmt.Sprintf("等级从%d升级到%d(%s),子账号数量上限从%d调整为%d,价格:%d元,时长:%d个月,新到期时间:%s",
|
||||||
oldLevel, levelConfig.Level, levelConfig.LevelName, oldMaxNum, levelConfig.MaxNum, (levelConfig.Price*req.Duration)/100, req.Duration,
|
oldLevel, levelConfig.Level, levelConfig.LevelName, oldMaxNum, levelConfig.MaxNum, (levelConfig.Price*req.Duration)/100, req.Duration,
|
||||||
|
time.Unix(expireTime, 0).Format("2006-01-02"))*/
|
||||||
|
oldMaxNumText := "不限制"
|
||||||
|
if oldMaxNum > 0 {
|
||||||
|
oldMaxNumText = fmt.Sprintf("%d", oldMaxNum)
|
||||||
|
}
|
||||||
|
newMaxNumText := "不限制"
|
||||||
|
if levelConfig.MaxNum > 0 {
|
||||||
|
newMaxNumText = fmt.Sprintf("%d", levelConfig.MaxNum)
|
||||||
|
}
|
||||||
|
operationRemark := fmt.Sprintf("等级从%d升级到%d(%s),子账号数量上限从%s调整为%s,价格:%d元,时长:%d个月,新到期时间:%s",
|
||||||
|
oldLevel, levelConfig.Level, levelConfig.LevelName, oldMaxNumText, newMaxNumText, (levelConfig.Price*req.Duration)/100, req.Duration,
|
||||||
time.Unix(expireTime, 0).Format("2006-01-02"))
|
time.Unix(expireTime, 0).Format("2006-01-02"))
|
||||||
if err := s.createEmployeeLevelLog(req.EmpId, 2, oldLevel, levelConfig.Level, oldMaxNum, levelConfig.MaxNum, oldExpireTime, expireTime, levelConfig.Price*req.Duration, req.PayTime, operatorID, operatorName, operationRemark); err != nil {
|
if err := s.createEmployeeLevelLog(req.EmpId, 2, oldLevel, levelConfig.Level, oldMaxNum, levelConfig.MaxNum, oldExpireTime, expireTime, levelConfig.Price*req.Duration, req.PayTime, operatorID, operatorName, operationRemark); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -27,8 +27,28 @@ func (s *LogisticsService) GetLogisticsList(req request.QueryLogisticsRequest, d
|
|||||||
req.PageSize = 20
|
req.PageSize = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果提供了 about_id,先查询该租户下的所有仓库
|
||||||
|
var warehouseIDs []int64
|
||||||
|
if req.AboutID > 0 {
|
||||||
|
if err := databaseConn.Model(&models.Warehouse{}).
|
||||||
|
Where("about_id = ? AND is_del = ?", req.AboutID, 0).
|
||||||
|
Pluck("id", &warehouseIDs).Error; err != nil {
|
||||||
|
return nil, 0, errors.New("查询仓库信息失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到仓库,直接返回空结果
|
||||||
|
if len(warehouseIDs) == 0 {
|
||||||
|
return []response.LogisticsResponse{}, 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query := databaseConn.Model(&models.Logistics{}).Where("del_flag = ?", "0")
|
query := databaseConn.Model(&models.Logistics{}).Where("del_flag = ?", "0")
|
||||||
|
|
||||||
|
// 如果有仓库ID列表,只查询这些仓库关联的物流模板
|
||||||
|
if len(warehouseIDs) > 0 {
|
||||||
|
query = query.Where("id IN (?)", warehouseIDs)
|
||||||
|
}
|
||||||
|
|
||||||
if req.Keyword != "" {
|
if req.Keyword != "" {
|
||||||
query = query.Where("template_name LIKE ? OR delivery_province LIKE ? OR delivery_city LIKE ?",
|
query = query.Where("template_name LIKE ? OR delivery_province LIKE ? OR delivery_city LIKE ?",
|
||||||
"%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
|
"%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
|
||||||
@ -39,7 +59,7 @@ func (s *LogisticsService) GetLogisticsList(req request.QueryLogisticsRequest, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === debug: 打印完整SQL ===
|
// === debug: 打印完整SQL ===
|
||||||
log.Printf("[GetLogisticsList] 请求参数 page=%d pageSize=%d keyword=%q", req.Page, req.PageSize, req.Keyword)
|
log.Printf("[GetLogisticsList] 请求参数 page=%d pageSize=%d keyword=%q about_id=%d", req.Page, req.PageSize, req.Keyword, req.AboutID)
|
||||||
// === debug end ===
|
// === debug end ===
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
|
|||||||
@ -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,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -3260,15 +3267,15 @@ func (s *ProcessService) lockInventory(tx *gorm.DB, warehouseID, productID, quan
|
|||||||
func (s *ProcessService) unlockInventory(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) error {
|
func (s *ProcessService) unlockInventory(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) error {
|
||||||
var inventories []models.Inventory
|
var inventories []models.Inventory
|
||||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
Where("warehouse_id = ? AND product_id = ? AND is_del = 0 AND (locked_quantity > 0 or quality > 0)",
|
Where("product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
||||||
warehouseID, productID).
|
productID).
|
||||||
Order("created_at DESC").
|
Order("created_at DESC").
|
||||||
Find(&inventories).Error; err != nil {
|
Find(&inventories).Error; err != nil {
|
||||||
return fmt.Errorf("查询锁定库存失败: %v", err)
|
return fmt.Errorf("查询锁定库存失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inventories) == 0 {
|
if len(inventories) == 0 {
|
||||||
return fmt.Errorf("商品ID=%d在仓库ID=%d中无锁定库存", productID, warehouseID)
|
return fmt.Errorf("商品ID=%d无锁定库存", productID)
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingUnlock := quantity
|
remainingUnlock := quantity
|
||||||
@ -3297,48 +3304,6 @@ func (s *ProcessService) unlockInventory(tx *gorm.DB, warehouseID, productID, qu
|
|||||||
return fmt.Errorf("锁定库存已被其他事务修改,请重试")
|
return fmt.Errorf("锁定库存已被其他事务修改,请重试")
|
||||||
}
|
}
|
||||||
|
|
||||||
var inventoryDetails []models.InventoryDetail
|
|
||||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
||||||
Where("warehouse_id = ? AND product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
|
||||||
warehouseID, productID).
|
|
||||||
Order("created_at DESC").
|
|
||||||
Find(&inventoryDetails).Error; err != nil {
|
|
||||||
return fmt.Errorf("查询库存明细失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
detailRemainingUnlock := unlockQty
|
|
||||||
for j := range inventoryDetails {
|
|
||||||
if detailRemainingUnlock <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
detailUnlockQty := inventoryDetails[j].LockedQuantity
|
|
||||||
if detailUnlockQty > detailRemainingUnlock {
|
|
||||||
detailUnlockQty = detailRemainingUnlock
|
|
||||||
}
|
|
||||||
|
|
||||||
detailResult := tx.Model(&inventoryDetails[j]).
|
|
||||||
Where("locked_quantity >= ?", detailUnlockQty).
|
|
||||||
UpdateColumns(map[string]interface{}{
|
|
||||||
"locked_quantity": gorm.Expr("locked_quantity - ?", detailUnlockQty),
|
|
||||||
"updated_at": now,
|
|
||||||
})
|
|
||||||
|
|
||||||
if detailResult.Error != nil {
|
|
||||||
return fmt.Errorf("解锁库存明细失败: %v", detailResult.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if detailResult.RowsAffected == 0 {
|
|
||||||
return fmt.Errorf("库存明细锁定数量已被其他事务修改,请重试")
|
|
||||||
}
|
|
||||||
|
|
||||||
detailRemainingUnlock -= detailUnlockQty
|
|
||||||
}
|
|
||||||
|
|
||||||
if detailRemainingUnlock > 0 {
|
|
||||||
return fmt.Errorf("库存明细锁定数量不足,还需解锁:%d", detailRemainingUnlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingUnlock -= unlockQty
|
remainingUnlock -= unlockQty
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3429,7 +3394,6 @@ func (s *ProcessService) CancelOutboundWave(waveID int64, operator string, opera
|
|||||||
|
|
||||||
// CancelSalesOrder 取消销售订单并释放锁定库存
|
// CancelSalesOrder 取消销售订单并释放锁定库存
|
||||||
func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operatorID int64, db ...*gorm.DB) error {
|
func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operatorID int64, db ...*gorm.DB) error {
|
||||||
|
|
||||||
databaseConn := database.OptionalDB(db...)
|
databaseConn := database.OptionalDB(db...)
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
@ -3440,12 +3404,15 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
|
|||||||
return fmt.Errorf("销售订单不存在: %v", err)
|
return fmt.Errorf("销售订单不存在: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if salesOrder.Status == constant.SalesStatusShipped || salesOrder.Status == constant.SalesStatusCancelled {
|
if salesOrder.Status == constant.SalesStatusShipped {
|
||||||
return fmt.Errorf("订单状态不允许取消,当前状态: %s", getSalesStatusText(salesOrder.Status))
|
return fmt.Errorf("订单已发货,无法取消")
|
||||||
|
}
|
||||||
|
if salesOrder.Status == constant.SalesStatusCancelled {
|
||||||
|
return fmt.Errorf("订单已取消")
|
||||||
}
|
}
|
||||||
|
|
||||||
var orderItems []models.SalesOrderItem
|
var orderItems []models.SalesOrderItem
|
||||||
if err := tx.Where("sales_order_id = ? AND is_del = 0", orderID).Find(&orderItems).Error; err != nil {
|
if err := tx.Where("sales_order_id = ? AND is_del = 0", salesOrder.ID).Find(&orderItems).Error; err != nil {
|
||||||
return fmt.Errorf("查询订单明细失败: %v", err)
|
return fmt.Errorf("查询订单明细失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3457,15 +3424,8 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Model(&salesOrder).Updates(map[string]interface{}{
|
|
||||||
"status": constant.SalesStatusCancelled,
|
|
||||||
"updated_at": now,
|
|
||||||
}).Error; err != nil {
|
|
||||||
return fmt.Errorf("更新销售订单状态失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Model(&models.SalesOrderItem{}).
|
if err := tx.Model(&models.SalesOrderItem{}).
|
||||||
Where("sales_order_id = ? AND is_del = 0", orderID).
|
Where("sales_order_id = ? AND is_del = 0", salesOrder.ID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"allocated_quantity": 0,
|
"allocated_quantity": 0,
|
||||||
"updated_at": now,
|
"updated_at": now,
|
||||||
@ -3473,30 +3433,125 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
|
|||||||
return fmt.Errorf("重置订单明细已分配数量失败: %v", err)
|
return fmt.Errorf("重置订单明细已分配数量失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if salesOrder.Status == constant.SalesStatusAllocated || salesOrder.Status == constant.SalesStatusPicking {
|
if err := tx.Model(&salesOrder).Updates(map[string]interface{}{
|
||||||
var waveHeader models.WaveHeader
|
"status": constant.SalesStatusCancelled,
|
||||||
if err := tx.Where("related_order_id = ? AND direction = ? AND is_del = 0",
|
|
||||||
orderID, constant.DirectionOutbound).First(&waveHeader).Error; err == nil {
|
|
||||||
if err := tx.Model(&models.WaveHeader{}).Where("id = ?", waveHeader.ID).Updates(map[string]interface{}{
|
|
||||||
"status": constant.WaveStatusCancelled,
|
|
||||||
"updated_at": now,
|
"updated_at": now,
|
||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
return fmt.Errorf("更新关联波次状态失败: %v", err)
|
return fmt.Errorf("更新销售订单状态失败: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Model(&models.WaveTask{}).Where("wave_id = ?", waveHeader.ID).Updates(map[string]interface{}{
|
|
||||||
"status": constant.WaveStatusCancelled,
|
|
||||||
"updated_at": now,
|
|
||||||
}).Error; err != nil {
|
|
||||||
return fmt.Errorf("更新关联波次任务状态失败: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelSalesOrder 取消销售订单并释放锁定库存
|
||||||
|
// ... existing code ...
|
||||||
|
func (s *ProcessService) UnlockSalesOrderInventory(aboutID int64, associationOrderNo string, operator string, operatorID int64) (*systemRes.UnlockInventoryResponse, error) {
|
||||||
|
databaseConn, err := database.GetTenantDB(aboutID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取数据库连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
var unlockResp *systemRes.UnlockInventoryResponse
|
||||||
|
|
||||||
|
err = executeInTransactionWithDB(databaseConn, func(tx *gorm.DB) error {
|
||||||
|
var salesOrder models.SalesOrder
|
||||||
|
if err := tx.Where("association_order_no = ? AND is_del = 0", associationOrderNo).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: salesOrder.SoNo,
|
||||||
|
AssociationOrderNo: 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) {
|
||||||
|
|
||||||
@ -4077,6 +4132,18 @@ func (s *ProcessService) syncProductsToExternal(receivingOrderID, waveTaskID, us
|
|||||||
"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": "",
|
||||||
|
"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{}{
|
||||||
@ -4134,6 +4201,18 @@ func (s *ProcessService) syncProductsToExternal(receivingOrderID, waveTaskID, us
|
|||||||
"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": "",
|
||||||
|
"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{}{
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1926,6 +2256,18 @@ func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTas
|
|||||||
"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": "",
|
||||||
|
"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)
|
||||||
@ -2109,6 +2444,18 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models.
|
|||||||
"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": "",
|
||||||
|
"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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2270,6 +2613,18 @@ func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product
|
|||||||
"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": "",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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("查询总数失败")
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"psi/database"
|
"psi/database"
|
||||||
"psi/models"
|
"psi/models"
|
||||||
@ -13,7 +14,62 @@ import (
|
|||||||
type StatistService struct{}
|
type StatistService struct{}
|
||||||
|
|
||||||
// DashboardStatist 获取仪表盘统计数据
|
// DashboardStatist 获取仪表盘统计数据
|
||||||
func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
|
/*func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
|
||||||
|
databaseConn := database.OptionalDB(db...)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
|
||||||
|
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
|
||||||
|
|
||||||
|
if req.StartDate == 0 {
|
||||||
|
req.StartDate = startOfDay
|
||||||
|
}
|
||||||
|
if req.EndDate == 0 {
|
||||||
|
req.EndDate = endOfDay
|
||||||
|
}
|
||||||
|
|
||||||
|
statDateStr := now.Format("20060102")
|
||||||
|
var statDate int64
|
||||||
|
if _, err := fmt.Sscanf(statDateStr, "%d", &statDate); err != nil {
|
||||||
|
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dashboardStat models.DashboardDailyStat
|
||||||
|
err := databaseConn.Where("stat_date = ? AND is_del = ?", statDate, 0).First(&dashboardStat).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userStats []systemRes.UserStatItem
|
||||||
|
var userDailyStats []models.UserDailyStat
|
||||||
|
if err := databaseConn.Where("stat_date = ? AND is_del = ?", statDate, 0).Find(&userDailyStats).Error; err == nil {
|
||||||
|
for _, userStat := range userDailyStats {
|
||||||
|
userStats = append(userStats, systemRes.UserStatItem{
|
||||||
|
UserID: userStat.UserID,
|
||||||
|
UserName: userStat.UserName,
|
||||||
|
ReceivingCount: userStat.ReceivingNum,
|
||||||
|
OutboundCount: userStat.OutboundNum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &systemRes.DashboardStatistResponse{
|
||||||
|
TotalReceivingCount: dashboardStat.TotalReceivingNum,
|
||||||
|
TotalOutboundCount: dashboardStat.TotalOutboundNum,
|
||||||
|
TotalSaleCount: dashboardStat.TodaySalesCount,
|
||||||
|
UserStats: userStats,
|
||||||
|
ProductTotal: dashboardStat.ProductTotal,
|
||||||
|
InventoryTotal: dashboardStat.InventoryTotal,
|
||||||
|
TodayInbound: dashboardStat.TodayInbound,
|
||||||
|
TodayOutbound: dashboardStat.TodayOutbound,
|
||||||
|
YesterdayInbound: dashboardStat.YesterdayInbound,
|
||||||
|
YesterdayOutbound: dashboardStat.YesterdayOutbound,
|
||||||
|
}, nil
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// DashboardStatist 获取仪表盘统计数据
|
||||||
|
/*func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
|
||||||
databaseConn := database.OptionalDB(db...)
|
databaseConn := database.OptionalDB(db...)
|
||||||
|
|
||||||
// 计算今日的时间范围(如果未指定)
|
// 计算今日的时间范围(如果未指定)
|
||||||
@ -94,6 +150,152 @@ func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest,
|
|||||||
TotalSaleCount: totalSaleCount,
|
TotalSaleCount: totalSaleCount,
|
||||||
UserStats: userStats,
|
UserStats: userStats,
|
||||||
}, nil
|
}, nil
|
||||||
|
}*/
|
||||||
|
// DashboardStatist 获取仪表盘统计数据
|
||||||
|
func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
|
||||||
|
databaseConn := database.OptionalDB(db...)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
|
||||||
|
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
|
||||||
|
|
||||||
|
if req.StartDate == 0 {
|
||||||
|
req.StartDate = startOfDay
|
||||||
|
}
|
||||||
|
if req.EndDate == 0 {
|
||||||
|
req.EndDate = endOfDay
|
||||||
|
}
|
||||||
|
|
||||||
|
statDateStr := now.Format("20060102")
|
||||||
|
var statDate int64
|
||||||
|
if _, err := fmt.Sscanf(statDateStr, "%d", &statDate); err != nil {
|
||||||
|
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dashboardStat models.DashboardDailyStat
|
||||||
|
err := databaseConn.Where("stat_date = ? AND is_del = ?", statDate, 0).First(&dashboardStat).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userStats []systemRes.UserStatItem
|
||||||
|
var userDailyStats []models.UserDailyStat
|
||||||
|
if err := databaseConn.Where("stat_date = ? AND is_del = ?", statDate, 0).Find(&userDailyStats).Error; err == nil {
|
||||||
|
for _, userStat := range userDailyStats {
|
||||||
|
userStats = append(userStats, systemRes.UserStatItem{
|
||||||
|
UserID: userStat.UserID,
|
||||||
|
UserName: userStat.UserName,
|
||||||
|
ReceivingCount: userStat.ReceivingNum,
|
||||||
|
OutboundCount: userStat.OutboundNum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &systemRes.DashboardStatistResponse{
|
||||||
|
TotalReceivingCount: dashboardStat.TotalReceivingNum,
|
||||||
|
TotalOutboundCount: dashboardStat.TotalOutboundNum,
|
||||||
|
TotalSaleCount: dashboardStat.TodaySalesCount,
|
||||||
|
UserStats: userStats,
|
||||||
|
ProductTotal: dashboardStat.ProductTotal,
|
||||||
|
InventoryTotal: dashboardStat.InventoryTotal,
|
||||||
|
TodayInbound: dashboardStat.TodayInbound,
|
||||||
|
TodayOutbound: dashboardStat.TodayOutbound,
|
||||||
|
YesterdayInbound: dashboardStat.YesterdayInbound,
|
||||||
|
YesterdayOutbound: dashboardStat.YesterdayOutbound,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDashboardStatRealtime 实时查询仪表盘统计数据(降级方案)
|
||||||
|
func (s *StatistService) getDashboardStatRealtime(databaseConn *gorm.DB, startDate, endDate int64) (*systemRes.DashboardStatistResponse, error) {
|
||||||
|
var totalSaleCount int64
|
||||||
|
saleOrderQuery := databaseConn.Model(&models.SalesOrder{}).
|
||||||
|
Where("created_at >= ? AND created_at <= ? AND is_del = ?", startDate, endDate, 0)
|
||||||
|
saleOrderQuery.Count(&totalSaleCount)
|
||||||
|
|
||||||
|
var totalReceivingCount, totalOutboundCount int64
|
||||||
|
statistQuery := databaseConn.Model(&models.Statist{}).
|
||||||
|
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", startDate, endDate, 0)
|
||||||
|
|
||||||
|
var statistList []models.Statist
|
||||||
|
if err := statistQuery.Find(&statistList).Error; err != nil {
|
||||||
|
return nil, utils.NewError("查询统计数据失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statistList) == 0 {
|
||||||
|
return &systemRes.DashboardStatistResponse{
|
||||||
|
TotalReceivingCount: 0,
|
||||||
|
TotalOutboundCount: 0,
|
||||||
|
TotalSaleCount: totalSaleCount,
|
||||||
|
UserStats: []systemRes.UserStatItem{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stat := range statistList {
|
||||||
|
totalReceivingCount += stat.ReceivingNum
|
||||||
|
totalOutboundCount += stat.OutboundNum
|
||||||
|
}
|
||||||
|
|
||||||
|
var userStats []systemRes.UserStatItem
|
||||||
|
|
||||||
|
type UserStatGroup struct {
|
||||||
|
CreateBy int64 `gorm:"column:create_by"`
|
||||||
|
TotalReceiving int64 `gorm:"column:total_receiving"`
|
||||||
|
TotalOutbound int64 `gorm:"column:total_outbound"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var userStatGroups []UserStatGroup
|
||||||
|
databaseConn.Model(&models.Statist{}).
|
||||||
|
Select("create_by, SUM(receiving_num) as total_receiving, SUM(outbound_num) as total_outbound").
|
||||||
|
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", startDate, endDate, 0).
|
||||||
|
Group("create_by").
|
||||||
|
Scan(&userStatGroups)
|
||||||
|
|
||||||
|
for _, group := range userStatGroups {
|
||||||
|
var employee models.Employee
|
||||||
|
if err := database.DB.Where("id = ?", group.CreateBy).First(&employee).Error; err == nil {
|
||||||
|
userStats = append(userStats, systemRes.UserStatItem{
|
||||||
|
UserID: employee.ID,
|
||||||
|
UserName: employee.Username,
|
||||||
|
ReceivingCount: group.TotalReceiving,
|
||||||
|
OutboundCount: group.TotalOutbound,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var productTotal int64
|
||||||
|
databaseConn.Model(&models.Product{}).Where("is_del = ?", 0).Count(&productTotal)
|
||||||
|
|
||||||
|
var inventoryTotal int64
|
||||||
|
databaseConn.Model(&models.Inventory{}).Where("is_del = ?", 0).Select("COALESCE(SUM(quantity), 0)").Row().Scan(&inventoryTotal)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
yesterdayStart := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location()).Unix()
|
||||||
|
yesterdayEnd := time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location()).Unix()
|
||||||
|
|
||||||
|
var todayInbound, todayOutbound, yesterdayInbound, yesterdayOutbound int64
|
||||||
|
databaseConn.Model(&models.Statist{}).
|
||||||
|
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", startDate, endDate, 0).
|
||||||
|
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||||||
|
Row().Scan(&todayInbound, &todayOutbound)
|
||||||
|
|
||||||
|
databaseConn.Model(&models.Statist{}).
|
||||||
|
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", yesterdayStart, yesterdayEnd, 0).
|
||||||
|
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||||||
|
Row().Scan(&yesterdayInbound, &yesterdayOutbound)
|
||||||
|
|
||||||
|
return &systemRes.DashboardStatistResponse{
|
||||||
|
TotalReceivingCount: totalReceivingCount,
|
||||||
|
TotalOutboundCount: totalOutboundCount,
|
||||||
|
TotalSaleCount: totalSaleCount,
|
||||||
|
UserStats: userStats,
|
||||||
|
ProductTotal: productTotal,
|
||||||
|
InventoryTotal: inventoryTotal,
|
||||||
|
TodayInbound: todayInbound,
|
||||||
|
TodayOutbound: todayOutbound,
|
||||||
|
YesterdayInbound: yesterdayInbound,
|
||||||
|
YesterdayOutbound: yesterdayOutbound,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWarehouseStatist 获取仓库统计数据
|
// GetWarehouseStatist 获取仓库统计数据
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user