459 lines
12 KiB
Go
459 lines
12 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/models"
|
|
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var (
|
|
ErrConfigNotFound = errors.New("configuration not found")
|
|
ErrConfigForbidden = errors.New("access to configuration forbidden")
|
|
)
|
|
|
|
// ConfigurationGetter is an interface for services that can retrieve configurations
|
|
// Used by handlers to work with both ConfigurationService and LocalConfigurationService
|
|
type ConfigurationGetter interface {
|
|
GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error)
|
|
}
|
|
|
|
type ConfigurationService struct {
|
|
configRepo *repository.ConfigurationRepository
|
|
componentRepo *repository.ComponentRepository
|
|
quoteService *QuoteService
|
|
}
|
|
|
|
func NewConfigurationService(
|
|
configRepo *repository.ConfigurationRepository,
|
|
componentRepo *repository.ComponentRepository,
|
|
quoteService *QuoteService,
|
|
) *ConfigurationService {
|
|
return &ConfigurationService{
|
|
configRepo: configRepo,
|
|
componentRepo: componentRepo,
|
|
quoteService: quoteService,
|
|
}
|
|
}
|
|
|
|
type CreateConfigRequest struct {
|
|
Name string `json:"name"`
|
|
Items models.ConfigItems `json:"items"`
|
|
CustomPrice *float64 `json:"custom_price"`
|
|
Notes string `json:"notes"`
|
|
IsTemplate bool `json:"is_template"`
|
|
ServerCount int `json:"server_count"`
|
|
}
|
|
|
|
func (s *ConfigurationService) Create(ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
|
|
total := req.Items.Total()
|
|
|
|
// If server count is greater than 1, multiply the total by server count
|
|
if req.ServerCount > 1 {
|
|
total *= float64(req.ServerCount)
|
|
}
|
|
|
|
config := &models.Configuration{
|
|
UUID: uuid.New().String(),
|
|
OwnerUsername: ownerUsername,
|
|
Name: req.Name,
|
|
Items: req.Items,
|
|
TotalPrice: &total,
|
|
CustomPrice: req.CustomPrice,
|
|
Notes: req.Notes,
|
|
IsTemplate: req.IsTemplate,
|
|
ServerCount: req.ServerCount,
|
|
}
|
|
|
|
if err := s.configRepo.Create(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Record usage stats
|
|
_ = s.quoteService.RecordUsage(req.Items)
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
// Allow access if user owns config or it's a template
|
|
if !s.isOwner(config, ownerUsername) && !config.IsTemplate {
|
|
return nil, ErrConfigForbidden
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) Update(uuid string, ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
if !s.isOwner(config, ownerUsername) {
|
|
return nil, ErrConfigForbidden
|
|
}
|
|
|
|
total := req.Items.Total()
|
|
|
|
// If server count is greater than 1, multiply the total by server count
|
|
if req.ServerCount > 1 {
|
|
total *= float64(req.ServerCount)
|
|
}
|
|
|
|
config.Name = req.Name
|
|
config.Items = req.Items
|
|
config.TotalPrice = &total
|
|
config.CustomPrice = req.CustomPrice
|
|
config.Notes = req.Notes
|
|
config.IsTemplate = req.IsTemplate
|
|
config.ServerCount = req.ServerCount
|
|
|
|
if err := s.configRepo.Update(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) Delete(uuid string, ownerUsername string) error {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return ErrConfigNotFound
|
|
}
|
|
|
|
if !s.isOwner(config, ownerUsername) {
|
|
return ErrConfigForbidden
|
|
}
|
|
|
|
return s.configRepo.Delete(config.ID)
|
|
}
|
|
|
|
func (s *ConfigurationService) Rename(uuid string, ownerUsername string, newName string) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
if !s.isOwner(config, ownerUsername) {
|
|
return nil, ErrConfigForbidden
|
|
}
|
|
|
|
config.Name = newName
|
|
|
|
if err := s.configRepo.Update(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) Clone(configUUID string, ownerUsername string, newName string) (*models.Configuration, error) {
|
|
original, err := s.GetByUUID(configUUID, ownerUsername)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create copy with new UUID and name
|
|
total := original.Items.Total()
|
|
|
|
// If server count is greater than 1, multiply the total by server count
|
|
if original.ServerCount > 1 {
|
|
total *= float64(original.ServerCount)
|
|
}
|
|
|
|
clone := &models.Configuration{
|
|
UUID: uuid.New().String(),
|
|
OwnerUsername: ownerUsername,
|
|
Name: newName,
|
|
Items: original.Items,
|
|
TotalPrice: &total,
|
|
CustomPrice: original.CustomPrice,
|
|
Notes: original.Notes,
|
|
IsTemplate: false, // Clone is never a template
|
|
ServerCount: original.ServerCount,
|
|
}
|
|
|
|
if err := s.configRepo.Create(clone); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return clone, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) ListByUser(ownerUsername string, page, perPage int) ([]models.Configuration, int64, error) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 || perPage > 100 {
|
|
perPage = 20
|
|
}
|
|
offset := (page - 1) * perPage
|
|
|
|
return s.configRepo.ListByUser(ownerUsername, offset, perPage)
|
|
}
|
|
|
|
// ListAll returns all configurations without user filter (for use when auth is disabled)
|
|
func (s *ConfigurationService) ListAll(page, perPage int) ([]models.Configuration, int64, error) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 || perPage > 100 {
|
|
perPage = 20
|
|
}
|
|
offset := (page - 1) * perPage
|
|
|
|
return s.configRepo.ListAll(offset, perPage)
|
|
}
|
|
|
|
// GetByUUIDNoAuth returns configuration without ownership check (for use when auth is disabled)
|
|
func (s *ConfigurationService) GetByUUIDNoAuth(uuid string) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
// UpdateNoAuth updates configuration without ownership check
|
|
func (s *ConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigRequest) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
total := req.Items.Total()
|
|
if req.ServerCount > 1 {
|
|
total *= float64(req.ServerCount)
|
|
}
|
|
|
|
config.Name = req.Name
|
|
config.Items = req.Items
|
|
config.TotalPrice = &total
|
|
config.CustomPrice = req.CustomPrice
|
|
config.Notes = req.Notes
|
|
config.IsTemplate = req.IsTemplate
|
|
config.ServerCount = req.ServerCount
|
|
|
|
if err := s.configRepo.Update(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// DeleteNoAuth deletes configuration without ownership check
|
|
func (s *ConfigurationService) DeleteNoAuth(uuid string) error {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return ErrConfigNotFound
|
|
}
|
|
return s.configRepo.Delete(config.ID)
|
|
}
|
|
|
|
// RenameNoAuth renames configuration without ownership check
|
|
func (s *ConfigurationService) RenameNoAuth(uuid string, newName string) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
config.Name = newName
|
|
if err := s.configRepo.Update(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// CloneNoAuth clones configuration without ownership check
|
|
func (s *ConfigurationService) CloneNoAuth(configUUID string, newName string, ownerUsername string) (*models.Configuration, error) {
|
|
original, err := s.configRepo.GetByUUID(configUUID)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
total := original.Items.Total()
|
|
if original.ServerCount > 1 {
|
|
total *= float64(original.ServerCount)
|
|
}
|
|
|
|
clone := &models.Configuration{
|
|
UUID: uuid.New().String(),
|
|
OwnerUsername: ownerUsername,
|
|
Name: newName,
|
|
Items: original.Items,
|
|
TotalPrice: &total,
|
|
CustomPrice: original.CustomPrice,
|
|
Notes: original.Notes,
|
|
IsTemplate: false,
|
|
ServerCount: original.ServerCount,
|
|
}
|
|
|
|
if err := s.configRepo.Create(clone); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return clone, nil
|
|
}
|
|
|
|
// RefreshPricesNoAuth refreshes prices without ownership check
|
|
func (s *ConfigurationService) RefreshPricesNoAuth(uuid string) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
updatedItems := make(models.ConfigItems, len(config.Items))
|
|
for i, item := range config.Items {
|
|
metadata, err := s.componentRepo.GetByLotName(item.LotName)
|
|
if err != nil || metadata.CurrentPrice == nil {
|
|
updatedItems[i] = item
|
|
continue
|
|
}
|
|
|
|
updatedItems[i] = models.ConfigItem{
|
|
LotName: item.LotName,
|
|
Quantity: item.Quantity,
|
|
UnitPrice: *metadata.CurrentPrice,
|
|
}
|
|
}
|
|
|
|
config.Items = updatedItems
|
|
total := updatedItems.Total()
|
|
if config.ServerCount > 1 {
|
|
total *= float64(config.ServerCount)
|
|
}
|
|
|
|
config.TotalPrice = &total
|
|
now := time.Now()
|
|
config.PriceUpdatedAt = &now
|
|
|
|
if err := s.configRepo.Update(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) ListTemplates(page, perPage int) ([]models.Configuration, int64, error) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 || perPage > 100 {
|
|
perPage = 20
|
|
}
|
|
offset := (page - 1) * perPage
|
|
|
|
return s.configRepo.ListTemplates(offset, perPage)
|
|
}
|
|
|
|
// RefreshPrices updates all component prices in the configuration with current prices
|
|
func (s *ConfigurationService) RefreshPrices(uuid string, ownerUsername string) (*models.Configuration, error) {
|
|
config, err := s.configRepo.GetByUUID(uuid)
|
|
if err != nil {
|
|
return nil, ErrConfigNotFound
|
|
}
|
|
|
|
if !s.isOwner(config, ownerUsername) {
|
|
return nil, ErrConfigForbidden
|
|
}
|
|
|
|
// Update prices for all items
|
|
updatedItems := make(models.ConfigItems, len(config.Items))
|
|
for i, item := range config.Items {
|
|
// Get current component price
|
|
metadata, err := s.componentRepo.GetByLotName(item.LotName)
|
|
if err != nil || metadata.CurrentPrice == nil {
|
|
// Keep original item if component not found or no price available
|
|
updatedItems[i] = item
|
|
continue
|
|
}
|
|
|
|
// Update item with current price
|
|
updatedItems[i] = models.ConfigItem{
|
|
LotName: item.LotName,
|
|
Quantity: item.Quantity,
|
|
UnitPrice: *metadata.CurrentPrice,
|
|
}
|
|
}
|
|
|
|
// Update configuration
|
|
config.Items = updatedItems
|
|
total := updatedItems.Total()
|
|
|
|
// If server count is greater than 1, multiply the total by server count
|
|
if config.ServerCount > 1 {
|
|
total *= float64(config.ServerCount)
|
|
}
|
|
|
|
config.TotalPrice = &total
|
|
|
|
// Set price update timestamp
|
|
now := time.Now()
|
|
config.PriceUpdatedAt = &now
|
|
|
|
if err := s.configRepo.Update(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
func (s *ConfigurationService) isOwner(config *models.Configuration, ownerUsername string) bool {
|
|
if config == nil || ownerUsername == "" {
|
|
return false
|
|
}
|
|
if config.OwnerUsername != "" {
|
|
return config.OwnerUsername == ownerUsername
|
|
}
|
|
if config.User != nil {
|
|
return config.User.Username == ownerUsername
|
|
}
|
|
return false
|
|
}
|
|
|
|
// // Export configuration as JSON
|
|
// type ConfigExport struct {
|
|
// Name string `json:"name"`
|
|
// Notes string `json:"notes"`
|
|
// Items models.ConfigItems `json:"items"`
|
|
// }
|
|
//
|
|
// func (s *ConfigurationService) ExportJSON(uuid string, userID uint) ([]byte, error) {
|
|
// config, err := s.GetByUUID(uuid, userID)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// export := ConfigExport{
|
|
// Name: config.Name,
|
|
// Notes: config.Notes,
|
|
// Items: config.Items,
|
|
// }
|
|
//
|
|
// return json.MarshalIndent(export, "", " ")
|
|
// }
|
|
//
|
|
// func (s *ConfigurationService) ImportJSON(userID uint, data []byte) (*models.Configuration, error) {
|
|
// var export ConfigExport
|
|
// if err := json.Unmarshal(data, &export); err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// req := &CreateConfigRequest{
|
|
// Name: export.Name,
|
|
// Notes: export.Notes,
|
|
// Items: export.Items,
|
|
// }
|
|
//
|
|
// return s.Create(userID, req)
|
|
// }
|