149 lines
5.3 KiB
Go
149 lines
5.3 KiB
Go
package localdb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
// BuildConfigurationSnapshot serializes the full local configuration state.
|
|
func BuildConfigurationSnapshot(localCfg *LocalConfiguration) (string, error) {
|
|
snapshot := map[string]interface{}{
|
|
"id": localCfg.ID,
|
|
"uuid": localCfg.UUID,
|
|
"server_id": localCfg.ServerID,
|
|
"project_uuid": localCfg.ProjectUUID,
|
|
"current_version_id": localCfg.CurrentVersionID,
|
|
"is_active": localCfg.IsActive,
|
|
"name": localCfg.Name,
|
|
"items": localCfg.Items,
|
|
"total_price": localCfg.TotalPrice,
|
|
"custom_price": localCfg.CustomPrice,
|
|
"notes": localCfg.Notes,
|
|
"is_template": localCfg.IsTemplate,
|
|
"server_count": localCfg.ServerCount,
|
|
"server_model": localCfg.ServerModel,
|
|
"support_code": localCfg.SupportCode,
|
|
"article": localCfg.Article,
|
|
"pricelist_id": localCfg.PricelistID,
|
|
"only_in_stock": localCfg.OnlyInStock,
|
|
"line": localCfg.Line,
|
|
"price_updated_at": localCfg.PriceUpdatedAt,
|
|
"created_at": localCfg.CreatedAt,
|
|
"updated_at": localCfg.UpdatedAt,
|
|
"synced_at": localCfg.SyncedAt,
|
|
"sync_status": localCfg.SyncStatus,
|
|
"original_user_id": localCfg.OriginalUserID,
|
|
"original_username": localCfg.OriginalUsername,
|
|
}
|
|
|
|
data, err := json.Marshal(snapshot)
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshal configuration snapshot: %w", err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// DecodeConfigurationSnapshot returns editable fields from one saved snapshot.
|
|
func DecodeConfigurationSnapshot(data string) (*LocalConfiguration, error) {
|
|
var snapshot struct {
|
|
ProjectUUID *string `json:"project_uuid"`
|
|
IsActive *bool `json:"is_active"`
|
|
Name string `json:"name"`
|
|
Items LocalConfigItems `json:"items"`
|
|
TotalPrice *float64 `json:"total_price"`
|
|
CustomPrice *float64 `json:"custom_price"`
|
|
Notes string `json:"notes"`
|
|
IsTemplate bool `json:"is_template"`
|
|
ServerCount int `json:"server_count"`
|
|
ServerModel string `json:"server_model"`
|
|
SupportCode string `json:"support_code"`
|
|
Article string `json:"article"`
|
|
PricelistID *uint `json:"pricelist_id"`
|
|
OnlyInStock bool `json:"only_in_stock"`
|
|
Line int `json:"line"`
|
|
PriceUpdatedAt *time.Time `json:"price_updated_at"`
|
|
OriginalUserID uint `json:"original_user_id"`
|
|
OriginalUsername string `json:"original_username"`
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(data), &snapshot); err != nil {
|
|
return nil, fmt.Errorf("unmarshal snapshot JSON: %w", err)
|
|
}
|
|
|
|
isActive := true
|
|
if snapshot.IsActive != nil {
|
|
isActive = *snapshot.IsActive
|
|
}
|
|
|
|
return &LocalConfiguration{
|
|
IsActive: isActive,
|
|
ProjectUUID: snapshot.ProjectUUID,
|
|
Name: snapshot.Name,
|
|
Items: snapshot.Items,
|
|
TotalPrice: snapshot.TotalPrice,
|
|
CustomPrice: snapshot.CustomPrice,
|
|
Notes: snapshot.Notes,
|
|
IsTemplate: snapshot.IsTemplate,
|
|
ServerCount: snapshot.ServerCount,
|
|
ServerModel: snapshot.ServerModel,
|
|
SupportCode: snapshot.SupportCode,
|
|
Article: snapshot.Article,
|
|
PricelistID: snapshot.PricelistID,
|
|
OnlyInStock: snapshot.OnlyInStock,
|
|
Line: snapshot.Line,
|
|
PriceUpdatedAt: snapshot.PriceUpdatedAt,
|
|
OriginalUserID: snapshot.OriginalUserID,
|
|
OriginalUsername: snapshot.OriginalUsername,
|
|
}, nil
|
|
}
|
|
|
|
type configurationSpecPriceFingerprint struct {
|
|
Items []configurationSpecPriceFingerprintItem `json:"items"`
|
|
ServerCount int `json:"server_count"`
|
|
TotalPrice *float64 `json:"total_price,omitempty"`
|
|
CustomPrice *float64 `json:"custom_price,omitempty"`
|
|
}
|
|
|
|
type configurationSpecPriceFingerprintItem struct {
|
|
LotName string `json:"lot_name"`
|
|
Quantity int `json:"quantity"`
|
|
UnitPrice float64 `json:"unit_price"`
|
|
}
|
|
|
|
// BuildConfigurationSpecPriceFingerprint returns a stable JSON key based on
|
|
// spec + price fields only, used for revision deduplication.
|
|
func BuildConfigurationSpecPriceFingerprint(localCfg *LocalConfiguration) (string, error) {
|
|
items := make([]configurationSpecPriceFingerprintItem, 0, len(localCfg.Items))
|
|
for _, item := range localCfg.Items {
|
|
items = append(items, configurationSpecPriceFingerprintItem{
|
|
LotName: item.LotName,
|
|
Quantity: item.Quantity,
|
|
UnitPrice: item.UnitPrice,
|
|
})
|
|
}
|
|
sort.Slice(items, func(i, j int) bool {
|
|
if items[i].LotName != items[j].LotName {
|
|
return items[i].LotName < items[j].LotName
|
|
}
|
|
if items[i].Quantity != items[j].Quantity {
|
|
return items[i].Quantity < items[j].Quantity
|
|
}
|
|
return items[i].UnitPrice < items[j].UnitPrice
|
|
})
|
|
|
|
payload := configurationSpecPriceFingerprint{
|
|
Items: items,
|
|
ServerCount: localCfg.ServerCount,
|
|
TotalPrice: localCfg.TotalPrice,
|
|
CustomPrice: localCfg.CustomPrice,
|
|
}
|
|
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshal spec+price fingerprint: %w", err)
|
|
}
|
|
return string(raw), nil
|
|
}
|