- Implement RefreshPrices for local-first mode - Update prices from local_components.current_price cache - Graceful degradation when component not found - Add PriceUpdatedAt timestamp to LocalConfiguration model - Support both authenticated and no-auth price refresh - Fix sync duplicate entry bug - pushConfigurationUpdate now ensures server_id exists before update - Fetch from LocalConfiguration.ServerID or search on server if missing - Update local config with server_id after finding - Add application auto-restart after settings save - Implement restartProcess() using syscall.Exec - Setup handler signals restart via channel - Setup page polls /health endpoint and redirects when ready - Add "Back" button on setup page when settings exist - Fix setup handler password handling - Use PasswordEncrypted field consistently - Support empty password by using saved value - Improve sync status handling - Add fallback for is_offline check in SyncStatusPartial - Enhance background sync logging with prefixes - Update CLAUDE.md documentation - Mark Phase 2.5 tasks as complete - Add UI Improvements section with future tasks - Update SQLite tables documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
package localdb
|
|
|
|
import (
|
|
"time"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/models"
|
|
)
|
|
|
|
// ConfigurationToLocal converts models.Configuration to LocalConfiguration
|
|
func ConfigurationToLocal(cfg *models.Configuration) *LocalConfiguration {
|
|
items := make(LocalConfigItems, len(cfg.Items))
|
|
for i, item := range cfg.Items {
|
|
items[i] = LocalConfigItem{
|
|
LotName: item.LotName,
|
|
Quantity: item.Quantity,
|
|
UnitPrice: item.UnitPrice,
|
|
}
|
|
}
|
|
|
|
local := &LocalConfiguration{
|
|
UUID: cfg.UUID,
|
|
Name: cfg.Name,
|
|
Items: items,
|
|
TotalPrice: cfg.TotalPrice,
|
|
CustomPrice: cfg.CustomPrice,
|
|
Notes: cfg.Notes,
|
|
IsTemplate: cfg.IsTemplate,
|
|
ServerCount: cfg.ServerCount,
|
|
PriceUpdatedAt: cfg.PriceUpdatedAt,
|
|
CreatedAt: cfg.CreatedAt,
|
|
UpdatedAt: time.Now(),
|
|
SyncStatus: "pending",
|
|
OriginalUserID: cfg.UserID,
|
|
}
|
|
|
|
if cfg.ID > 0 {
|
|
serverID := cfg.ID
|
|
local.ServerID = &serverID
|
|
}
|
|
|
|
return local
|
|
}
|
|
|
|
// LocalToConfiguration converts LocalConfiguration to models.Configuration
|
|
func LocalToConfiguration(local *LocalConfiguration) *models.Configuration {
|
|
items := make(models.ConfigItems, len(local.Items))
|
|
for i, item := range local.Items {
|
|
items[i] = models.ConfigItem{
|
|
LotName: item.LotName,
|
|
Quantity: item.Quantity,
|
|
UnitPrice: item.UnitPrice,
|
|
}
|
|
}
|
|
|
|
cfg := &models.Configuration{
|
|
UUID: local.UUID,
|
|
UserID: local.OriginalUserID,
|
|
Name: local.Name,
|
|
Items: items,
|
|
TotalPrice: local.TotalPrice,
|
|
CustomPrice: local.CustomPrice,
|
|
Notes: local.Notes,
|
|
IsTemplate: local.IsTemplate,
|
|
ServerCount: local.ServerCount,
|
|
PriceUpdatedAt: local.PriceUpdatedAt,
|
|
CreatedAt: local.CreatedAt,
|
|
}
|
|
|
|
if local.ServerID != nil {
|
|
cfg.ID = *local.ServerID
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
// PricelistToLocal converts models.Pricelist to LocalPricelist
|
|
func PricelistToLocal(pl *models.Pricelist) *LocalPricelist {
|
|
name := pl.Notification
|
|
if name == "" {
|
|
name = pl.Version
|
|
}
|
|
|
|
return &LocalPricelist{
|
|
ServerID: pl.ID,
|
|
Version: pl.Version,
|
|
Name: name,
|
|
CreatedAt: pl.CreatedAt,
|
|
SyncedAt: time.Now(),
|
|
IsUsed: false,
|
|
}
|
|
}
|
|
|
|
// LocalToPricelist converts LocalPricelist to models.Pricelist
|
|
func LocalToPricelist(local *LocalPricelist) *models.Pricelist {
|
|
return &models.Pricelist{
|
|
ID: local.ServerID,
|
|
Version: local.Version,
|
|
Notification: local.Name,
|
|
CreatedAt: local.CreatedAt,
|
|
IsActive: true,
|
|
}
|
|
}
|
|
|
|
// PricelistItemToLocal converts models.PricelistItem to LocalPricelistItem
|
|
func PricelistItemToLocal(item *models.PricelistItem, localPricelistID uint) *LocalPricelistItem {
|
|
return &LocalPricelistItem{
|
|
PricelistID: localPricelistID,
|
|
LotName: item.LotName,
|
|
Price: item.Price,
|
|
}
|
|
}
|
|
|
|
// LocalToPricelistItem converts LocalPricelistItem to models.PricelistItem
|
|
func LocalToPricelistItem(local *LocalPricelistItem, serverPricelistID uint) *models.PricelistItem {
|
|
return &models.PricelistItem{
|
|
ID: local.ID,
|
|
PricelistID: serverPricelistID,
|
|
LotName: local.LotName,
|
|
Price: local.Price,
|
|
}
|
|
}
|
|
|
|
// ComponentToLocal converts models.LotMetadata to LocalComponent
|
|
func ComponentToLocal(meta *models.LotMetadata) *LocalComponent {
|
|
var lotDesc string
|
|
var category string
|
|
|
|
if meta.Lot != nil {
|
|
lotDesc = meta.Lot.LotDescription
|
|
}
|
|
|
|
// Extract category from lot_name (e.g., "CPU_AMD_9654" -> "CPU")
|
|
if len(meta.LotName) > 0 {
|
|
for i, ch := range meta.LotName {
|
|
if ch == '_' {
|
|
category = meta.LotName[:i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return &LocalComponent{
|
|
LotName: meta.LotName,
|
|
LotDescription: lotDesc,
|
|
Category: category,
|
|
Model: meta.Model,
|
|
CurrentPrice: meta.CurrentPrice,
|
|
SyncedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// LocalToComponent converts LocalComponent to models.LotMetadata
|
|
func LocalToComponent(local *LocalComponent) *models.LotMetadata {
|
|
return &models.LotMetadata{
|
|
LotName: local.LotName,
|
|
Model: local.Model,
|
|
CurrentPrice: local.CurrentPrice,
|
|
Lot: &models.Lot{
|
|
LotName: local.LotName,
|
|
LotDescription: local.LotDescription,
|
|
},
|
|
}
|
|
}
|