From 4253c6ade0d8ed49f91402449fd048a0ef6fe962 Mon Sep 17 00:00:00 2001 From: Administrator <1269936630@qq.com> Date: Mon, 22 Jun 2026 16:14:10 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=95=86=E5=93=81=E5=AE=9E=E7=8E=B0=E5=A4=9A?= =?UTF-8?q?=E5=9B=BE/api/product/list=202.=E5=9C=A8=E5=95=86=E5=93=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=8B=E7=9A=84=E5=95=86=E5=93=81=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=8E=A5=E5=8F=A3=E9=87=8C=20=E5=88=86=E5=88=AB?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=EF=BC=9A=E5=B7=B2=E8=90=BD=E4=BD=8D=20locate?= =?UTF-8?q?dCount,=20=E6=9C=AA=E8=90=BD=E4=BD=8D=20unlocatedCount,=20?= =?UTF-8?q?=E5=90=AF=E7=94=A8=E4=B8=AD=20enabledCount,=20=E5=B7=B2?= =?UTF-8?q?=E7=A6=81=E7=94=A8=20disabledCount=204=E4=B8=AA=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=B9=B6=E8=BF=9B=E8=A1=8C=E8=BF=94=E5=9B=9E=203.?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA=E6=8E=A5=E5=8F=A3=20?= =?UTF-8?q?=E5=AF=B9=E5=95=86=E5=93=81=E5=90=8D=E7=A7=B0=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BF=AE=E6=94=B9,=E5=AF=B9=E5=AE=9E?= =?UTF-8?q?=E6=8B=8D=E5=9B=BE=E5=AD=97=E6=AE=B5=E4=BF=AE=E6=94=B9=EF=BC=8C?= =?UTF-8?q?=E5=AE=9E=E6=8B=8D=E5=9B=BE=E5=8F=AF=E4=BB=A5=E4=B8=BA=E5=8D=95?= =?UTF-8?q?=E5=9B=BE=E6=88=96=E8=80=85=E5=A4=9A=E5=9B=BE=204.=E6=B3=A2?= =?UTF-8?q?=E6=AC=A1=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8-=20=E7=BC=BA?= =?UTF-8?q?=E5=B0=91=E7=BB=9F=E8=AE=A1=E4=BF=A1=E6=81=AF=EF=BC=9A=E5=88=86?= =?UTF-8?q?=E5=88=AB=E4=B8=BA=E4=BB=8A=E6=97=A5=E5=85=A5=E5=BA=93=E6=B3=A2?= =?UTF-8?q?=E6=AC=A1=E6=95=B0=20today=5Finbound=5Fwaves=EF=BC=8C=20?= =?UTF-8?q?=E6=98=A8=E6=97=A5=E5=85=A5=E5=BA=93=E6=B3=A2=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=20=EF=BC=8Cyesterday=5Finbound=5Fwaves=20=20=E4=BB=8A=E6=97=A5?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E6=95=B0=E9=87=8F=EF=BC=8Ctoday=5Finbound=5F?= =?UTF-8?q?qty=20=E6=98=A8=E6=97=A5=E5=85=A5=E5=BA=93=E6=95=B0=E9=87=8F?= =?UTF-8?q?=EF=BC=8Cyesterday=5Finbound=5Fqty=20=E4=BB=8A=E6=97=A5?= =?UTF-8?q?=E5=87=BA=E5=BA=93=E6=95=B0=E9=87=8F=EF=BC=8Ctoday=5Foutbound?= =?UTF-8?q?=5Fqty=20=E6=98=A8=E6=97=A5=E5=87=BA=E5=BA=93=E6=95=B0=E9=87=8F?= =?UTF-8?q?=20yesterday=5Foutbound=5Fqty=205.=E9=87=87=E8=B4=AD=E5=8D=95?= =?UTF-8?q?=E5=88=97=E8=A1=A8-=20=E7=BC=BA=E5=B0=91=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=88=B0=E6=97=BA=E5=BA=97=E9=80=9A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controllers/product.go | 27 +++++ controllers/purchase.go | 22 +++++ models/request/product.go | 7 ++ models/request/purchase.go | 11 +++ models/response/product.go | 12 ++- models/response/wave.go | 14 ++- routes/routes.go | 14 ++- service/product.go | 159 +++++++++++++++++++++++++++--- service/purchase.go | 196 ++++++++++++++++++++++++++++++++++++- service/wave.go | 68 ++++++++++++- 10 files changed, 500 insertions(+), 30 deletions(-) diff --git a/controllers/product.go b/controllers/product.go index 46ae775..2245772 100644 --- a/controllers/product.go +++ b/controllers/product.go @@ -540,3 +540,30 @@ func parseNewImageFromForm(c *gin.Context) ([]string, error) { return images, nil } + +// UpdateProductNameAndImages 修改商品名称和实拍图 +func (r *ProductApi) UpdateProductNameAndImages(c *gin.Context) { + var req systemReq.UpdateProductNameAndImagesRequest + + if err := c.ShouldBind(&req); err != nil { + ValidAndFail(constant.LoggerChannelRequest, "修改商品信息请求参数异常", "参数错误: "+err.Error(), c, err) + return + } + + // 处理live_image参数: 如果表单未绑定,尝试从原始表单解析 + if len(req.LiveImage) == 0 { + image, err := parseImageFromForm(c) + if err != nil { + systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c) + return + } + req.LiveImage = image + } + + if err := productService.UpdateProductNameAndImages(req, database.GetDB(c)); err != nil { + utils.FailWithRequestLog(constant.LoggerChannelWork, "修改商品信息异常", err, c, req) + return + } + + systemRes.OkWithMessage("修改成功", c) +} diff --git a/controllers/purchase.go b/controllers/purchase.go index 3750474..23251c1 100644 --- a/controllers/purchase.go +++ b/controllers/purchase.go @@ -55,3 +55,25 @@ func (r *PurchaseApi) GetPurchaseOrderDetail(c *gin.Context) { systemRes.OkWithDetailed(result, "查询成功", c) } + +// ExportPurchaseOrderToWDT 导出采购单到旺店通 +func (r *PurchaseApi) ExportPurchaseOrderToWDT(c *gin.Context) { + var req systemReq.ExportPurchaseOrderToWDTRequest + + if err := c.ShouldBindQuery(&req); err != nil { + ValidAndFail(constant.LoggerChannelRequest, "导出采购单请求参数异常", "参数错误: "+err.Error(), c, err) + return + } + + userInfo := utils.GetUserInfo(c) + result, err := purchaseService.ExportPurchaseOrderToWDT(req, userInfo.ID, userInfo.Role, database.GetDB(c)) + if err != nil { + utils.FailWithRequestLog(constant.LoggerChannelWork, "导出采购单异常", err, c, req) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 200, + "data": result, + }) +} diff --git a/models/request/product.go b/models/request/product.go index d77cd9c..d79402a 100644 --- a/models/request/product.go +++ b/models/request/product.go @@ -190,3 +190,10 @@ type PushProductToShopRequest struct { Photos []string `form:"photos" json:"photos"` // 照片数组 Appearance int64 `form:"appearance" json:"appearance" binding:"required"` // 品相 } + +// UpdateProductNameAndImagesRequest 修改商品名称和实拍图请求 +type UpdateProductNameAndImagesRequest struct { + ProductID int64 `form:"product_id" binding:"required"` // 商品ID + Name string `form:"name"` // 商品名称(可选) + LiveImage []string `form:"live_image[]"` // 商品实拍图(可选,支持单图或多图) +} diff --git a/models/request/purchase.go b/models/request/purchase.go index 05f4fe8..8fd7610 100644 --- a/models/request/purchase.go +++ b/models/request/purchase.go @@ -16,3 +16,14 @@ type GetPurchaseOrderListRequest struct { type GetPurchaseOrderDetailRequest struct { ID int64 `form:"id" binding:"required"` } + +// ExportPurchaseOrderToWDTRequest 导出采购单到旺店通请求 +type ExportPurchaseOrderToWDTRequest struct { + IDs []int64 `form:"ids[]"` // 采购单ID列表(可选,不传则导出全部) + Status int8 `form:"status"` // 状态筛选(可选) + SupplierID int64 `form:"supplier_id"` // 供应商筛选(可选) + WarehouseID int64 `form:"warehouse_id"` // 仓库筛选(可选) + PoNo string `form:"po_no"` // 采购单号筛选(可选) + StartDate int64 `form:"start_date"` // 开始时间筛选(可选) + EndDate int64 `form:"end_date"` // 结束时间筛选(可选) +} diff --git a/models/response/product.go b/models/response/product.go index 5250695..357d836 100644 --- a/models/response/product.go +++ b/models/response/product.go @@ -6,10 +6,14 @@ import ( ) type ProductListResponse struct { - List []ProductItem `json:"list"` - Total int64 `json:"total"` - Page int `json:"page"` - PageSize int `json:"pageSize"` + List []ProductItem `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + LocatedCount int64 `json:"located_count"` // 已落位商品数 + UnlocatedCount int64 `json:"unlocated_count"` // 未落位商品数 + EnabledCount int64 `json:"enabled_count"` // 启用中商品数 + DisabledCount int64 `json:"disabled_count"` // 已禁用商品数 } type ProductItem struct { diff --git a/models/response/wave.go b/models/response/wave.go index fb68e7b..36f8f2e 100644 --- a/models/response/wave.go +++ b/models/response/wave.go @@ -24,10 +24,16 @@ type DetailWithInfo struct { // WaveTaskListResponse 波次任务列表响应 type WaveTaskListResponse struct { - List []WaveTaskItem `json:"list"` - Total int64 `json:"total"` - Page int `json:"page"` - PageSize int `json:"pageSize"` + List []WaveTaskItem `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + TodayInboundWaves int64 `json:"today_inbound_waves"` // 今日入库波次数 + YesterdayInboundWaves int64 `json:"yesterday_inbound_waves"` // 昨日入库波次数 + TodayInboundQty int64 `json:"today_inbound_qty"` // 今日入库数量 + YesterdayInboundQty int64 `json:"yesterday_inbound_qty"` // 昨日入库数量 + TodayOutboundQty int64 `json:"today_outbound_qty"` // 今日出库数量 + YesterdayOutboundQty int64 `json:"yesterday_outbound_qty"` // 昨日出库数量 } // WaveTaskItem 波次任务列表项 diff --git a/routes/routes.go b/routes/routes.go index a7fa054..b308143 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -149,9 +149,12 @@ func initRouter() (r *gin.Engine) { auth.GET("/getProCode", bookApi.GetProCode) // 获取商品条码 auth.POST("/syncBook", bookApi.SyncBook) // 同步图书 // 商品管理 - auth.GET("/product/list", productApi.GetProductList) // 商品列表 - auth.GET("/product/detail", productApi.GetProductDetail) // 商品详情 - auth.POST("/product/save", productApi.SaveProduct) // 保存商品 + auth.GET("/product/list", productApi.GetProductList) // 商品列表 + auth.GET("/product/detail", productApi.GetProductDetail) // 商品详情 + auth.POST("/product/save", productApi.SaveProduct) // 保存商品 + + auth.POST("/product/updateNameAndImages", productApi.UpdateProductNameAndImages) // 修改商品名称和实拍图 + auth.POST("/product/delete", productApi.DeleteProduct) // 删除商品 auth.POST("/product/retry-out-task", productApi.RetryOutTask) // 重新出库 auth.GET("/product/export", productApi.ExportProducts) // 导出商品 @@ -184,8 +187,9 @@ func initRouter() (r *gin.Engine) { auth.POST("/stock_check/adjust", processApi.AdjustInventory) // 盘库 auth.POST("/stock_check/return", processApi.ReturnInventory) // 盘库退货 // 采购订单管理 - auth.GET("/purchase-order/list", purchaseApi.GetPurchaseOrderList) // 获取采购订单列表 - auth.GET("/purchase-order/detail", purchaseApi.GetPurchaseOrderDetail) // 获取采购订单详情 + auth.GET("/purchase-order/list", purchaseApi.GetPurchaseOrderList) // 获取采购订单列表 + auth.GET("/purchase-order/detail", purchaseApi.GetPurchaseOrderDetail) // 获取采购订单详情 + auth.GET("/purchase-order/export-to-wdt", purchaseApi.ExportPurchaseOrderToWDT) // 导出采购单到旺店通 // 入库订单管理 auth.GET("/receiving-order/list", receivingApi.GetReceivingOrderList) // 获取入库订单列表 auth.GET("/receiving-order/detail", receivingApi.GetReceivingOrderDetail) // 获取入库订单详情 diff --git a/service/product.go b/service/product.go index f2f18b2..0fd15d6 100644 --- a/service/product.go +++ b/service/product.go @@ -116,20 +116,116 @@ func (s *ProductService) GetProductList(req systemReq.GetProductListRequest, db item.ShopList = outTaskInfo.ShopList } productItems = append(productItems, item) - /* // item.LiveImage[0] 按照 , 分割数组 - liveImage := strings.Split(item.LiveImage[0], ",") - item.LiveImage[0] = liveImage[0] - productItems = append(productItems, item)*/ } + locatedCount, unlocatedCount, enabledCount, disabledCount := s.getProductStatistics(databaseConn, req, total) + return &systemRes.ProductListResponse{ - List: productItems, - Total: total, - Page: req.Page, - PageSize: req.PageSize, + List: productItems, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + LocatedCount: locatedCount, + UnlocatedCount: unlocatedCount, + EnabledCount: enabledCount, + DisabledCount: disabledCount, }, nil } +// getProductStatistics 获取商品统计数据 +func (s *ProductService) getProductStatistics(db *gorm.DB, req systemReq.GetProductListRequest, total int64) (int64, int64, int64, int64) { + var enabledCount, disabledCount, locatedCount int64 + + buildBaseQuery := func() *gorm.DB { + query := db.Model(&models.Product{}).Where("product.is_del = ?", 0) + + if len(req.IDs) > 0 { + query = query.Where("product.id IN ?", req.IDs) + } + if req.Keyword != "" { + query = query.Where("product.name LIKE ? OR product.barcode LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%") + } + if req.StartCreatedAt > 0 { + query = query.Where("product.created_at >= ?", req.StartCreatedAt) + } + if req.EndCreatedAt > 0 { + query = query.Where("product.created_at <= ?", req.EndCreatedAt) + } + if req.MinSalePrice > 0 { + query = query.Where("product.sale_price >= ?", req.MinSalePrice) + } + if req.MaxSalePrice > 0 { + query = query.Where("product.sale_price <= ?", req.MaxSalePrice) + } + + hasInventoryFilter := req.WarehouseID > 0 || req.MinStock > 0 || req.MaxStock > 0 || req.LocationID > 0 + if hasInventoryFilter { + query = query.Joins("LEFT JOIN inventory_detail inv_filter ON inv_filter.product_id = product.id AND inv_filter.is_del = ?", 0) + if req.WarehouseID > 0 { + query = query.Where("inv_filter.warehouse_id = ?", req.WarehouseID) + } + if req.MinStock > 0 { + query = query.Where("COALESCE(inv_filter.quantity, 0) > ?", req.MinStock) + } + if req.MaxStock > 0 { + query = query.Where("COALESCE(inv_filter.quantity, 0) < ?", req.MaxStock) + } + if req.LocationID > 0 { + query = query.Where("inv_filter.location_id = ?", req.LocationID) + } + } + + return query + } + + buildBaseQuery().Where("product.status = ?", 1).Count(&enabledCount) + buildBaseQuery().Where("product.status = ?", 0).Count(&disabledCount) + + locatedQuery := db.Table("product"). + Joins("INNER JOIN inventory_detail inv ON inv.product_id = product.id AND inv.is_del = ?", 0). + Where("product.is_del = ?", 0) + + if len(req.IDs) > 0 { + locatedQuery = locatedQuery.Where("product.id IN ?", req.IDs) + } + if req.Status != "" { + locatedQuery = locatedQuery.Where("product.status = ?", req.Status) + } + if req.Keyword != "" { + locatedQuery = locatedQuery.Where("product.name LIKE ? OR product.barcode LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%") + } + if req.StartCreatedAt > 0 { + locatedQuery = locatedQuery.Where("product.created_at >= ?", req.StartCreatedAt) + } + if req.EndCreatedAt > 0 { + locatedQuery = locatedQuery.Where("product.created_at <= ?", req.EndCreatedAt) + } + if req.MinSalePrice > 0 { + locatedQuery = locatedQuery.Where("product.sale_price >= ?", req.MinSalePrice) + } + if req.MaxSalePrice > 0 { + locatedQuery = locatedQuery.Where("product.sale_price <= ?", req.MaxSalePrice) + } + if req.WarehouseID > 0 { + locatedQuery = locatedQuery.Where("inv.warehouse_id = ?", req.WarehouseID) + } + if req.MinStock > 0 { + locatedQuery = locatedQuery.Where("inv.quantity > ?", req.MinStock) + } + if req.MaxStock > 0 { + locatedQuery = locatedQuery.Where("inv.quantity < ?", req.MaxStock) + } + if req.LocationID > 0 { + locatedQuery = locatedQuery.Where("inv.location_id = ?", req.LocationID) + } + + locatedQuery.Distinct("product.id").Count(&locatedCount) + + unlocatedCount := total - locatedCount + + return locatedCount, unlocatedCount, enabledCount, disabledCount +} + // 获取商品任务信息 func (s *ProductService) GetDistributionProductList(req systemReq.GetDistributionProductListRequest) (*systemRes.ProductListResponse, error) { databaseConn, err := database.GetTenantDB(req.UserID) @@ -230,9 +326,7 @@ func (s *ProductService) GetDistributionProductList(req systemReq.GetDistributio var productItems []systemRes.ProductItem for _, product := range products { item := systemRes.ConvertProductWithInfoToItem(product) - /*if outTaskInfo, exists := outTaskInfoMap[product.ID]; exists { - item.ShopList = outTaskInfo.ShopList - }*/ + productItems = append(productItems, item) } @@ -564,6 +658,49 @@ func (s *ProductService) GetProductFullInfo(req systemReq.GetProductFullInfoRequ return response, nil } +// UpdateProductNameAndImages 修改商品名称和实拍图 +func (s *ProductService) UpdateProductNameAndImages(req systemReq.UpdateProductNameAndImagesRequest, db ...*gorm.DB) error { + databaseConn := database.OptionalDB(db...) + + if req.ProductID <= 0 { + return fmt.Errorf("商品ID不能为空") + } + + 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 fmt.Errorf("商品不存在") + } + return fmt.Errorf("查询商品失败: %v", err) + } + + updates := make(map[string]interface{}) + hasUpdate := false + + if req.Name != "" { + updates["name"] = req.Name + hasUpdate = true + } + + if len(req.LiveImage) > 0 { + jsonBytes, _ := json.Marshal(req.LiveImage) + updates["live_image"] = datatypes.JSON(jsonBytes) + hasUpdate = true + } + + if !hasUpdate { + return fmt.Errorf("至少需要提供一个要修改的字段") + } + + updates["updated_at"] = time.Now().Unix() + + if err := databaseConn.Model(&product).Updates(updates).Error; err != nil { + return fmt.Errorf("更新商品失败: %v", err) + } + + return nil +} + // getProductOutTaskInfo func (s *ProductService) getProductOutTaskInfo(db *gorm.DB, productIDs []int64) (map[int64]*OutTaskInfo, error) { resultMap := make(map[int64]*OutTaskInfo) diff --git a/service/purchase.go b/service/purchase.go index e9ad70c..dfc950f 100644 --- a/service/purchase.go +++ b/service/purchase.go @@ -2,17 +2,211 @@ package service import ( "encoding/json" + "psi/constant" + + "fmt" + "psi/config" + "psi/database" "psi/models" systemReq "psi/models/request" systemRes "psi/models/response" - "psi/utils" + "psi/utils" + "time" + + "github.com/xuri/excelize/v2" "gorm.io/gorm" ) type PurchaseService struct{} +// ExportPurchaseOrderToWDT 导出采购单到旺店通 +func (s *PurchaseService) ExportPurchaseOrderToWDT(req systemReq.ExportPurchaseOrderToWDTRequest, creatorID int64, role int64, db ...*gorm.DB) (*systemRes.ExportProductResponse, error) { + databaseConn := database.OptionalDB(db...) + + query := databaseConn.Model(&models.PurchaseOrder{}).Where("purchase_order.is_del = ?", 0) + + if role == 128 { + query = query.Where("purchase_order.creator_id = ?", creatorID) + } + if len(req.IDs) > 0 { + query = query.Where("purchase_order.id IN ?", req.IDs) + } + if req.Status > 0 { + query = query.Where("purchase_order.status = ?", req.Status) + } + if req.SupplierID > 0 { + query = query.Where("purchase_order.supplier_id = ?", req.SupplierID) + } + if req.WarehouseID > 0 { + query = query.Where("purchase_order.warehouse_id = ?", req.WarehouseID) + } + if req.PoNo != "" { + query = query.Where("purchase_order.po_no LIKE ?", "%"+req.PoNo+"%") + } + if req.StartDate > 0 { + query = query.Where("purchase_order.created_at >= ?", req.StartDate) + } + if req.EndDate > 0 { + query = query.Where("purchase_order.created_at <= ?", req.EndDate) + } + + var total int64 + if err := query.Count(&total).Error; err != nil { + return nil, utils.NewError("查询总数失败") + } + + if total == 0 { + return nil, fmt.Errorf("没有符合条件的采购单数据") + } + + type PurchaseOrderExportData struct { + PoNo string `gorm:"column:po_no"` + SupplierName string `gorm:"column:supplier_name"` + WarehouseName string `gorm:"column:warehouse_name"` + ProductName string `gorm:"column:product_name"` + Barcode string `gorm:"column:barcode"` + Quantity int64 `gorm:"column:quantity"` + UnitPrice int64 `gorm:"column:unit_price"` + Amount int64 `gorm:"column:amount"` + OrderDate int64 `gorm:"column:order_date"` + ExpectedArrivalDate int64 `gorm:"column:expected_arrival_date"` + Status int8 `gorm:"column:status"` + Creator string `gorm:"column:creator"` + Remark string `gorm:"column:remark"` + } + + var orders []PurchaseOrderExportData + if err := query.Select(`purchase_order.po_no, + s.name as supplier_name, + w.name as warehouse_name, + p.name as product_name, + p.barcode, + poi.quantity, + poi.unit_price, + poi.amount, + purchase_order.order_date, + purchase_order.expected_arrival_date, + purchase_order.status, + purchase_order.creator, + purchase_order.remark`). + Joins("LEFT JOIN supplier s ON purchase_order.supplier_id = s.id AND s.is_del = 0"). + Joins("LEFT JOIN warehouse w ON purchase_order.warehouse_id = w.id AND w.is_del = 0"). + Joins("LEFT JOIN purchase_order_item poi ON poi.purchase_order_id = purchase_order.id AND poi.is_del = 0"). + Joins("LEFT JOIN product p ON poi.product_id = p.id AND p.is_del = 0"). + Order("purchase_order.created_at DESC"). + Find(&orders).Error; err != nil { + return nil, utils.NewError("查询采购单数据失败") + } + + f := excelize.NewFile() + defer func() { + if err := f.Close(); err != nil { + utils.ErrorLog(constant.LoggerChannelWork, map[string]interface{}{ + "source": "关闭Excel文件", + "error": fmt.Sprintf("关闭失败: %v", err), + }) + } + }() + + sheetName := "Sheet1" + f.SetSheetName("Sheet1", sheetName) + + headers := []string{"采购单号", "供应商", "仓库", "商品名称", "ISBN/条码", "采购数量", "单价(元)", "金额(元)", "订单日期", "预计到货日期", "状态", "创建人", "备注"} + for i, header := range headers { + cell, _ := excelize.CoordinatesToCellName(i+1, 1) + f.SetCellValue(sheetName, cell, header) + } + + headerStyle, _ := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{ + Bold: true, + Size: 12, + }, + Alignment: &excelize.Alignment{ + Horizontal: "center", + Vertical: "center", + }, + Fill: excelize.Fill{ + Type: "pattern", + Color: []string{"#E0E0E0"}, + Pattern: 1, + }, + }) + f.SetCellStyle(sheetName, "A1", "M1", headerStyle) + + statusMap := map[int8]string{ + 1: "草稿", + 2: "已提交", + 3: "已审核", + 4: "部分收货", + 5: "已收货", + 6: "已取消", + } + + for idx, order := range orders { + row := idx + 2 + + f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), order.PoNo) + f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), order.SupplierName) + f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), order.WarehouseName) + f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), order.ProductName) + f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), order.Barcode) + f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), order.Quantity) + f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), float64(order.UnitPrice)/100.0) + f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), float64(order.Amount)/100.0) + + orderDateStr := time.Unix(order.OrderDate, 0).Format("2006-01-02") + f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), orderDateStr) + + expectedDateStr := time.Unix(order.ExpectedArrivalDate, 0).Format("2006-01-02") + f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), expectedDateStr) + + statusText := statusMap[order.Status] + if statusText == "" { + statusText = "未知" + } + f.SetCellValue(sheetName, fmt.Sprintf("K%d", row), statusText) + + f.SetCellValue(sheetName, fmt.Sprintf("L%d", row), order.Creator) + f.SetCellValue(sheetName, fmt.Sprintf("M%d", row), order.Remark) + } + + colWidths := map[string]float64{ + "A": 20, + "B": 15, + "C": 15, + "D": 30, + "E": 15, + "F": 12, + "G": 12, + "H": 12, + "I": 15, + "J": 15, + "K": 12, + "L": 12, + "M": 30, + } + for col, width := range colWidths { + f.SetColWidth(sheetName, col, col, width) + } + + now := time.Now() + fileName := fmt.Sprintf("purchase_order_wdt_%s.xlsx", now.Format("20060102150405")) + filePath := fmt.Sprintf("excel/%s", fileName) + + if err := f.SaveAs(filePath); err != nil { + return nil, fmt.Errorf("保存Excel文件失败: %v", err) + } + + return &systemRes.ExportProductResponse{ + Total: total, + FileName: fileName, + FilePath: config.AppConfig.Server.Host + filePath, + }, nil +} + // GetPurchaseOrderList 获取采购订单列表 func (s *PurchaseService) GetPurchaseOrderList(req systemReq.GetPurchaseOrderListRequest, creatorID int64, role int64, db ...*gorm.DB) (*systemRes.PurchaseOrderListResponse, error) { databaseConn := database.OptionalDB(db...) diff --git a/service/wave.go b/service/wave.go index a068a63..9eaceb0 100644 --- a/service/wave.go +++ b/service/wave.go @@ -3,13 +3,13 @@ package service import ( "encoding/json" "fmt" - "gorm.io/gorm" "psi/database" "psi/models" systemReq "psi/models/request" systemRes "psi/models/response" "psi/utils" + "time" ) type WaveService struct{} @@ -107,15 +107,73 @@ func (s *WaveService) GetWaveTaskList(req systemReq.GetWaveTaskListRequest, crea for _, task := range tasks { taskItems = append(taskItems, systemRes.ConvertWaveTaskToItem(task.WaveTask, task.WaveNo, task.OutOrderId, task.ReceivingOrderId, task.BatchNo)) } + todayInboundWaves, yesterdayInboundWaves, todayInboundQty, yesterdayInboundQty, todayOutboundQty, yesterdayOutboundQty := s.getWaveTaskStatistics(databaseConn) return &systemRes.WaveTaskListResponse{ - List: taskItems, - Total: total, - Page: req.Page, - PageSize: req.PageSize, + List: taskItems, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + TodayInboundWaves: todayInboundWaves, + YesterdayInboundWaves: yesterdayInboundWaves, + TodayInboundQty: todayInboundQty, + YesterdayInboundQty: yesterdayInboundQty, + TodayOutboundQty: todayOutboundQty, + YesterdayOutboundQty: yesterdayOutboundQty, }, nil } +// getWaveTaskStatistics 获取波次任务统计数据 +func (s *WaveService) getWaveTaskStatistics(db *gorm.DB) (int64, int64, int64, int64, int64, int64) { + now := time.Now() + + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix() + yesterdayStart := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location()).Unix() + yesterdayEnd := todayStart + + var todayInboundWaves, yesterdayInboundWaves int64 + var todayInboundQty, yesterdayInboundQty int64 + var todayOutboundQty, yesterdayOutboundQty int64 + + db.Model(&models.WaveHeader{}). + Where("is_del = ? AND direction = ? AND created_at >= ?", 0, 1, todayStart). + Count(&todayInboundWaves) + + db.Model(&models.WaveHeader{}). + Where("is_del = ? AND direction = ? AND created_at >= ? AND created_at < ?", 0, 1, yesterdayStart, yesterdayEnd). + Count(&yesterdayInboundWaves) + + db.Table("wave_task_detail wtd"). + Joins("INNER JOIN wave_task wt ON wt.id = wtd.wave_task_id AND wt.is_del = ?", 0). + Joins("INNER JOIN wave_header wh ON wh.id = wt.wave_id AND wh.is_del = ? AND wh.direction = ?", 0, 1). + Where("wtd.is_del = ? AND wt.created_at >= ?", 0, todayStart). + Select("COALESCE(SUM(wtd.actual_quantity), 0)"). + Scan(&todayInboundQty) + + db.Table("wave_task_detail wtd"). + Joins("INNER JOIN wave_task wt ON wt.id = wtd.wave_task_id AND wt.is_del = ?", 0). + Joins("INNER JOIN wave_header wh ON wh.id = wt.wave_id AND wh.is_del = ? AND wh.direction = ?", 0, 1). + Where("wtd.is_del = ? AND wt.created_at >= ? AND wt.created_at < ?", 0, yesterdayStart, yesterdayEnd). + Select("COALESCE(SUM(wtd.actual_quantity), 0)"). + Scan(&yesterdayInboundQty) + + db.Table("wave_task_detail wtd"). + Joins("INNER JOIN wave_task wt ON wt.id = wtd.wave_task_id AND wt.is_del = ?", 0). + Joins("INNER JOIN wave_header wh ON wh.id = wt.wave_id AND wh.is_del = ? AND wh.direction = ?", 0, 2). + Where("wtd.is_del = ? AND wt.created_at >= ?", 0, todayStart). + Select("COALESCE(SUM(wtd.actual_quantity), 0)"). + Scan(&todayOutboundQty) + + db.Table("wave_task_detail wtd"). + Joins("INNER JOIN wave_task wt ON wt.id = wtd.wave_task_id AND wt.is_del = ?", 0). + Joins("INNER JOIN wave_header wh ON wh.id = wt.wave_id AND wh.is_del = ? AND wh.direction = ?", 0, 2). + Where("wtd.is_del = ? AND wt.created_at >= ? AND wt.created_at < ?", 0, yesterdayStart, yesterdayEnd). + Select("COALESCE(SUM(wtd.actual_quantity), 0)"). + Scan(&yesterdayOutboundQty) + + return todayInboundWaves, yesterdayInboundWaves, todayInboundQty, yesterdayInboundQty, todayOutboundQty, yesterdayOutboundQty +} + // GetWaveTaskDetail 获取波次任务详情 func (s *WaveService) GetWaveTaskDetail(id int64, creatorID int64, role int64, db ...*gorm.DB) (*systemRes.WaveTaskDetailResponse, error) { databaseConn := database.OptionalDB(db...)