Files
zmkgC/third/excel/export.go
2025-07-07 20:11:59 +08:00

514 lines
16 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package excel
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/samber/lo"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"github.com/tiger1103/gfast/v3/internal/app/system/model"
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
"github.com/xuri/excelize/v2"
)
// 记录累计完成量和下一周计划量
type ExcelInfo struct {
// 累计完成量
Accumulated int
// 下一周计划量
NextWeek int
}
// 导出单个方阵
func ExportExcel(fangZhenID string, startAt, endAt string) ([]*model.WorkStatusProgressRes, map[string]ExcelInfo, error) {
ctx := context.Background()
type scheduler struct {
// 实体
WorkStatus *entity.WorkSchedule
// 总进度
FinishedPtr int
// 总量
Total int
}
type Schdule map[string]scheduler
Get := func(scheduleMap Schdule, workID string) (int, int) {
if scheduler, ok := scheduleMap[workID]; ok {
return scheduler.FinishedPtr, scheduler.Total
}
return 0, 0
}
// 获取指定方阵ID的所有计划
ScheduleData := []entity.WorkSchedule{}
if err := dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().FangzhenId, fangZhenID).
Scan(&ScheduleData); err != nil {
return nil, nil, err
}
// 存储指定 workID 的累计完成量
workMap := make(map[string]ExcelInfo)
// 计算下一周的日期
nextWeekStart := lo.Must(time.Parse("2006-01-02", startAt)).AddDate(0, 0, 7)
nextWeekEnd := lo.Must(time.Parse("2006-01-02", endAt)).AddDate(0, 0, 7)
scheduleMap := make(Schdule)
// 遍历 ScheduleData 将数据按照 WorkID 分组
lo.ForEach(ScheduleData, func(item entity.WorkSchedule, _ int) {
if work, ok := workMap[item.WorkId]; ok {
// 累加完成量
work.Accumulated += item.FinishedNum
workMap[item.WorkId] = work
} else {
workMap[item.WorkId] = ExcelInfo{
Accumulated: item.FinishedNum,
}
}
// 如果计划处于下一周,则添加到下一周计划量中
stime := lo.Must(time.Parse("2006-01-02", item.StartAt.Format("2006-01-02")))
if stime.After(nextWeekStart) && stime.Before(nextWeekEnd) {
var scheduleDetails []model.WorkScheduleDetail
if err := json.Unmarshal([]byte(item.Detail), &scheduleDetails); err != nil {
return
}
nextweekPlanNum := 0
for _, detail := range scheduleDetails {
// 如果 Date 大于开始时间并小于结束时间
if detail.Date <= endAt {
nextweekPlanNum += detail.PlanNum
}
}
work := workMap[item.WorkId]
work.NextWeek += nextweekPlanNum
workMap[item.WorkId] = work
}
// 如果开始时间大于当前时间,则跳过
startTime := item.StartAt.Format("Y-m-d")
if startTime > startAt {
return
}
// 如果不存在则直接添加
_, ok := scheduleMap[item.WorkId]
if !ok {
scheduleMap[item.WorkId] = scheduler{
WorkStatus: &item,
}
}
// 反序列化 item.Detail 字段
var scheduleDetails []model.WorkScheduleDetail
if err := json.Unmarshal([]byte(item.Detail), &scheduleDetails); err != nil {
return
}
lo.ForEach(scheduleDetails, func(detail model.WorkScheduleDetail, _ int) {
// 如果 Date 大于开始时间并小于结束时间
if detail.Date <= endAt {
scheduler := scheduleMap[item.WorkId]
scheduler.Total += detail.PlanNum
scheduler.FinishedPtr += detail.FinishedNum
scheduleMap[item.WorkId] = scheduler
}
})
})
// 获取指定方阵ID的所有项目
workStatusList := []entity.WorkStatus{}
if err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangZhenID).Scan(&workStatusList); err != nil {
return nil, nil, err
}
// 父列表
parentList := []entity.WorkStatus{}
// 子列表,使用 map 存储,键为父元素的 ID
childrenMap := make(map[int][]entity.WorkStatus)
// 遍历所有数据,将父级和子集分开
for i := 0; i < len(workStatusList); i++ {
item := workStatusList[i]
if item.Parent == 0 {
parentList = append(parentList, item)
} else {
childrenMap[item.Parent] = append(childrenMap[item.Parent], item)
}
}
index := 1
projectList := make([]*model.WorkStatusProgressRes, 0, len(parentList))
// 遍历父级,将子集添加到父级的字段中
for _, parent := range parentList {
projectItem := model.WorkStatusProgressRes{
ID: index,
WorkID: parent.WorkId,
Startat: parent.StartAt.String(),
Endat: parent.EndAt.String(),
Name: parent.WorkName,
Total: parent.Total,
Finished: parent.Finished,
Status: parent.Status,
WorkType: parent.Type,
Children: make([]model.WorkStatusProgressRes, 0, len(workStatusList)),
}
index++
// 子类的总量和完成量
var subTotal, subDone int
// 从 map 中获取子元素,将子元素添加到父元素的字段中
for _, child := range childrenMap[int(parent.Id)] {
// 如果计划数量等于实际数量且原状态非3则更新状态为2
if child.Total == child.Finished && child.Status != 3 {
// 并且 total,finished 不为 0
if child.Total != 0 && child.Finished != 0 {
child.Status = 2
}
}
// 根据 WorkID 获取计划总量和实际进度
finishedPtr, total := Get(scheduleMap, child.WorkId)
subTotal += child.Total
subDone += child.Finished
projectItem.Children = append(projectItem.Children, model.WorkStatusProgressRes{
ID: index,
WorkID: child.WorkId,
Startat: child.StartAt.String(),
Endat: child.EndAt.String(),
Name: child.WorkName,
Total: child.Total,
Finished: child.Finished,
IsPercent: child.IsPercent,
Status: child.Status,
WorkType: child.Type,
PlanProgress: &model.PlanProgress{
Finished: &finishedPtr,
Total: &total,
},
})
index++
}
// 修改父项目的总量和进度
projectItem.Total = subTotal
projectItem.Finished = subDone
projectList = append(projectList, &projectItem)
}
return projectList, workMap, nil
}
// 导出一个子项目
func AccumulateProject(subProjectID string, startAt, endAt string) ([]*model.WorkStatusProgressRes, map[string]ExcelInfo, error) {
fangzhenList := []entity.QianqiFangzhen{}
if err := dao.QianqiFangzhen.Ctx(context.Background()).
Where(dao.QianqiFangzhen.Columns().ProjectId, subProjectID).Scan(&fangzhenList); err != nil {
return nil, nil, err
}
cumulativeProjects := []*model.WorkStatusProgressRes{}
// 用于判断是否为第一次迭代
firstTime := true
excelMap := make(map[string]ExcelInfo)
// 遍历项目列表
for index := 0; index < len(fangzhenList); index++ {
// 获取当前项目
project := fangzhenList[index]
// 获取当前项目的子项目
childProjects, emap, err := ExportExcel(strconv.Itoa(project.Id), startAt, endAt)
if err != nil {
return nil, nil, err
}
excelMap = lo.Assign(emap, excelMap)
// 如果是第一次迭代,将子项目列表赋值给累积项目列表
if firstTime {
cumulativeProjects = childProjects
// 更新标志变量,表示已经不是第一次迭代
firstTime = false
continue
}
// 遍历子项目列表
for childIndex := 0; childIndex < len(childProjects); childIndex++ {
// 获取当前子项目和对应的累积项目
singleChild := childProjects[childIndex]
cumulativeChild := cumulativeProjects[childIndex]
// 更新累积项目的总数和完成数
cumulativeChild.Total += singleChild.Total
cumulativeChild.Finished += singleChild.Finished
// 如果当前子项目还有子项目,也进行同样的操作
if len(singleChild.Children) > 0 {
for subChildIndex := 0; subChildIndex < len(singleChild.Children); subChildIndex++ {
// 获取当前子项目的子项目和对应的累积项目
singleSubChild := singleChild.Children[subChildIndex]
cumulativeSubChild := cumulativeChild.Children[subChildIndex]
// 更新累积项目的总数和完成数
cumulativeSubChild.Total += singleSubChild.Total
cumulativeSubChild.Finished += singleSubChild.Finished
}
}
}
}
return cumulativeProjects, excelMap, nil
}
// 传递一个 []*model.WorkStatusProgressRes将数据写入到 excel 中
func WriteExcel(tableName string, list []*model.WorkStatusProgressRes, aiResult map[string]int) (*excelize.File, error) {
f, err := excelize.OpenFile("resource/template.xlsx")
if err != nil {
return nil, err
}
// 创建一个工作表
sheetName := "Sheet1"
headerPrefixes := []string{"一、", "二、", "三、", "四、", "五、", "六、", "七、", "八、", "九、", "十、", "十一、", "十二、"}
childPrefixes := []string{"1、", "2、", "3、", "4、", "5、", "6、", "7、", "8、", "9、", "10、", "11、", "12、"}
// 修改表头
f.SetCellValue(sheetName, "A1", tableName)
// 遍历 list
rowIndex := 3
lo.ForEach(list, func(item *model.WorkStatusProgressRes, index int) {
setExcelValues(f, sheetName, rowIndex, item, headerPrefixes[index], aiResult)
rowIndex++
// 遍历子集
lo.ForEach(item.Children, func(child model.WorkStatusProgressRes, childIndex int) {
setExcelValues(f, sheetName, rowIndex, &child, childPrefixes[childIndex], aiResult)
rowIndex++
})
})
return f, nil
}
func setExcelValues(f *excelize.File, sheetName string, i int, item *model.WorkStatusProgressRes, prefix string, aiResult map[string]int) {
// 单元格 A
f.SetCellValue(sheetName, fmt.Sprintf("A%d", i), prefix+item.Name) // 父名
f.SetCellValue(sheetName, fmt.Sprintf("B%d", i), item.Total) // 总量
f.SetCellValue(sheetName, fmt.Sprintf("C%d", i), item.Finished) // 完成
var percentComplete float64
if item.Total != 0 {
percentComplete = float64(item.Finished) / float64(item.Total) * 100
} else {
percentComplete = 0
}
f.SetCellValue(sheetName, fmt.Sprintf("D%d", i), fmt.Sprintf("%.f%%", percentComplete)) // 总量完成百分比
if item.PlanProgress != nil {
total := 0
if item.PlanProgress.Total != nil {
total = *item.PlanProgress.Total
}
f.SetCellValue(sheetName, fmt.Sprintf("E%d", i), total) // 计划量
// AI 填报数量 F
// 用 workID 作为 key,获取 aiResult 中的值,如果不存在则为 0
aiResultValue := aiResult[item.WorkID]
f.SetCellValue(sheetName, fmt.Sprintf("F%d", i), aiResultValue)
finished := 0
if item.PlanProgress.Finished != nil {
finished = *item.PlanProgress.Finished
}
f.SetCellValue(sheetName, fmt.Sprintf("G%d", i), finished) // 计划完成量
var plannedPercentComplete float64
if item.PlanProgress != nil && item.PlanProgress.Finished != nil && item.PlanProgress.Total != nil && *item.PlanProgress.Total != 0 {
plannedPercentComplete = float64(*item.PlanProgress.Finished) / float64(*item.PlanProgress.Total) * 100
} else {
plannedPercentComplete = 0
}
f.SetCellValue(sheetName, fmt.Sprintf("H%d", i), fmt.Sprintf("%.f%%", plannedPercentComplete)) // 计划完成百分比
}
}
// 根据方阵ID获取子项目名
func GetSubProjectName(fangZhenID string) (string, string) {
// select sb.project_name
// from sub_project as sb
// left join qianqi_fangzhen as qf on qf.project_id = sb.id
// where qf.id = 1959;
result, err := dao.SubProject.Ctx(context.Background()).As("sb").
Where("qf.id", fangZhenID).
InnerJoin("qianqi_fangzhen as qf", "qf.project_id = sb.id").
Fields("sb.project_name,qf.name").All()
if err != nil {
return "", ""
}
// 获取子项目名
subProjectName := result[0].GMap().Get("project_name").(string)
// 方阵名
fangZhenName := result[0].GMap().Get("name").(string)
return subProjectName, fangZhenName
}
// 根据子项目ID获取其主项目名和子项目名
func GetNameById(subProjectID string) (string, string) {
// select sp.project_name as projectName,sb.project_name as subProjectName
// from sys_project as sp
// inner join sub_project as sb on sb.project_id = sp.id
// where sb.id = 23;
result, err := dao.SubProject.Ctx(context.Background()).As("sb").
Where("sb.id", subProjectID).
InnerJoin("sys_project as sp", "sb.project_id = sp.id").
Fields("sp.project_name as projectName,sb.project_name as subProjectName").All()
if err != nil {
return "", ""
}
// 获取主项目名
projectName := result[0].GMap().Get("projectName").(string)
// 获取子项目名
subProjectName := result[0].GMap().Get("subProjectName").(string)
return projectName, subProjectName
}
func SetExcelValue(f *excelize.File, sheetName string, index int, item model.WorkStatusProgressRes, excelmap map[string]ExcelInfo, postionName string) {
// 设计数量 F10
total := item.Total
// 本周完成量 G10
finished := item.Finished
// 累计完成量和下周计划量
accumulated, nextWeek := 0, 0
if data, exists := excelmap[item.WorkID]; exists {
accumulated = data.Accumulated
nextWeek = data.NextWeek
}
// 累计完成百分比 单元格 I10
percentComplete := 0.0
if total != 0 {
percentComplete = float64(accumulated) / float64(total) * 100
}
// 名称
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "C", index), item.Name)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "C", index), fmt.Sprintf("%s%d", "C", index), lo.Must(f.NewStyle(Style)))
// 位置
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "D", index), postionName)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "D", index), fmt.Sprintf("%s%d", "D", index), lo.Must(f.NewStyle(Style)))
// 单位 样式
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "E", index), fmt.Sprintf("%s%d", "E", index), lo.Must(f.NewStyle(Style)))
// 设计数量
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "F", index), total)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "F", index), fmt.Sprintf("%s%d", "F", index), lo.Must(f.NewStyle(Style)))
// 本周
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "G", index), finished)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "G", index), fmt.Sprintf("%s%d", "G", index), lo.Must(f.NewStyle(Style)))
// 累计完成量
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "H", index), accumulated)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "H", index), fmt.Sprintf("%s%d", "H", index), lo.Must(f.NewStyle(Style)))
// 累计完成百分比
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "I", index), fmt.Sprintf("%.f%%", percentComplete))
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "I", index), fmt.Sprintf("%s%d", "I", index), lo.Must(f.NewStyle(Style)))
// 合并 jk 单元格
_ = f.MergeCell(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index))
f.SetCellStyle(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index), lo.Must(f.NewStyle(Style)))
// 水平居中 jk 单元格
f.SetCellStyle(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index), lo.Must(f.NewStyle(&excelize.Style{
Border: []excelize.Border{
{Type: "left", Color: "000000", Style: 1},
{Type: "top", Color: "000000", Style: 1},
{Type: "bottom", Color: "000000", Style: 1},
{Type: "right", Color: "000000", Style: 1},
},
Fill: excelize.Fill{},
Font: nil,
Alignment: &excelize.Alignment{
Horizontal: "center", // 水平居中
Indent: 0,
JustifyLastLine: false,
ReadingOrder: 0,
RelativeIndent: 0,
ShrinkToFit: false,
TextRotation: 0,
Vertical: "", // 垂直居中
WrapText: false,
},
})))
// 下周计划量
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "J", index), nextWeek)
// L
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "L", index), fmt.Sprintf("%s%d", "L", index), lo.Must(f.NewStyle(Style)))
}
func GetWeekNumbers(endTime string) (int, error) {
end, err := time.Parse(time.DateOnly, endTime)
if err != nil {
return 0, fmt.Errorf("解析结束时间错误: %w", err)
}
_, endWeek := end.ISOWeek()
return endWeek, nil
}
func CalculateTotalProgress(list []*model.WorkStatusProgressRes) float64 {
var totalFinished, total int
var calculate func(w model.WorkStatusProgressRes)
calculate = func(w model.WorkStatusProgressRes) {
totalFinished += w.Finished
total += w.Total
for _, child := range w.Children {
calculate(child)
}
}
for _, item := range list {
calculate(*item)
}
if total == 0 {
return 0
}
return (float64(totalFinished) / float64(total)) * 100
}