From 6725fc0a1fb8ec98e156a6afd94a6cdf9e63d06b Mon Sep 17 00:00:00 2001 From: chenlin Date: Fri, 20 Dec 2024 17:11:09 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3=EF=BC=9A?= =?UTF-8?q?=E9=97=A8=E5=BA=97=E9=94=80=E5=94=AE=E5=AF=B9=E6=AF=94=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/apis/decision/decision.go | 34 +++ app/admin/models/decision.go | 355 ++++++++++++++++++++++++++++ app/admin/router/decision.go | 3 +- 3 files changed, 391 insertions(+), 1 deletion(-) diff --git a/app/admin/apis/decision/decision.go b/app/admin/apis/decision/decision.go index 877c782..34b0900 100644 --- a/app/admin/apis/decision/decision.go +++ b/app/admin/apis/decision/decision.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" model "go-admin/app/admin/models" "go-admin/logger" + "go-admin/tools" "go-admin/tools/app" "net/http" ) @@ -35,3 +36,36 @@ func ErpDecisionReport(c *gin.Context) { app.OK(c, resp, "OK") return } + +// ErpStoreSalesData 门店销售对比 +// @Summary 门店销售对比 +// @Tags 决策中心,V1.4.0 +// @Produce json +// @Accept json +// @Param request body models.ErpStoreSalesDataReq true "门店销售对比数据模型" +// @Success 200 {object} models.ErpStoreSalesDataResp +// @Router /api/v1/decision/store_sales_report [post] +func ErpStoreSalesData(c *gin.Context) { + var req = new(model.ErpStoreSalesDataReq) + if err := c.ShouldBindJSON(&req); err != nil { + logger.Error("ShouldBindJSON err:", logger.Field("err", err)) + app.Error(c, http.StatusBadRequest, err, "参数错误:"+err.Error()) + return + } + + err := tools.Validate(req) //必填参数校验 + if err != nil { + app.Error(c, http.StatusBadRequest, err, err.Error()) + return + } + + resp, err := model.QueryStoreSalesData(req, c) + if err != nil { + logger.Error("QueryStoreSalesData err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, err, "查询失败:"+err.Error()) + return + } + + app.OK(c, resp, "") + return +} diff --git a/app/admin/models/decision.go b/app/admin/models/decision.go index 8d565f9..2d94975 100644 --- a/app/admin/models/decision.go +++ b/app/admin/models/decision.go @@ -2754,3 +2754,358 @@ func reportDecisionExport(req *ErpDecisionReportResp) (string, error) { } return url + fileName, nil } + +// ErpStoreSalesDataReq 门店销售对比入参 +type ErpStoreSalesDataReq struct { + StoreId []uint32 `json:"store_id"` // 门店ID + StartTime string `json:"start_time"` // 开始时间 + EndTime string `json:"end_time"` // 结束时间 + PageIndex int `json:"pageIndex"` // 页码 + PageSize int `json:"pageSize"` // 页面条数 + IsExport uint32 `json:"is_export"` // 1-导出 + SortType string `json:"sort_type"` // 排序类型:desc 降序、asc 升序 +} + +// ErpStoreSalesDataResp 门店销售对比出参 +type ErpStoreSalesDataResp struct { + List []StoreSalesData `json:"list"` + Total int `json:"total"` // 总条数 + PageIndex int `json:"pageIndex"` // 页码 + PageSize int `json:"pageSize"` // 每页展示条数 + ExportUrl string `json:"export_url"` + TotalSalesAmount float64 `json:"total_sales_amount"` // 总销售额 + TotalPromotionFee float64 `json:"total_promotion_fee"` // 总推广费 + TotalSalesProfit float64 `json:"total_sales_profit"` // 总销售毛利 + TotalStaffProfit float64 `json:"total_staff_profit"` // 总员工毛利 + TotalCount int64 `json:"total_count"` // 总销售数量 +} + +// StoreSalesData 门店销售数据 +type StoreSalesData struct { + StoreId uint32 `json:"store_id"` // 门店id + StoreName string `json:"store_name"` // 门店名称 + TotalSalesAmount float64 `json:"total_sales_amount"` // 销售额 + PromotionFee float64 `json:"promotion_fee"` // 推广费 + SalesProfit float64 `json:"sales_profit"` // 销售毛利 + StaffProfit float64 `json:"staff_profit"` // 员工毛利 + Count int64 `json:"count"` // 销售数量 +} + +// QueryStoreSalesData 查询门店销售对比数据 +func QueryStoreSalesData(req *ErpStoreSalesDataReq, c *gin.Context) (*ErpStoreSalesDataResp, error) { + showConfig, err := GetErpOrderShowConfig() + if err != nil { + logger.Errorf("List err:", err) + showConfig.ShowAll = "ON" + } + + page := req.PageIndex - 1 + if page < 0 { + page = 0 + } + if req.PageSize == 0 { + req.PageSize = 10 + } + + resp := &ErpStoreSalesDataResp{ + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + var storeManageDataList []StoreSalesData + + // 构建查询条件 + qs := orm.Eloquent.Model(&ErpOrder{}) + if len(req.StoreId) != 0 { + qs.Where("erp_order.store_id in ?", req.StoreId) + } + + var storeList []uint32 + // 非管理员才判断所属门店 + if !(tools.GetRoleName(c) == "admin" || tools.GetRoleName(c) == "系统管理员") { + sysUser, err := GetSysUserByCtx(c) + if err != nil { + return nil, errors.New("操作失败:" + err.Error()) + } + + // 返回sysUser未过期的门店id列表 + storeList = GetValidStoreIDs(sysUser.StoreData) + if len(storeList) > 0 { + if len(storeList) == 1 { + qs = qs.Where("store_id = ?", storeList[0]) + } else { + qs = qs.Where("store_id IN (?)", storeList) + } + } else { + return nil, errors.New("用户未绑定门店") + } + } else { + var allStoreList []Store + err = orm.Eloquent.Table("store").Where("is_online = 1 and cooperative_business_id = 1"). + Find(&allStoreList).Error + if err != nil { + return nil, err + } + + for _, v := range allStoreList { + storeList = append(storeList, v.ID) + } + } + + if showConfig.ShowAll == "OFF" { + qs = qs.Where("is_print = ? or retail_type = ?", HavePrinted, RetailTypeRejected) + } + + if req.StartTime != "" && req.EndTime != "" { + startTime, err := time.Parse(QueryTimeFormat, req.StartTime) + if err != nil { + logger.Error("startTime parse err") + } + + endTime, err := time.Parse(QueryTimeFormat, req.EndTime) + if err != nil { + logger.Error("endTime parse err") + } + + qs = qs.Where("maker_time BETWEEN ? AND ?", startTime, endTime) + } else { + qs = qs.Where("maker_time IS NOT NULL") + } + qs.Where("state = ?", ErpOrderStateAudited) + + // 查询汇总数据 + var summary struct { + TotalSalesAmount float64 + TotalPromotionFee float64 + TotalSalesProfit float64 + TotalStaffProfit float64 + TotalCount int64 + } + err = qs.Select("SUM(CASE WHEN retail_type = 'sale' THEN total_amount ELSE -total_amount END) AS total_sales_amount, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_discount ELSE -total_discount END) AS total_promotion_fee, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_sales_profit ELSE -total_sales_profit END) AS total_sales_profit, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_staff_profit ELSE -total_staff_profit END) AS total_staff_profit, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_count ELSE -total_count END) AS total_count"). + Find(&summary).Error + if err != nil { + logger.Error("QueryStoreManageData summary err:", logger.Field("err", err)) + return nil, err + } + + // 查询分页数据 按 store_id 进行汇总 + var storeData []struct { + StoreId uint32 `json:"store_id"` + TotalSalesAmount float64 `json:"total_sales_amount"` + PromotionFee float64 `json:"promotion_fee"` + SalesProfit float64 `json:"sales_profit"` + StaffProfit float64 `json:"staff_profit"` + Count int64 `json:"count"` + } + err = qs.Select("store_id, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_discount ELSE -total_discount END) AS promotion_fee, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_amount ELSE -total_amount END) AS total_sales_amount, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_sales_profit ELSE -total_sales_profit END) AS sales_profit, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_staff_profit ELSE -total_staff_profit END) AS staff_profit, " + + "SUM(CASE WHEN retail_type = 'sale' THEN total_count ELSE -total_count END) AS count"). + Group("store_id"). + Order("store_id"). + Find(&storeData).Error + if err != nil { + logger.Error("QueryStoreManageData err:", logger.Field("err", err)) + return nil, err + } + + storeMap, err := GetAllStoreData() + if err != nil { + return nil, err + } + + // 组合数据 + for _, storeId := range storeList { + flag := false + for _, v := range storeData { + if v.StoreId == storeId { + flag = true + storeManageDataList = append(storeManageDataList, StoreSalesData{ + StoreId: v.StoreId, + StoreName: storeMap[v.StoreId].Name, + TotalSalesAmount: math.Round(v.TotalSalesAmount*100) / 100, + PromotionFee: math.Round(v.PromotionFee*100) / 100, + SalesProfit: math.Round(v.SalesProfit*100) / 100, + StaffProfit: math.Round(v.StaffProfit*100) / 100, + Count: v.Count, + }) + } else { + continue + } + } + + if !flag { + storeManageDataList = append(storeManageDataList, StoreSalesData{ + StoreId: storeId, + StoreName: storeMap[storeId].Name, + TotalSalesAmount: 0, + PromotionFee: 0, + SalesProfit: 0, + StaffProfit: 0, + Count: 0, + }) + } + } + + // 汇总数据赋值给响应 + resp.TotalSalesAmount = math.Round(summary.TotalSalesAmount*100) / 100 + resp.TotalPromotionFee = math.Round(summary.TotalPromotionFee*100) / 100 + resp.TotalSalesProfit = math.Round(summary.TotalSalesProfit*100) / 100 + resp.TotalStaffProfit = math.Round(summary.TotalStaffProfit*100) / 100 + resp.TotalCount = summary.TotalCount + + if req.IsExport == 1 { //导出excel + filePath, err := storeSalesDataExport(storeManageDataList, summary, c) + if err != nil { + logger.Error("StoreManageDataExport err:", logger.Field("err", err)) + return nil, err + } + + resp.ExportUrl = filePath + } else { + // 根据页码和页面条数截取结果 + startIndex := page * req.PageSize + endIndex := startIndex + req.PageSize + if endIndex > len(storeManageDataList) { + endIndex = len(storeManageDataList) + } + resp.List = storeManageDataList[startIndex:endIndex] + + resp.Total = len(storeManageDataList) + } + + return resp, nil +} + +// storeSalesDataExport 导出门店经营数据 +func storeSalesDataExport(list []StoreSalesData, summary struct { + TotalSalesAmount float64 + TotalPromotionFee float64 + TotalSalesProfit float64 + TotalStaffProfit float64 + TotalCount int64 +}, c *gin.Context) (string, error) { + file := excelize.NewFile() + fSheet := "Sheet1" + + url := config.ExportConfig.Url + fileName := time.Now().Format(TimeFormat) + "门店销售对比" + ".xlsx" + fmt.Println("url fileName:", url+fileName) + + // 判断是否有入销售毛利、员工毛利的权限 + flag1, _ := checkRoleMenu(c, SalesProfitMenu) + flag2, _ := checkRoleMenu(c, StaffProfitMenu) + fmt.Println("flag1 is:", flag1) + fmt.Println("flag2 is:", flag2) + logger.Info("flag1 is:", logger.Field("flag1", flag1)) + logger.Info("flag2 is:", logger.Field("flag2", flag2)) + + nEndCount := 0 + title := []interface{}{"店铺名称", "销售额", "推广费"} + if flag1 { // 销售毛利 + title = append(title, "销售毛利") + nEndCount += 1 + } + if flag2 { // 员工毛利 + title = append(title, "员工毛利") + nEndCount += 1 + } + title = append(title, "销售数量") + + for i, _ := range title { + cell, _ := excelize.CoordinatesToCellName(1+i, 1) + err := file.SetCellValue(fSheet, cell, title[i]) + if err != nil { + logger.Errorf("file set value err:", err) + } + } + + var row []interface{} + nExcelStartRow := 0 + for rowId := 0; rowId < len(list); rowId++ { + row = []interface{}{ + list[rowId].StoreName, + list[rowId].TotalSalesAmount, + list[rowId].PromotionFee, + } + + // 控制是否导出 + if flag1 { // 销售毛利 + row = append(row, list[rowId].SalesProfit) + } + if flag2 { // 员工毛利 + row = append(row, list[rowId].StaffProfit) + } + row = append(row, list[rowId].Count) + + for j, _ := range row { + cell, _ := excelize.CoordinatesToCellName(1+j, nExcelStartRow+2) + err := file.SetCellValue(fSheet, cell, row[j]) + if err != nil { + logger.Error("file set value err:", logger.Field("err", err)) + } + } + nExcelStartRow++ + } + + totalData := "汇总 记录数:" + strconv.FormatInt(int64(len(list)), 10) + end := []interface{}{totalData, summary.TotalSalesAmount, summary.TotalPromotionFee} + + if flag1 { // 销售毛利 + end = append(end, summary.TotalSalesProfit) + } + if flag2 { // 员工毛利 + end = append(end, summary.TotalStaffProfit) + } + end = append(end, summary.TotalCount) + + for i, _ := range end { + cell, _ := excelize.CoordinatesToCellName(1+i, nExcelStartRow+2) + err := file.SetCellValue(fSheet, cell, end[i]) + if err != nil { + logger.Error("file set value err:", logger.Field("err", err)) + } + } + + // 设置所有单元格的样式: 居中、加边框 + style, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"}, + "border":[{"type":"left","color":"000000","style":1}, + {"type":"top","color":"000000","style":1}, + {"type":"right","color":"000000","style":1}, + {"type":"bottom","color":"000000","style":1}]}`) + + //设置单元格高度 + file.SetRowHeight("Sheet1", 1, 20) + + // 设置单元格大小 + file.SetColWidth("Sheet1", "A", "A", 30) + file.SetColWidth("Sheet1", "B", "B", 18) + file.SetColWidth("Sheet1", "C", "C", 18) + file.SetColWidth("Sheet1", "D", "D", 18) + file.SetColWidth("Sheet1", "E", "E", 18) + file.SetColWidth("Sheet1", "F", "F", 18) + + var endRow string + switch nEndCount { + case 1: + endRow = fmt.Sprintf("E"+"%d", nExcelStartRow+2) + case 2: + endRow = fmt.Sprintf("F"+"%d", nExcelStartRow+2) + default: + endRow = fmt.Sprintf("D"+"%d", nExcelStartRow+2) + } + // 应用样式到整个表格 + _ = file.SetCellStyle("Sheet1", "A1", endRow, style) + + fmt.Println("save fileName:", config.ExportConfig.Path+fileName) + if err := file.SaveAs(config.ExportConfig.Path + fileName); err != nil { + fmt.Println(err) + } + + return url + fileName, nil +} diff --git a/app/admin/router/decision.go b/app/admin/router/decision.go index 2c9ce8d..8bc2594 100644 --- a/app/admin/router/decision.go +++ b/app/admin/router/decision.go @@ -10,5 +10,6 @@ import ( func registerErpDecisionRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { r := v1.Group("/decision").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) - r.POST("report", decision.ErpDecisionReport) // 进销存报表 + r.POST("report", decision.ErpDecisionReport) // 进销存报表 + r.POST("store_sales_report", decision.ErpStoreSalesData) // 进销存报表 }