refactor: унифицировать CSV-экспорт, перенести pricing на сервер
- Вынести sortConfigsByLine() — устранить дублирование sort.Slice в ProjectToExportData и ProjectToPricingExportData - Добавить ConfigToPricingExportData() и ExportConfigPricingCSV handler - Зарегистрировать POST /api/configs/:uuid/export/pricing - Заменить клиентский DOM-скрапинг exportPricingCSV() на fetch к новому endpoint; артикул теперь включается через pricingConfigSummaryRow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -223,6 +223,63 @@ func (h *ExportHandler) ExportProjectCSV(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) {
|
||||
uuid := c.Param("uuid")
|
||||
|
||||
var req ProjectExportOptionsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
RespondError(c, http.StatusBadRequest, "invalid request", err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := h.configService.GetByUUID(uuid, h.dbUsername)
|
||||
if err != nil {
|
||||
RespondError(c, http.StatusNotFound, "resource not found", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := services.ProjectPricingExportOptions{
|
||||
IncludeLOT: req.IncludeLOT,
|
||||
IncludeBOM: req.IncludeBOM,
|
||||
IncludeEstimate: req.IncludeEstimate,
|
||||
IncludeStock: req.IncludeStock,
|
||||
IncludeCompetitor: req.IncludeCompetitor,
|
||||
Basis: req.Basis,
|
||||
SaleMarkup: req.SaleMarkup,
|
||||
}
|
||||
|
||||
data, err := h.exportService.ConfigToPricingExportData(config, opts)
|
||||
if err != nil {
|
||||
RespondError(c, http.StatusInternalServerError, "internal server error", err)
|
||||
return
|
||||
}
|
||||
|
||||
basisLabel := "FOB"
|
||||
if strings.EqualFold(strings.TrimSpace(req.Basis), "ddp") {
|
||||
basisLabel = "DDP"
|
||||
}
|
||||
|
||||
projectCode := config.Name
|
||||
if config.ProjectUUID != nil && *config.ProjectUUID != "" {
|
||||
if project, err := h.projectService.GetByUUID(*config.ProjectUUID, h.dbUsername); err == nil && project != nil {
|
||||
projectCode = project.Code
|
||||
}
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s (%s) %s %s SPEC.csv",
|
||||
time.Now().Format("2006-01-02"),
|
||||
projectCode,
|
||||
config.Name,
|
||||
basisLabel,
|
||||
)
|
||||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
|
||||
if err := h.exportService.ToPricingCSV(c.Writer, data, opts); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportProjectPricingCSV(c *gin.Context) {
|
||||
projectUUID := c.Param("uuid")
|
||||
|
||||
|
||||
@@ -213,27 +213,30 @@ func (s *ExportService) ToCSVBytes(data *ProjectExportData) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *ExportService) ProjectToPricingExportData(configs []models.Configuration, opts ProjectPricingExportOptions) (*ProjectPricingExportData, error) {
|
||||
sortedConfigs := make([]models.Configuration, len(configs))
|
||||
copy(sortedConfigs, configs)
|
||||
sort.Slice(sortedConfigs, func(i, j int) bool {
|
||||
leftLine := sortedConfigs[i].Line
|
||||
rightLine := sortedConfigs[j].Line
|
||||
|
||||
if leftLine <= 0 {
|
||||
leftLine = int(^uint(0) >> 1)
|
||||
func sortConfigsByLine(configs []models.Configuration) []models.Configuration {
|
||||
sorted := make([]models.Configuration, len(configs))
|
||||
copy(sorted, configs)
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
li, lj := sorted[i].Line, sorted[j].Line
|
||||
if li <= 0 {
|
||||
li = int(^uint(0) >> 1)
|
||||
}
|
||||
if rightLine <= 0 {
|
||||
rightLine = int(^uint(0) >> 1)
|
||||
if lj <= 0 {
|
||||
lj = int(^uint(0) >> 1)
|
||||
}
|
||||
if leftLine != rightLine {
|
||||
return leftLine < rightLine
|
||||
if li != lj {
|
||||
return li < lj
|
||||
}
|
||||
if !sortedConfigs[i].CreatedAt.Equal(sortedConfigs[j].CreatedAt) {
|
||||
return sortedConfigs[i].CreatedAt.After(sortedConfigs[j].CreatedAt)
|
||||
if !sorted[i].CreatedAt.Equal(sorted[j].CreatedAt) {
|
||||
return sorted[i].CreatedAt.After(sorted[j].CreatedAt)
|
||||
}
|
||||
return sortedConfigs[i].UUID > sortedConfigs[j].UUID
|
||||
return sorted[i].UUID > sorted[j].UUID
|
||||
})
|
||||
return sorted
|
||||
}
|
||||
|
||||
func (s *ExportService) ProjectToPricingExportData(configs []models.Configuration, opts ProjectPricingExportOptions) (*ProjectPricingExportData, error) {
|
||||
sortedConfigs := sortConfigsByLine(configs)
|
||||
|
||||
blocks := make([]ProjectPricingExportConfig, 0, len(sortedConfigs))
|
||||
for i := range sortedConfigs {
|
||||
@@ -296,26 +299,7 @@ func (s *ExportService) ConfigToExportData(cfg *models.Configuration) *ProjectEx
|
||||
|
||||
// ProjectToExportData converts multiple configurations into ProjectExportData.
|
||||
func (s *ExportService) ProjectToExportData(configs []models.Configuration) *ProjectExportData {
|
||||
sortedConfigs := make([]models.Configuration, len(configs))
|
||||
copy(sortedConfigs, configs)
|
||||
sort.Slice(sortedConfigs, func(i, j int) bool {
|
||||
leftLine := sortedConfigs[i].Line
|
||||
rightLine := sortedConfigs[j].Line
|
||||
|
||||
if leftLine <= 0 {
|
||||
leftLine = int(^uint(0) >> 1)
|
||||
}
|
||||
if rightLine <= 0 {
|
||||
rightLine = int(^uint(0) >> 1)
|
||||
}
|
||||
if leftLine != rightLine {
|
||||
return leftLine < rightLine
|
||||
}
|
||||
if !sortedConfigs[i].CreatedAt.Equal(sortedConfigs[j].CreatedAt) {
|
||||
return sortedConfigs[i].CreatedAt.After(sortedConfigs[j].CreatedAt)
|
||||
}
|
||||
return sortedConfigs[i].UUID > sortedConfigs[j].UUID
|
||||
})
|
||||
sortedConfigs := sortConfigsByLine(configs)
|
||||
|
||||
blocks := make([]ConfigExportBlock, 0, len(configs))
|
||||
for i := range sortedConfigs {
|
||||
@@ -327,6 +311,18 @@ func (s *ExportService) ProjectToExportData(configs []models.Configuration) *Pro
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigToPricingExportData is a single-config variant of ProjectToPricingExportData.
|
||||
func (s *ExportService) ConfigToPricingExportData(cfg *models.Configuration, opts ProjectPricingExportOptions) (*ProjectPricingExportData, error) {
|
||||
block, err := s.buildPricingExportBlock(cfg, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ProjectPricingExportData{
|
||||
Configs: []ProjectPricingExportConfig{block},
|
||||
CreatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ExportService) buildExportBlock(cfg *models.Configuration) ConfigExportBlock {
|
||||
// Batch-fetch categories from local data (pricelist items → local_components fallback)
|
||||
lotNames := make([]string, len(cfg.Items))
|
||||
|
||||
Reference in New Issue
Block a user