1、零售明细导出excel优化,去掉商品分类合并项;

2、采购订单反审核后库存状态改为9;
3、新增经营日报表接口;
This commit is contained in:
chenlin 2024-11-08 18:58:41 +08:00
parent 6172ceaa66
commit ae5b1a80eb
10 changed files with 925 additions and 64 deletions

View File

@ -590,3 +590,36 @@ func ErpOrderShowAllData(c *gin.Context) {
app.OK(c, nil, "")
return
}
// ErpOrderDailyReport 经营日报表
// @Summary 经营日报表
// @Tags 零售报表
// @Produce json
// @Accept json
// @Param request body models.ErpOrderDailyReportReq true "经营日报表模型"
// @Success 200 {object} models.ErpOrderDailyReportResp
// @Router /api/v1/erp_order/daily_report [post]
func ErpOrderDailyReport(c *gin.Context) {
var req = new(model.ErpOrderDailyReportReq)
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.QueryOrderDailyReport(req, c)
if err != nil {
logger.Error("QueryOrderDailyReport err:", logger.Field("err", err))
app.Error(c, http.StatusInternalServerError, err, "查询失败:"+err.Error())
return
}
app.OK(c, resp, "")
return
}

View File

@ -326,3 +326,24 @@ func GetCategoryLevelsFromMap(categoryID uint32, categoryMap map[uint32]Category
return levels
}
// GetAllFirstCategories 查询所有1级分类
func GetAllFirstCategories() ([]Category, error) {
var categories []Category
err := orm.Eloquent.Model(&Category{}).Where("pid = 0 and display = 1").Find(&categories).Error
if err != nil {
return nil, err
}
return categories, nil
}
// getCategoryNameByID 获取分类名称
func getCategoryNameByCategoryID(categoryID uint32) string {
// 假设有一个GetCategoryByID函数返回分类信息
category, err := GetCategoryById(categoryID)
if err != nil {
return "未知分类"
}
return category.Name
}

View File

@ -36,6 +36,7 @@ const (
SystemOut = 5 // 系统出库
CheckOut = 6 // 盘点出库
OnSale = 7 // 销售锁定中
PurchaseCancel = 8 // 采购订单反审核
)
// ErpStock 库存列表

View File

