Unified export filename format across both ExportCSV and ExportConfigCSV: - Format: YYYY-MM-DD (project_name) config_name BOM.csv - Use PriceUpdatedAt if available, otherwise CreatedAt - Extract project name from ProjectUUID for ExportCSV via projectService - Pass project_uuid from frontend to backend in export request - Add projectUUID and projectName state variables to track project context This ensures consistent naming whether exporting from form or project view, and uses most recent price update timestamp in filename. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
169 lines
5.1 KiB
Go
169 lines
5.1 KiB
Go
package handlers
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"time"
|
||
|
||
"git.mchus.pro/mchus/quoteforge/internal/middleware"
|
||
"git.mchus.pro/mchus/quoteforge/internal/services"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
type ExportHandler struct {
|
||
exportService *services.ExportService
|
||
configService services.ConfigurationGetter
|
||
componentService *services.ComponentService
|
||
projectService *services.ProjectService
|
||
}
|
||
|
||
func NewExportHandler(
|
||
exportService *services.ExportService,
|
||
configService services.ConfigurationGetter,
|
||
componentService *services.ComponentService,
|
||
projectService *services.ProjectService,
|
||
) *ExportHandler {
|
||
return &ExportHandler{
|
||
exportService: exportService,
|
||
configService: configService,
|
||
componentService: componentService,
|
||
projectService: projectService,
|
||
}
|
||
}
|
||
|
||
type ExportRequest struct {
|
||
Name string `json:"name" binding:"required"`
|
||
ProjectName string `json:"project_name"`
|
||
ProjectUUID string `json:"project_uuid"`
|
||
Items []struct {
|
||
LotName string `json:"lot_name" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,min=1"`
|
||
UnitPrice float64 `json:"unit_price"`
|
||
} `json:"items" binding:"required,min=1"`
|
||
Notes string `json:"notes"`
|
||
}
|
||
|
||
func (h *ExportHandler) ExportCSV(c *gin.Context) {
|
||
var req ExportRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
data := h.buildExportData(&req)
|
||
|
||
// Validate before streaming (can return JSON error)
|
||
if len(data.Items) == 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "no items to export"})
|
||
return
|
||
}
|
||
|
||
// Get project name if available
|
||
projectName := req.ProjectName
|
||
if projectName == "" && req.ProjectUUID != "" {
|
||
// Try to load project name from database
|
||
username := middleware.GetUsername(c)
|
||
if project, err := h.projectService.GetByUUID(req.ProjectUUID, username); err == nil && project != nil {
|
||
projectName = project.Name
|
||
}
|
||
}
|
||
if projectName == "" {
|
||
projectName = req.Name
|
||
}
|
||
|
||
// Set headers before streaming
|
||
exportDate := data.CreatedAt
|
||
filename := fmt.Sprintf("%s (%s) %s BOM.csv", exportDate.Format("2006-01-02"), projectName, req.Name)
|
||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||
|
||
// Stream CSV (cannot return JSON after this point)
|
||
if err := h.exportService.ToCSV(c.Writer, data); err != nil {
|
||
c.Error(err) // Log only
|
||
return
|
||
}
|
||
}
|
||
|
||
func (h *ExportHandler) buildExportData(req *ExportRequest) *services.ExportData {
|
||
items := make([]services.ExportItem, len(req.Items))
|
||
var total float64
|
||
|
||
for i, item := range req.Items {
|
||
itemTotal := item.UnitPrice * float64(item.Quantity)
|
||
|
||
// Получаем информацию о компоненте для заполнения категории и описания
|
||
componentView, err := h.componentService.GetByLotName(item.LotName)
|
||
if err != nil {
|
||
// Если не удалось получить информацию о компоненте, используем только основные данные
|
||
items[i] = services.ExportItem{
|
||
LotName: item.LotName,
|
||
Quantity: item.Quantity,
|
||
UnitPrice: item.UnitPrice,
|
||
TotalPrice: itemTotal,
|
||
}
|
||
} else {
|
||
items[i] = services.ExportItem{
|
||
LotName: item.LotName,
|
||
Description: componentView.Description,
|
||
Category: componentView.Category,
|
||
Quantity: item.Quantity,
|
||
UnitPrice: item.UnitPrice,
|
||
TotalPrice: itemTotal,
|
||
}
|
||
}
|
||
total += itemTotal
|
||
}
|
||
|
||
return &services.ExportData{
|
||
Name: req.Name,
|
||
Items: items,
|
||
Total: total,
|
||
Notes: req.Notes,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
}
|
||
|
||
func (h *ExportHandler) ExportConfigCSV(c *gin.Context) {
|
||
username := middleware.GetUsername(c)
|
||
uuid := c.Param("uuid")
|
||
|
||
// Get config before streaming (can return JSON error)
|
||
config, err := h.configService.GetByUUID(uuid, username)
|
||
if err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
data := h.exportService.ConfigToExportData(config, h.componentService)
|
||
|
||
// Validate before streaming (can return JSON error)
|
||
if len(data.Items) == 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "no items to export"})
|
||
return
|
||
}
|
||
|
||
// Get project name if configuration belongs to a project
|
||
projectName := config.Name // fallback: use config name if no project
|
||
if config.ProjectUUID != nil && *config.ProjectUUID != "" {
|
||
if project, err := h.projectService.GetByUUID(*config.ProjectUUID, username); err == nil && project != nil {
|
||
projectName = project.Name
|
||
}
|
||
}
|
||
|
||
// Set headers before streaming
|
||
// Use price update time if available, otherwise creation time
|
||
exportDate := config.CreatedAt
|
||
if config.PriceUpdatedAt != nil {
|
||
exportDate = *config.PriceUpdatedAt
|
||
}
|
||
filename := fmt.Sprintf("%s (%s) %s BOM.csv", exportDate.Format("2006-01-02"), projectName, config.Name)
|
||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||
|
||
// Stream CSV (cannot return JSON after this point)
|
||
if err := h.exportService.ToCSV(c.Writer, data); err != nil {
|
||
c.Error(err) // Log only
|
||
return
|
||
}
|
||
}
|