540 lines
16 KiB
Go
Executable File
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())
|
|
}
|