package handler import ( "bytes" "fmt" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/rovina/zsp-backend/internal/dto" "github.com/rovina/zsp-backend/internal/service" "github.com/xuri/excelize/v2" ) type ZSPContractHandler struct { service service.ZSPContractService } func NewZSPContractHandler(service service.ZSPContractService) *ZSPContractHandler { return &ZSPContractHandler{service: service} } // zspContracts.POST("/folders", zspContractHandler.CreateFolder) // zspContracts.GET("/folders", zspContractHandler.GetFolders) // zspContracts.GET("/folders/:id", zspContractHandler.GetFolder) // zspContracts.DELETE("/folders/:id", zspContractHandler.DeleteFolder) // zspContracts.POST("/files", zspContractHandler.UploadFile) // zspContracts.GET("/files/:id", zspContractHandler.GetFile) // zspContracts.GET("/files", zspContractHandler.ListFiles) // zspContracts.DELETE("/files/:id", zspContractHandler.DeleteFile) func (h *ZSPContractHandler) CreateFolder(c *gin.Context) { var req dto.ZSPFolderCreateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"success": false, "message": err.Error()}) return } err := h.service.CreateFolder(&req) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "folder created"}) } func (h *ZSPContractHandler) GetFolders(c *gin.Context) { folders, err := h.service.GetFolders() if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "folders retrieved", "data": folders}) } func (h *ZSPContractHandler) GetFolder(c *gin.Context) { folderID := c.Param("id") // 这里可以调用 service 获取单个文件夹的详情 folder, err := h.service.GetFolderById(folderID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "folder retrieved", "data": folder}) } func (h *ZSPContractHandler) DeleteFolder(c *gin.Context) { folderID := c.Param("id") err := h.service.DeleteFolderById(folderID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "folder deleted"}) } func (h *ZSPContractHandler) UpdateFolder(c *gin.Context) { folderID := c.Param("id") var req dto.ZSPFolderUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"success": false, "message": err.Error()}) return } // 这里可以调用 service 更新文件夹的信息 err := h.service.UpdateFolderById(folderID, &req) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "folder updated"}) } func (h *ZSPContractHandler) UploadContract(c *gin.Context) { var req dto.ZSPContractCreateRequest // FormData 方式上传文件,参数在 form 中 req.Commodity = c.PostForm("commodity") req.SignDate = c.PostForm("signDate") req.ContractType = c.PostForm("contractType") folderIDStr := c.PostForm("folderId") if folderIDStr == "" { c.JSON(400, gin.H{"success": false, "message": "请选择合同文件夹"}) return } folderID, err := strconv.ParseUint(folderIDStr, 10, 64) if err != nil { c.JSON(400, gin.H{"success": false, "message": "invalid folderId"}) return } req.FolderID = uint(folderID) req.ContractNumber = c.PostForm("contractNumber") req.CompanyName = c.PostForm("companyName") file, err := c.FormFile("file") if err != nil { c.JSON(400, gin.H{"success": false, "message": "file is required"}) return } err = h.service.UploadContract(&req, file) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract uploaded"}) } func (h *ZSPContractHandler) GetContract(c *gin.Context) { contractID := c.Param("id") // 这里可以调用 service 获取单个合同的详情 contract, err := h.service.GetContractById(contractID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract retrieved", "data": contract}) } func (h *ZSPContractHandler) ListContracts(c *gin.Context) { folderID := c.Query("folderId") contracts, err := h.service.ListContracts(folderID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contracts retrieved", "data": contracts}) } func (h *ZSPContractHandler) DeleteContract(c *gin.Context) { contractID := c.Param("id") err := h.service.DeleteContractById(contractID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract deleted"}) } func (h *ZSPContractHandler) UpdateContract(c *gin.Context) { contractID := c.Param("id") var req dto.ZSPContractUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"success": false, "message": err.Error()}) return } err := h.service.UpdateContractById(contractID, &req) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract updated"}) } // ============ 合同详情相关Handler ============ // GetContractWithDetails 获取合同及其详情列表 func (h *ZSPContractHandler) GetContractWithDetails(c *gin.Context) { contractID := c.Param("id") contract, err := h.service.GetContractWithDetails(contractID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract with details retrieved", "data": contract}) } // ListContractsWithDetails 获取合同列表(包含详情) func (h *ZSPContractHandler) ListContractsWithDetails(c *gin.Context) { folderID := c.Query("folderId") contracts, err := h.service.ListContractsWithDetails(folderID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contracts with details retrieved", "data": contracts}) } // CreateContractDetail 创建合同详情 func (h *ZSPContractHandler) CreateContractDetail(c *gin.Context) { var req dto.ZSPContractDetailCreateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"success": false, "message": err.Error()}) return } err := h.service.CreateContractDetail(&req) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract detail created"}) } // UpdateContractDetail 更新合同详情 func (h *ZSPContractHandler) UpdateContractDetail(c *gin.Context) { var req dto.ZSPContractDetailUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"success": false, "message": err.Error()}) return } err := h.service.UpdateContractDetail(&req) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract detail updated"}) } // GetContractDetail 获取合同详情 func (h *ZSPContractHandler) GetContractDetail(c *gin.Context) { detailID := c.Param("id") detail, err := h.service.GetContractDetailById(detailID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract detail retrieved", "data": detail}) } // ListContractDetails 获取合同详情列表 func (h *ZSPContractHandler) ListContractDetails(c *gin.Context) { contractFileID := c.Param("contractFileId") details, err := h.service.ListContractDetails(contractFileID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract details retrieved", "data": details}) } // DeleteContractDetail 删除合同详情 func (h *ZSPContractHandler) DeleteContractDetail(c *gin.Context) { detailID := c.Param("id") err := h.service.DeleteContractDetailById(detailID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": "contract detail deleted"}) } // BatchCreateContractDetails 批量创建合同详情 func (h *ZSPContractHandler) BatchCreateContractDetails(c *gin.Context) { var req dto.ZSPContractDetailBatchCreateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"success": false, "message": err.Error()}) return } err := h.service.BatchCreateContractDetails(&req) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } c.JSON(200, gin.H{"success": true, "message": fmt.Sprintf("%d contract details created", len(req.Details))}) } // ImportContractDetailsFromExcel 从Excel导入合同详情 func (h *ZSPContractHandler) ImportContractDetailsFromExcel(c *gin.Context) { contractFileIDStr := c.PostForm("contractFileId") if contractFileIDStr == "" { c.JSON(400, gin.H{"success": false, "message": "contractFileId is required"}) return } contractFileID, err := strconv.ParseUint(contractFileIDStr, 10, 64) if err != nil { c.JSON(400, gin.H{"success": false, "message": "invalid contractFileId"}) return } file, err := c.FormFile("file") if err != nil { c.JSON(400, gin.H{"success": false, "message": "file is required"}) return } // 打开上传的Excel文件 src, err := file.Open() if err != nil { c.JSON(500, gin.H{"success": false, "message": fmt.Sprintf("failed to open file: %v", err)}) return } defer src.Close() // 使用excelize读取Excel文件 f, err := excelize.OpenReader(src) if err != nil { c.JSON(500, gin.H{"success": false, "message": fmt.Sprintf("failed to read Excel file: %v", err)}) return } defer f.Close() // 获取第一个sheet的名称 sheets := f.GetSheetList() if len(sheets) == 0 { c.JSON(400, gin.H{"success": false, "message": "Excel file is empty"}) return } sheetName := sheets[0] // 读取所有行数据 rows, err := f.GetRows(sheetName) if err != nil { c.JSON(500, gin.H{"success": false, "message": fmt.Sprintf("failed to read rows: %v", err)}) return } // 检查是否有数据 if len(rows) <= 1 { c.JSON(400, gin.H{"success": false, "message": "Excel file has no data rows"}) return } // 解析数据(假设第一行是表头) var details []dto.ZSPContractDetailCreateRequest for i, row := range rows { // 跳过表头 if i == 0 { continue } // 确保至少有基本列 if len(row) < 6 { continue } detail := dto.ZSPContractDetailCreateRequest{ ContractFileID: uint(contractFileID), } // 商品名称 (第1列) detail.CommodityName = strings.TrimSpace(row[0]) // 单位 (第2列) detail.Unit = strings.TrimSpace(row[1]) if detail.Unit == "" { detail.Unit = "吨" } // 总量 (第3列) if row[2] != "" { detail.TotalQuantity, err = strconv.ParseFloat(strings.TrimSpace(row[2]), 64) if err != nil { c.JSON(400, gin.H{"success": false, "message": fmt.Sprintf("invalid total quantity at row %d: %v", i+1, err)}) return } } // 单价 (第4列) if row[3] != "" { detail.UnitPrice, err = strconv.ParseFloat(strings.TrimSpace(row[3]), 64) if err != nil { c.JSON(400, gin.H{"success": false, "message": fmt.Sprintf("invalid unit price at row %d: %v", i+1, err)}) return } } // 已提货数量 (第5列) if row[4] != "" { detail.DeliveredQty, err = strconv.ParseFloat(strings.TrimSpace(row[4]), 64) if err != nil { c.JSON(400, gin.H{"success": false, "message": fmt.Sprintf("invalid delivered quantity at row %d: %v", i+1, err)}) return } } // 提单号 (第6列) if len(row) > 5 { detail.BillOfLading = strings.TrimSpace(row[5]) } // 备注 (第7列) if len(row) > 6 { detail.Remark = strings.TrimSpace(row[6]) } // 验证必填字段 if detail.CommodityName == "" { c.JSON(400, gin.H{"success": false, "message": fmt.Sprintf("commodity name is required at row %d", i+1)}) return } if detail.TotalQuantity == 0 { c.JSON(400, gin.H{"success": false, "message": fmt.Sprintf("total quantity is required at row %d", i+1)}) return } if detail.UnitPrice == 0 { c.JSON(400, gin.H{"success": false, "message": fmt.Sprintf("unit price is required at row %d", i+1)}) return } details = append(details, detail) } // 批量创建合同详情 if len(details) == 0 { c.JSON(400, gin.H{"success": false, "message": "no valid data found in Excel file"}) return } batchReq := &dto.ZSPContractDetailBatchCreateRequest{ ContractFileID: uint(contractFileID), Details: details, } err = h.service.BatchCreateContractDetails(batchReq) if err != nil { c.JSON(500, gin.H{"success": false, "message": fmt.Sprintf("failed to create contract details: %v", err)}) return } c.JSON(200, gin.H{"success": true, "message": fmt.Sprintf("successfully imported %d contract details", len(details))}) } // ExportContractDetailsToExcel 导出合同详情到Excel func (h *ZSPContractHandler) ExportContractDetailsToExcel(c *gin.Context) { contractFileID := c.Param("id") details, err := h.service.ListContractDetails(contractFileID) if err != nil { c.JSON(500, gin.H{"success": false, "message": err.Error()}) return } // 创建新的Excel文件 f := excelize.NewFile() sheetName := "Sheet1" // 设置表头 headers := []string{"商品名称", "单位", "总量", "单价", "已提货数量", "应提货数量", "提单号", "备注"} for i, header := range headers { cell, _ := excelize.CoordinatesToCellName(i+1, 1) f.SetCellValue(sheetName, cell, header) // 设置表头样式(加粗、背景色) style, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{ Bold: true, }, Fill: excelize.Fill{ Type: "pattern", Color: []string{"#E0E0E0"}, Pattern: 1, }, Alignment: &excelize.Alignment{ Horizontal: "center", Vertical: "center", }, }) f.SetCellStyle(sheetName, cell, cell, style) } // 填充数据 for i, detail := range details { row := i + 2 // 商品名称 cell, _ := excelize.CoordinatesToCellName(1, row) f.SetCellValue(sheetName, cell, detail.CommodityName) // 单位 cell, _ = excelize.CoordinatesToCellName(2, row) f.SetCellValue(sheetName, cell, detail.Unit) // 总量 cell, _ = excelize.CoordinatesToCellName(3, row) f.SetCellValue(sheetName, cell, detail.TotalQuantity) // 单价 cell, _ = excelize.CoordinatesToCellName(4, row) f.SetCellValue(sheetName, cell, detail.UnitPrice) // 已提货数量 cell, _ = excelize.CoordinatesToCellName(5, row) f.SetCellValue(sheetName, cell, detail.DeliveredQty) // 应提货数量 cell, _ = excelize.CoordinatesToCellName(6, row) f.SetCellValue(sheetName, cell, detail.PendingQty) // 提单号 cell, _ = excelize.CoordinatesToCellName(7, row) f.SetCellValue(sheetName, cell, detail.BillOfLading) // 备注 cell, _ = excelize.CoordinatesToCellName(8, row) f.SetCellValue(sheetName, cell, detail.Remark) } // 设置列宽 f.SetColWidth(sheetName, "A", "A", 20) // 商品名称 f.SetColWidth(sheetName, "B", "B", 10) // 单位 f.SetColWidth(sheetName, "C", "C", 12) // 总量 f.SetColWidth(sheetName, "D", "D", 12) // 单价 f.SetColWidth(sheetName, "E", "E", 12) // 已提货数量 f.SetColWidth(sheetName, "F", "F", 12) // 应提货数量 f.SetColWidth(sheetName, "G", "G", 20) // 提单号 f.SetColWidth(sheetName, "H", "H", 30) // 备注 // 使用buffer保存Excel数据 var buf bytes.Buffer err = f.Write(&buf) if err != nil { c.JSON(500, gin.H{"success": false, "message": fmt.Sprintf("failed to write Excel buffer: %v", err)}) return } // 关闭文件 f.Close() // 设置响应头 c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=contract_details_%s.xlsx", contractFileID)) // 返回Excel文件 c.Data(200, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", buf.Bytes()) }