package services import ( "bytes" "encoding/csv" "fmt" "time" "git.mchus.pro/mchus/quoteforge/internal/config" "git.mchus.pro/mchus/quoteforge/internal/models" "git.mchus.pro/mchus/quoteforge/internal/repository" ) type ExportService struct { config config.ExportConfig categoryRepo *repository.CategoryRepository } func NewExportService(cfg config.ExportConfig, categoryRepo *repository.CategoryRepository) *ExportService { return &ExportService{ config: cfg, categoryRepo: categoryRepo, } } type ExportData struct { Name string Items []ExportItem Total float64 Notes string CreatedAt time.Time } type ExportItem struct { LotName string Description string Category string Quantity int UnitPrice float64 TotalPrice float64 } func (s *ExportService) ToCSV(data *ExportData) ([]byte, error) { var buf bytes.Buffer w := csv.NewWriter(&buf) // Header headers := []string{"Артикул", "Описание", "Категория", "Количество", "Цена за единицу", "Сумма"} if err := w.Write(headers); err != nil { return nil, err } // Get category hierarchy for sorting categoryOrder := make(map[string]int) if s.categoryRepo != nil { categories, err := s.categoryRepo.GetAll() if err == nil { for _, cat := range categories { categoryOrder[cat.Code] = cat.DisplayOrder } } } // Sort items by category display order sortedItems := make([]ExportItem, len(data.Items)) copy(sortedItems, data.Items) // Sort using category display order (items without category go to the end) for i := 0; i < len(sortedItems)-1; i++ { for j := i + 1; j < len(sortedItems); j++ { orderI, hasI := categoryOrder[sortedItems[i].Category] orderJ, hasJ := categoryOrder[sortedItems[j].Category] // Items without category go to the end if !hasI && hasJ { sortedItems[i], sortedItems[j] = sortedItems[j], sortedItems[i] } else if hasI && hasJ { // Both have categories, sort by display order if orderI > orderJ { sortedItems[i], sortedItems[j] = sortedItems[j], sortedItems[i] } } } } // Items for _, item := range sortedItems { row := []string{ item.LotName, item.Description, item.Category, fmt.Sprintf("%d", item.Quantity), fmt.Sprintf("%.2f", item.UnitPrice), fmt.Sprintf("%.2f", item.TotalPrice), } if err := w.Write(row); err != nil { return nil, err } } // Total row if err := w.Write([]string{"", "", "", "", "ИТОГО:", fmt.Sprintf("%.2f", data.Total)}); err != nil { return nil, err } w.Flush() return buf.Bytes(), w.Error() } func (s *ExportService) ConfigToExportData(config *models.Configuration, componentService *ComponentService) *ExportData { items := make([]ExportItem, len(config.Items)) var total float64 for i, item := range config.Items { itemTotal := item.UnitPrice * float64(item.Quantity) // Получаем информацию о компоненте для заполнения категории componentView, err := componentService.GetByLotName(item.LotName) if err != nil { // Если не удалось получить информацию о компоненте, используем только основные данные items[i] = ExportItem{ LotName: item.LotName, Quantity: item.Quantity, UnitPrice: item.UnitPrice, TotalPrice: itemTotal, } } else { items[i] = ExportItem{ LotName: item.LotName, Description: componentView.Description, Category: componentView.Category, Quantity: item.Quantity, UnitPrice: item.UnitPrice, TotalPrice: itemTotal, } } total += itemTotal } return &ExportData{ Name: config.Name, Items: items, Total: total, Notes: config.Notes, CreatedAt: config.CreatedAt, } }