Files
zsp-project/backend/internal/handler/zsp_contract_handler.go
2026-06-03 20:59:39 +08:00

540 lines
16 KiB
Go
Executable File

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())
}