@ -3781,70 +3781,54 @@ func retailDetailExport(list []ErpOrder, sumData RetailDetailTotalData, c *gin.C
flag10, _ := checkRoleMenu(c, DetailStorePerMenu)
nEndCount := 0
title := []interface{}{"订单编号", "订单类型", "用户ID", "客户手机号", "日期", "审核时间", "店铺", "银联流水号", "销售员1", "销售员2", "商品分类", "", "", "商品名称",
"供应商", "是否串码", "商品串码", "是否赠送", "销售数量", "指导零售价", "零售价", "零售优惠", "会员优惠", "实际零售价/退货价"}
title2 := []interface{}{"订单编号", "订单类型", "用户ID", "客户手机号", "日期", "审核时间", "店铺", "银联流水号", "销售员1", "销售员2", "一级分类", "二级分类", "三级分类", "商品名称",
title := []interface{}{"订单编号", "订单类型", "用户ID", "客户手机号", "日期", "审核时间", "店铺", "银联流水号", "销售员1", "销售员2", "一级分类", "二级分类", "三级分类", "商品名称",
"供应商", "是否串码", "商品串码", "是否赠送", "销售数量", "指导零售价", "零售价", "零售优惠", "会员优惠", "实际零售价/退货价"}
if flag1 { // 采购单价
title = append(title, "采购单价")
title2 = append(title2, "采购单价")
nEndCount += 1
}
if flag2 { // 员工成本价
title = append(title, "员工成本价")
title2 = append(title2, "员工成本价")
nEndCount += 1
}
if flag3 { // 销售毛利
title = append(title, "销售毛利")
title2 = append(title2, "销售毛利")
nEndCount += 1
}
if flag4 { // 员工毛利
title = append(title, "员工毛利")
title2 = append(title2, "员工毛利")
nEndCount += 1
}
title = append(title, "订单总指导零售价", "订单总优惠", "订单实收", "【扫码付", "现金收款", "pos机收款",
"商场积分抵扣", "其他付款方式】")
title2 = append(title2, "订单总指导零售价", "订单总优惠", "订单实收", "【扫码付", "现金收款", "pos机收款",
"商场积分抵扣", "其他付款方式】")
if flag5 { // 订单总销售毛利
title = append(title, "订单总销售毛利")
title2 = append(title2, "订单总销售毛利")
nEndCount += 1
}
if flag6 { // 订单总员工毛利
title = append(title, "订单总员工毛利")
title2 = append(title2, "订单总员工毛利")
nEndCount += 1
}
if flag7 { // 销售毛利提成
title = append(title, "销售毛利提成")
title2 = append(title2, "销售毛利提成")
nEndCount += 1
}
if flag8 { // 员工毛利提成
title = append(title, "员工毛利提成")
title2 = append(title2, "员工毛利提成")
nEndCount += 1
}
if flag9 { // 销售员提成
title = append(title, "销售员提成")
title2 = append(title2, "销售员提成")
nEndCount += 1
}
if flag10 { // 门店提成
title = append(title, "门店提成")
title2 = append(title2, "门店提成")
nEndCount += 1
}
title = append(title, "备注")
title2 = append(title2, "备注")
for i, _ := range title {
cell, _ := excelize.CoordinatesToCellName(1+i, 1)
@ -3854,14 +3838,6 @@ func retailDetailExport(list []ErpOrder, sumData RetailDetailTotalData, c *gin.C
}
}
for i, _ := range title2 {
cell, _ := excelize.CoordinatesToCellName(1+i, 2)
err := file.SetCellValue(fSheet, cell, title2[i])
if err != nil {
logger.Error("file set value err:", logger.Field("err", err))
}
}
categoryMap, err := GetAllCategories()
if err != nil {
// 处理错误
@ -4246,7 +4222,7 @@ func retailDetailExport(list []ErpOrder, sumData RetailDetailTotalData, c *gin.C
}
for j, _ := range row {
cell, _ := excelize.CoordinatesToCellName(1+j, nExcelStartRow+3)
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))
@ -4434,7 +4410,7 @@ func retailDetailExport(list []ErpOrder, sumData RetailDetailTotalData, c *gin.C
end = append(end, "")
for i, _ := range end {
cell, _ := excelize.CoordinatesToCellName(1+i, nExcelStartRow+3)
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))
@ -4451,27 +4427,27 @@ func retailDetailExport(list []ErpOrder, sumData RetailDetailTotalData, c *gin.C
var endRow string
switch nEndCount {
case 1:
endRow = fmt.Sprintf("AH"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AH"+"%d", nExcelStartRow+2)
case 2:
endRow = fmt.Sprintf("AI"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AI"+"%d", nExcelStartRow+2)
case 3:
endRow = fmt.Sprintf("AJ"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AJ"+"%d", nExcelStartRow+2)
case 4:
endRow = fmt.Sprintf("AK"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AK"+"%d", nExcelStartRow+2)
case 5:
endRow = fmt.Sprintf("AL"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AL"+"%d", nExcelStartRow+2)
case 6:
endRow = fmt.Sprintf("AM"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AM"+"%d", nExcelStartRow+2)
case 7:
endRow = fmt.Sprintf("AN"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AN"+"%d", nExcelStartRow+2)
case 8:
endRow = fmt.Sprintf("AO"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AO"+"%d", nExcelStartRow+2)
case 9:
endRow = fmt.Sprintf("AP"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AP"+"%d", nExcelStartRow+2)
case 10:
endRow = fmt.Sprintf("AQ"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AQ"+"%d", nExcelStartRow+2)
default:
endRow = fmt.Sprintf("AG"+"%d", nExcelStartRow+3)
endRow = fmt.Sprintf("AG"+"%d", nExcelStartRow+2)
}
//endRow := fmt.Sprintf("AL%d", nExcelStartRow+2)
// 应用样式到整个表格
@ -4486,34 +4462,14 @@ func retailDetailExport(list []ErpOrder, sumData RetailDetailTotalData, c *gin.C
{"type":"bottom","color":"000000","style":1}]}`)
//需要自动换行的列
endRow2 := fmt.Sprintf("AI%d", nExcelStartRow+3)
endRow3 := fmt.Sprintf("AJ%d", nExcelStartRow+3)
endRow4 := fmt.Sprintf("AK%d", nExcelStartRow+3)
endRow2 := fmt.Sprintf("AI%d", nExcelStartRow+2)
endRow3 := fmt.Sprintf("AJ%d", nExcelStartRow+2)
endRow4 := fmt.Sprintf("AK%d", nExcelStartRow+2)
_ = file.SetCellStyle("Sheet1", "A1", "AN1", style1)
_ = file.SetCellStyle("Sheet1", "AL2", endRow2, style1)
_ = file.SetCellStyle("Sheet1", "AJ2", endRow3, style1)
_ = file.SetCellStyle("Sheet1", "AK2", endRow4, style1)
// 合并单元格
// 定义要排除的列
excludeCols := map[string]bool{"K": true, "L": true, "M": true}
// 合并 J1-L1
_ = file.MergeCell("Sheet1", "K1", "M1")
// 根据 nEndCount 动态合并列
for i := 0; i < len(title); i++ {
// 获取当前列名
currentCol := getExcelColumnName(i)
// 跳过 J、K、L 列(已通过 J1-L1 合并处理)
if excludeCols[currentCol] {
continue
}
// 合并当前列的第 1 行和第 2 行
startCell := fmt.Sprintf("%s1", currentCol)
endCell := fmt.Sprintf("%s2", currentCol)
_ = file.MergeCell("Sheet1", startCell, endCell)
}
// 设置单元格大小
file.SetColWidth("Sheet1", "A", "A", 15)
file.SetColWidth("Sheet1", "D", "D", 15)
@ -5587,11 +5543,26 @@ func QueryReceiptData(req *ErpOrderDeleteReq, c *gin.Context) (*ErpOrderReceiptD
resp.IsShowImei = 1
}
var categoryList []uint32
if sysUser.CooperativeBusinessId == 1 { // 迪为特殊处理
// 获取任天堂游戏的子分类
categoryList, _ = GetSubcategoryIds(4)
}
var totalCouponAmount float64
commodityMap := make(map[string]TableData, 0)
for i, item := range order.Commodities {
var tableData TableData
if Contains(categoryList, item.ErpCategoryId) {
category, err := GetCategoryById(4)
if err != nil {
tableData.Name = "任天堂游戏"
} else {
tableData.Name = category.Name
}
} else {
tableData.Name = item.ErpCommodityName
}
tableData.SL = uint32(item.Count)
tableData.DJ = item.RetailPrice
tableData.JE = float64(item.Count) * item.RetailPrice
@ -6507,3 +6478,419 @@ func updatePayWayData(gdb *gorm.DB, orderId uint32, req *ErpOrderCreateReq) erro
return nil
}
// ErpOrderDailyReportReq 经营日报表入参
type ErpOrderDailyReportReq struct {
StartTime string `json:"start_time"` // 开始时间
EndTime string `json:"end_time"` // 结束时间
PageIndex int `json:"pageIndex"` // 页码
PageSize int `json:"pageSize"` // 页面条数
}
// ErpOrderDailyReportResp 经营日报表出参
type ErpOrderDailyReportResp struct {
Total int `json:"total"` // 总条数(总订单数)
PageIndex int `json:"pageIndex"` // 页码
PageSize int `json:"pageSize"` // 每页展示条数
TotalAmount float64 `json:"total_amount"` // 合计总金额
TotalSalesProfit float64 `json:"total_sales_profit"` // 合计总毛利(销售毛利;店员看员工毛利)
List []DailyReport `json:"list"` // 零售明细
}
// DailyReport 经营日报表
type DailyReport struct {
StoreId uint32 `json:"store_id"` // 门店ID
StoreName string `json:"store_name"` // 门店名称
StoreAmount float64 `json:"store_amount"` // 门店销售金额
StoreSalesProfit float64 `json:"store_sales_profit"` // 门店销售毛利(销售毛利;店员看员工毛利)
CategorySales []CategorySalesData `json:"category_sales"` // 各分类的销售数据
}
// CategorySalesData 门店分类销售数据
type CategorySalesData struct {
CategoryID uint32 `json:"category_id"` // 一级分类ID
CategoryName string `json:"category_name"` // 一级分类名称
SaleTitle string `json:"sale_title"` // 金额标题 (例如: "主机金额")
TotalSaleAmount float64 `json:"total_sale_amount"` // 销售金额
CostTitle string `json:"cost_title"` // 成本标题 (例如: "主机成本")
TotalCostAmount float64 `json:"total_cost_amount"` // 销售成本
ProfitTitle string `json:"profit_title"` // 毛利标题 (例如: "主机毛利")
TotalProfitAmount float64 `json:"total_profit_amount"` // 销售利润
}
// QueryOrderDailyReport 生成经营日报表
func QueryOrderDailyReport(req *ErpOrderDailyReportReq, c *gin.Context) (*ErpOrderDailyReportResp, error) {
page := req.PageIndex - 1
if page < 0 {
page = 0
}
if req.PageSize == 0 {
req.PageSize = 10
}
resp := &ErpOrderDailyReportResp{
PageIndex: page + 1,
PageSize: req.PageSize,
}
// 查询用户所属门店
var storeList []uint32
sysUser, err := GetSysUserByCtx(c)
if err != nil {
return nil, err
}
if !(tools.GetRoleName(c) == "admin" || tools.GetRoleName(c) == "系统管理员") {
// 返回sysUser未过期的门店id列表
storeList = GetValidStoreIDs(sysUser.StoreData)
} else {
storeList, err = GetAllStoreIdsByCooperativeBusinessId(sysUser.CooperativeBusinessId)
if err != nil {
return nil, errors.New("用户未绑定门店")
}
}
if len(storeList) == 0 {
return nil, errors.New("用户未绑定门店")
}
storeMap, err := GetAllStoreData()
if err != nil {
return nil, err
}
// 查询所有门店和分类的零售信息
var sumData []RetailDetailSumData
sumData, err = getSumDataByStoreAndCategory(req)
if err != nil {
return nil, err
}
var nTotalAmount, nTotalSalesProfit float64
var storeDataList []DailyReport
for _, store := range storeList {
var categoryDataList []CategorySalesData
var nStoreAmount, nStoreSalesProfit float64
// 判断门店名称中是否包含"哈曼"
displayCategories := getDisplayCategories(storeMap[store].Name)
for _, item := range sumData {
nTotalAmount += item.Amount
nTotalSalesProfit += item.SalesProfit
// 仅当门店ID匹配且分类ID在展示分类中时才添加分类数据
if item.StoreId == store && containsCategory(displayCategories, item.ErpCategoryId) {
nStoreAmount += item.Amount
nStoreSalesProfit += item.TotalSalesProfit
// 处理单个分类数据
categoryData := processCategoryData(item)
categoryDataList = append(categoryDataList, categoryData)
}
}
// 如果分类数据不足4个补充缺失的分类金额为0
categoryDataList = appendMissingCategories(categoryDataList, displayCategories)
// 排序分类数据列表
sort.Slice(categoryDataList, func(i, j int) bool {
return categoryDataList[i].CategoryID < categoryDataList[j].CategoryID
})
// 构建门店数据
storeData := DailyReport{
StoreId: store,
StoreName: storeMap[store].Name,
StoreAmount: tools.RoundToTwoDecimalPlaces(nStoreAmount),
StoreSalesProfit: tools.RoundToTwoDecimalPlaces(nStoreSalesProfit),
CategorySales: categoryDataList,
}
storeDataList = append(storeDataList, storeData)
}
sort.Slice(storeDataList, func(i, j int) bool {
return storeDataList[i].StoreAmount > storeDataList[j].StoreAmount
})
// 计算分页所需的切片索引
startIndex := page * req.PageSize
endIndex := (page + 1) * req.PageSize
if endIndex > len(storeDataList) {
endIndex = len(storeDataList)
}
resp.List = storeDataList[startIndex:endIndex]
resp.Total = len(storeDataList)
resp.PageIndex = req.PageIndex
resp.PageSize = req.PageSize
resp.TotalAmount = tools.RoundToTwoDecimalPlaces(nTotalAmount)
resp.TotalSalesProfit = tools.RoundToTwoDecimalPlaces(nTotalSalesProfit)
return resp, nil
}
// 获取分类展示列表
func getDisplayCategories(storeName string) []uint32 {
if strings.Contains(storeName, "哈曼") {
return []uint32{27, 34} // 如果包含"哈曼"只显示分类27和34
}
return []uint32{1, 4, 7, 13} // 否则显示分类1、4、7、13
}
// 处理单个分类数据
func processCategoryData(item RetailDetailSumData) CategorySalesData {
var categoryName string
switch item.ErpCategoryId {
case 1:
categoryName = "主机"
case 4:
categoryName = "游戏"
case 7:
categoryName = "配件"
case 13:
categoryName = "周边"
default:
categoryName = item.ErpCategoryName
}
return CategorySalesData{
CategoryID: item.ErpCategoryId,
CategoryName: item.ErpCategoryName,
SaleTitle: categoryName + "销售金额",
TotalSaleAmount: tools.RoundToTwoDecimalPlaces(item.Amount),
CostTitle: categoryName + "销售成本",
TotalCostAmount: tools.RoundToTwoDecimalPlaces(item.WholesalePrice),
ProfitTitle: categoryName + "销售利润",
TotalProfitAmount: tools.RoundToTwoDecimalPlaces(item.SalesProfit),
}
}
// 补充缺失的分类数据
func appendMissingCategories(categoryDataList []CategorySalesData, displayCategories []uint32) []CategorySalesData {
for _, categoryId := range displayCategories {
if !containsCategorySalesData(categoryDataList, categoryId) {
var categoryName string
switch categoryId {
case 1:
categoryName = "主机"
case 4:
categoryName = "游戏"
case 7:
categoryName = "配件"
case 13:
categoryName = "周边"
case 27:
categoryName = "哈曼卡顿"
case 34:
categoryName = "JBL"
}
categoryDataList = append(categoryDataList, CategorySalesData{
CategoryID: categoryId,
CategoryName: categoryName,
SaleTitle: categoryName + "销售金额",
TotalSaleAmount: 0, // 补充分类金额为0
CostTitle: categoryName + "销售成本",
TotalCostAmount: 0, // 补充分类成本为0
ProfitTitle: categoryName + "销售利润",
TotalProfitAmount: 0, // 补充分类利润为0
})
}
}
return categoryDataList
}
// RetailDetailSumData 零售明细相关金额汇总
type RetailDetailSumData struct {
StoreId uint32 `json:"store_id" gorm:"index"` // 门店id
StoreName string `json:"store_name"` // 门店名称
ErpCategoryId uint32 `json:"erp_category_id" gorm:"index"` // 分类id
ErpCategoryName string `json:"erp_category_name"` // 分类名称
Count int32 `json:"count"` // 销售数量
RetailPrice float64 `json:"retail_price"` // 指导零售价
SalePrice float64 `json:"sale_price"` // 零售价
SaleDiscount float64 `json:"sale_discount"` // 零售优惠
MemberDiscount float64 `json:"member_discount"` // 会员优惠
VmDiscount float64 `json:"vm_discount"` // 会员积分抵扣
CouponDiscount float64 `json:"coupon_discount"` // 优惠券抵扣
Amount float64 `json:"amount"` // 实际零售价
WholesalePrice float64 `json:"wholesale_price"` // 采购单价
StaffPrice float64 `json:"staff_price"` // 员工成本价
SalesProfit float64 `json:"sales_profit"` // 销售毛利
StaffProfit float64 `json:"staff_profit"` // 员工毛利
TotalRetailPrice float64 `json:"total_retail_price"` // 订单总指导零售价
TotalDiscount float64 `json:"total_discount"` // 订单总优惠:订单所有商品零售优惠+会员优惠+会员积分抵扣之和
TotalAmount float64 `json:"total_amount"` // 订单实收金额
TotalCashierData
TotalSalesProfit float64 `json:"total_sales_profit"` // 订单总销售毛利
TotalStaffProfit float64 `json:"total_staff_profit"` // 订单总员工毛利
TotalPerData
StorePer float64 `json:"store_per"` // 门店提成
}
// 查询零售和退货订单的汇总并直接做减法,增加时间筛选条件
func getSumDataByStoreAndCategory(req *ErpOrderDailyReportReq) ([]RetailDetailSumData, error) {
var result []RetailDetailSumData
// 解析开始时间
startTimeCondition := ""
if req.StartTime != "" {
parse, err := time.Parse(QueryTimeFormat, req.StartTime)
if err != nil {
logger.Errorf("开始时间解析错误: %v", err)
return result, err
}
startTimeCondition = fmt.Sprintf("AND eo.audit_time > '%s'", parse.Format(QueryTimeFormat))
}
// 解析结束时间
endTimeCondition := ""
if req.EndTime != "" {
parse, err := time.Parse(QueryTimeFormat, req.EndTime)
if err != nil {
logger.Errorf("结束时间解析错误: %v", err)
return result, err
}
endTimeCondition = fmt.Sprintf("AND eo.audit_time < '%s'", parse.Format(QueryTimeFormat))
}
// 获取所有1级分类
firstCategories, err := GetAllFirstCategories()
if err != nil {
logger.Error("获取一级分类错误", logger.Field("err", err))
return result, err
}
// 获取所有一级分类对应的子分类IDs
categorySubIds := make(map[uint32][]uint32)
for _, category := range firstCategories {
subCategoryIDs, err := GetSubcategoryIds(category.ID)
if err != nil {
logger.Error("获取子分类ID错误", logger.Field("err", err), logger.Field("categoryID", category.ID))
return result, err
}
categorySubIds[category.ID] = subCategoryIDs
}
err = orm.Eloquent.Raw(fmt.Sprintf(`
SELECT
retail.store_id,
retail.store_name,
retail.erp_category_id,
retail.erp_category_name,
COALESCE(retail.count, 0) - COALESCE(rejected.count, 0) AS count,
COALESCE(retail.retail_price, 0) - COALESCE(rejected.retail_price, 0) AS retail_price,
COALESCE(retail.sale_price, 0) - COALESCE(rejected.sale_price, 0) AS sale_price,
COALESCE(retail.sale_discount, 0) - COALESCE(rejected.sale_discount, 0) AS sale_discount,
COALESCE(retail.member_discount, 0) - COALESCE(rejected.member_discount, 0) AS member_discount,
COALESCE(retail.amount, 0) - COALESCE(rejected.amount, 0) AS amount,
COALESCE(retail.wholesale_price, 0) - COALESCE(rejected.wholesale_price, 0) AS wholesale_price,
COALESCE(retail.staff_price, 0) - COALESCE(rejected.staff_price, 0) AS staff_price,
COALESCE(retail.sales_profit, 0) - COALESCE(rejected.sales_profit, 0) AS sales_profit,
COALESCE(retail.staff_profit, 0) - COALESCE(rejected.staff_profit, 0) AS staff_profit,
COALESCE(retail.total_amount, 0) - COALESCE(rejected.total_amount, 0) AS total_amount,
COALESCE(retail.total_sales_profit, 0) - COALESCE(rejected.total_sales_profit, 0) AS total_sales_profit,
COALESCE(retail.total_staff_profit, 0) - COALESCE(rejected.total_staff_profit, 0) AS total_staff_profit
FROM (
SELECT eo.store_id, eo.store_name, oc.erp_category_id, oc.erp_category_name,
SUM(oc.count) AS count,
SUM(oc.retail_price) AS retail_price,
SUM(oc.sale_price) AS sale_price,
SUM(oc.sale_discount) AS sale_discount,
SUM(oc.member_discount) AS member_discount,
SUM(oc.amount) AS amount,
SUM(oc.wholesale_price) AS wholesale_price,
(SUM(oc.wholesale_price) + SUM(oc.staff_cost_price)) AS staff_price,
(SUM(oc.amount) - SUM(oc.wholesale_price)) AS sales_profit,
(SUM(oc.amount) - SUM(oc.wholesale_price) - SUM(oc.staff_cost_price)) AS staff_profit,
SUM(eo.total_amount) as total_amount,
SUM(eo.total_sales_profit) as total_sales_profit,
SUM(eo.total_staff_profit) as total_staff_profit
FROM erp_order eo
JOIN erp_order_commodity oc ON eo.id = oc.erp_order_id
WHERE eo.retail_type = ? %s %s
GROUP BY eo.store_id, eo.store_name, oc.erp_category_id, oc.erp_category_name
) AS retail
LEFT JOIN (
SELECT eo.store_id, eo.store_name, oc.erp_category_id, oc.erp_category_name,
SUM(oc.count) AS count,
SUM(oc.retail_price) AS retail_price,
SUM(oc.sale_price) AS sale_price,
SUM(oc.sale_discount) AS sale_discount,
SUM(oc.member_discount) AS member_discount,
SUM(oc.rejected_amount) AS amount,
SUM(oc.wholesale_price) AS wholesale_price,
(SUM(oc.wholesale_price) + SUM(oc.staff_cost_price)) AS staff_price,
SUM(oc.sales_profit) AS sales_profit,
SUM(oc.staff_profit) AS staff_profit,
SUM(eo.total_amount) as total_amount,
SUM(eo.total_sales_profit) as total_sales_profit,
SUM(eo.total_staff_profit) as total_staff_profit
FROM erp_order eo
JOIN erp_order_commodity oc ON eo.id = oc.erp_order_id
WHERE eo.retail_type = ? %s %s
GROUP BY eo.store_id, eo.store_name, oc.erp_category_id, oc.erp_category_name
) AS rejected ON retail.store_id = rejected.store_id AND retail.erp_category_id = rejected.erp_category_id
`, startTimeCondition, endTimeCondition, startTimeCondition, endTimeCondition), RetailTypeSale, RetailTypeRejected).Scan(&result).Error
if err != nil {
logger.Error("query data with subtraction err:", logger.Field("err", err))
return result, err
}
// 合并子分类数据根据门店ID和父分类合并并累加金额
mergedResult := make(map[string]RetailDetailSumData)
for i := range result {
for parentID, subCategories := range categorySubIds {
if containsCategory(subCategories, result[i].ErpCategoryId) {
key := fmt.Sprintf("%d-%d", result[i].StoreId, parentID)
if existing, exists := mergedResult[key]; exists {
existing.Count += result[i].Count
existing.RetailPrice += result[i].RetailPrice
existing.SalePrice += result[i].SalePrice
existing.SaleDiscount += result[i].SaleDiscount
existing.MemberDiscount += result[i].MemberDiscount
existing.Amount += result[i].Amount
existing.WholesalePrice += result[i].WholesalePrice
existing.StaffPrice += result[i].StaffPrice
existing.SalesProfit += result[i].SalesProfit
existing.StaffProfit += result[i].StaffProfit
mergedResult[key] = existing
} else {
result[i].ErpCategoryId = parentID
//result[i].ErpCategoryName = getCategoryNameByCategoryID(parentID)
mergedResult[key] = result[i]
}
break
}
}
}
// 转换合并结果为切片
finalResult := make([]RetailDetailSumData, 0, len(mergedResult))
for _, v := range mergedResult {
finalResult = append(finalResult, v)
}
return finalResult, nil
}
// containsCategory 检查分类ID是否在子分类IDs列表中
func containsCategory(subCategoryIds []uint32, categoryId uint32) bool {
for _, id := range subCategoryIds {
if id == categoryId {
return true
}
}
return false
}
// 辅助函数用于检查分类是否已经存在于categoryDataList中
func containsCategorySalesData(categoryDataList []CategorySalesData, categoryId uint32) bool {
for _, category := range categoryDataList {
if category.CategoryID == categoryId {
return true
}
}
return false
}

View File

@ -1463,7 +1463,7 @@ func InventoryErpPurchaseUpdateRejectStock(gdb *gorm.DB, list []ErpPurchaseInven
err = gdb.Table("erp_stock_commodity").Where("imei = ?", list[i].IMEI).
Updates(&map[string]interface{}{
"state": PurchaseReturn,
"state": PurchaseCancel,
"stock_sn": list[i].SerialNumber,
"updated_at": time.Now(),
}).Error // 状态更新为采购退货
@ -1526,7 +1526,7 @@ func InventoryErpPurchaseUpdateRejectStock(gdb *gorm.DB, list []ErpPurchaseInven
return fmt.Errorf("商品[%s]采购退货数量超出实际库存数量,请先零售退货", list[i].ErpCommodityName)
}
err = gdb.Table("erp_stock_commodity").Where("id = ?", currentID).Updates(&map[string]interface{}{
"state": PurchaseReturn,
"state": PurchaseCancel,
"stock_sn": list[i].SerialNumber,
"updated_at": time.Now(),
}).Error // 状态更新为采购退货

View File

@ -304,3 +304,35 @@ func GetStoreIdsByCooperativeBusinessId(id uint32) ([]uint32, error) {
return ids, nil
}
func GetAllStoreIdsByCooperativeBusinessId(id uint32) ([]uint32, error) {
ids := make([]uint32, 0)
var stores []Store
err := orm.Eloquent.Table("store").Where("cooperative_business_id = ? and is_online = 1", id).Find(&stores).Error
if err != nil {
logger.Errorf("err:", logger.Field("err", err))
return ids, err
}
for i, _ := range stores {
ids = append(ids, stores[i].ID)
}
return ids, nil
}
func GetAllStoreData() (map[uint32]Store, error) {
storeMap := make(map[uint32]Store)
var stores []Store
err := orm.Eloquent.Table("store").Find(&stores).Error
if err != nil {
logger.Errorf("err:", logger.Field("err", err))
return storeMap, err
}
for _, store := range stores {
storeMap[store.ID] = store
}
return storeMap, nil
}

View File

@ -23,4 +23,5 @@ func registerErpOrderManageRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJW
r.POST("retail_detail", erpordermanage.ErpOrderRetailDetail) // 查询零售明细
r.POST("receipt_data", erpordermanage.ErpOrderReceiptData) // 查询小票数据
r.POST("show_all_data", erpordermanage.ErpOrderShowAllData) // 展示所有订单
r.POST("daily_report", erpordermanage.ErpOrderDailyReport) // 经营日报表
}

View File

@ -1844,6 +1844,39 @@ const docTemplate = `{
}
}
},
"/api/v1/erp_order/daily_report": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"零售订单"
],
"summary": "经营日报表",
"parameters": [
{
"description": "经营日报表模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.ErpOrderDailyReportReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.ErpOrderDailyReportResp"
}
}
}
}
},
"/api/v1/erp_order/delete": {
"post": {
"consumes": [
@ -7000,6 +7033,43 @@ const docTemplate = `{
}
}
},
"models.CategorySalesData": {
"type": "object",
"properties": {
"category_id": {
"description": "一级分类ID",
"type": "integer"
},
"category_name": {
"description": "一级分类名称",
"type": "string"
},
"cost_title": {
"description": "成本标题 (例如: \"主机成本\")",
"type": "string"
},
"profit_title": {
"description": "毛利标题 (例如: \"主机毛利\")",
"type": "string"
},
"sale_title": {
"description": "金额标题 (例如: \"主机金额\")",
"type": "string"
},
"total_cost_amount": {
"description": "销售成本",
"type": "number"
},
"total_profit_amount": {
"description": "销售利润",
"type": "number"
},
"total_sale_amount": {
"description": "销售金额",
"type": "number"
}
}
},
"models.CommodityCreateRequest": {
"type": "object",
"required": [
@ -7705,6 +7775,26 @@ const docTemplate = `{
}
}
},
"models.DailyReport": {
"type": "object",
"properties": {
"category_sales": {
"description": "各分类的销售数据",
"type": "array",
"items": {
"$ref": "#/definitions/models.CategorySalesData"
}
},
"store_id": {
"description": "门店ID",
"type": "integer"
},
"store_name": {
"description": "门店名称",
"type": "string"
}
}
},
"models.DecisionReportData": {
"type": "object",
"properties": {
@ -9561,6 +9651,59 @@ const docTemplate = `{
}
}
},
"models.ErpOrderDailyReportReq": {
"type": "object",
"properties": {
"end_time": {
"description": "结束时间",
"type": "string"
},
"pageIndex": {
"description": "页码",
"type": "integer"
},
"pageSize": {
"description": "页面条数",
"type": "integer"
},
"start_time": {
"description": "开始时间",
"type": "string"
}
}
},
"models.ErpOrderDailyReportResp": {
"type": "object",
"properties": {
"list": {
"description": "零售明细",
"type": "array",
"items": {
"$ref": "#/definitions/models.DailyReport"
}
},
"pageIndex": {
"description": "页码",
"type": "integer"
},
"pageSize": {
"description": "每页展示条数",
"type": "integer"
},
"total": {
"description": "总条数(总订单数)",
"type": "integer"
},
"total_amount": {
"description": "合计总金额",
"type": "number"
},
"total_sales_profit": {
"description": "合计总毛利(销售毛利;店员看员工毛利)",
"type": "number"
}
}
},
"models.ErpOrderDeleteReq": {
"type": "object",
"required": [

View File

@ -1833,6 +1833,39 @@
}
}
},
"/api/v1/erp_order/daily_report": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"零售订单"
],
"summary": "经营日报表",
"parameters": [
{
"description": "经营日报表模型",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.ErpOrderDailyReportReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.ErpOrderDailyReportResp"
}
}
}
}
},
"/api/v1/erp_order/delete": {
"post": {
"consumes": [
@ -6989,6 +7022,43 @@
}
}
},
"models.CategorySalesData": {
"type": "object",
"properties": {
"category_id": {
"description": "一级分类ID",
"type": "integer"
},
"category_name": {
"description": "一级分类名称",
"type": "string"
},
"cost_title": {
"description": "成本标题 (例如: \"主机成本\")",
"type": "string"
},
"profit_title": {
"description": "毛利标题 (例如: \"主机毛利\")",
"type": "string"
},
"sale_title": {
"description": "金额标题 (例如: \"主机金额\")",
"type": "string"
},
"total_cost_amount": {
"description": "销售成本",
"type": "number"
},
"total_profit_amount": {
"description": "销售利润",
"type": "number"
},
"total_sale_amount": {
"description": "销售金额",
"type": "number"
}
}
},
"models.CommodityCreateRequest": {
"type": "object",
"required": [
@ -7694,6 +7764,26 @@
}
}
},
"models.DailyReport": {
"type": "object",
"properties": {
"category_sales": {
"description": "各分类的销售数据",
"type": "array",
"items": {
"$ref": "#/definitions/models.CategorySalesData"
}
},
"store_id": {
"description": "门店ID",
"type": "integer"
},
"store_name": {
"description": "门店名称",
"type": "string"
}
}
},
"models.DecisionReportData": {
"type": "object",
"properties": {
@ -9550,6 +9640,59 @@
}
}
},
"models.ErpOrderDailyReportReq": {
"type": "object",
"properties": {
"end_time": {
"description": "结束时间",
"type": "string"
},
"pageIndex": {
"description": "页码",
"type": "integer"
},
"pageSize": {
"description": "页面条数",
"type": "integer"
},
"start_time": {
"description": "开始时间",
"type": "string"
}
}
},
"models.ErpOrderDailyReportResp": {
"type": "object",
"properties": {
"list": {
"description": "零售明细",
"type": "array",
"items": {
"$ref": "#/definitions/models.DailyReport"
}
},
"pageIndex": {
"description": "页码",
"type": "integer"
},
"pageSize": {
"description": "每页展示条数",
"type": "integer"
},
"total": {
"description": "总条数(总订单数)",
"type": "integer"
},
"total_amount": {
"description": "合计总金额",
"type": "number"
},
"total_sales_profit": {
"description": "合计总毛利(销售毛利;店员看员工毛利)",
"type": "number"
}
}
},
"models.ErpOrderDeleteReq": {
"type": "object",
"required": [

View File

@ -517,6 +517,33 @@ definitions:
description: 更新时间
type: string
type: object
models.CategorySalesData:
properties:
category_id:
description: 一级分类ID
type: integer
category_name:
description: 一级分类名称
type: string
cost_title:
description: '成本标题 (例如: "主机成本")'
type: string
profit_title:
description: '毛利标题 (例如: "主机毛利")'
type: string
sale_title:
description: '金额标题 (例如: "主机金额")'
type: string
total_cost_amount:
description: 销售成本
type: number
total_profit_amount:
description: 销售利润
type: number
total_sale_amount:
description: 销售金额
type: number
type: object
models.CommodityCreateRequest:
properties:
brokerage_1:
@ -1034,6 +1061,20 @@ definitions:
description: 备注
type: string
type: object
models.DailyReport:
properties:
category_sales:
description: 各分类的销售数据
items:
$ref: '#/definitions/models.CategorySalesData'
type: array
store_id:
description: 门店ID
type: integer
store_name:
description: 门店名称
type: string
type: object
models.DecisionReportData:
properties:
allot_in:
@ -2397,6 +2438,44 @@ definitions:
- store_name
- total_count
type: object
models.ErpOrderDailyReportReq:
properties:
end_time:
description: 结束时间
type: string
pageIndex:
description: 页码
type: integer
pageSize:
description: 页面条数
type: integer
start_time:
description: 开始时间
type: string
type: object
models.ErpOrderDailyReportResp:
properties:
list:
description: 零售明细
items:
$ref: '#/definitions/models.DailyReport'
type: array
pageIndex:
description: 页码
type: integer
pageSize:
description: 每页展示条数
type: integer
total:
description: 总条数(总订单数)
type: integer
total_amount:
description: 合计总金额
type: number
total_sales_profit:
description: 合计总毛利(销售毛利;店员看员工毛利)
type: number
type: object
models.ErpOrderDeleteReq:
properties:
bill_sn:
@ -9560,6 +9639,27 @@ paths:
summary: 新建零售订单
tags:
- 零售订单
/api/v1/erp_order/daily_report:
post:
consumes:
- application/json
parameters:
- description: 经营日报表模型
in: body
name: request
required: true
schema:
$ref: '#/definitions/models.ErpOrderDailyReportReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.ErpOrderDailyReportResp'
summary: 经营日报表
tags:
- 零售订单
/api/v1/erp_order/delete:
post:
consumes: