- 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>
140 lines
4.8 KiB
Go
140 lines
4.8 KiB
Go
package localdb
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// AppSetting stores application settings in local SQLite
|
|
type AppSetting struct {
|
|
Key string `gorm:"primaryKey" json:"key"`
|
|
Value string `gorm:"not null" json:"value"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
func (AppSetting) TableName() string {
|
|
return "app_settings"
|
|
}
|
|
|
|
// LocalConfigItem represents an item in a configuration
|
|
type LocalConfigItem struct {
|
|
LotName string `json:"lot_name"`
|
|
Quantity int `json:"quantity"`
|
|
UnitPrice float64 `json:"unit_price"`
|
|
}
|
|
|
|
// LocalConfigItems is a slice of LocalConfigItem that can be stored as JSON
|
|
type LocalConfigItems []LocalConfigItem
|
|
|
|
func (c LocalConfigItems) Value() (driver.Value, error) {
|
|
return json.Marshal(c)
|
|
}
|
|
|
|
func (c *LocalConfigItems) Scan(value interface{}) error {
|
|
if value == nil {
|
|
*c = make(LocalConfigItems, 0)
|
|
return nil
|
|
}
|
|
var bytes []byte
|
|
switch v := value.(type) {
|
|
case []byte:
|
|
bytes = v
|
|
case string:
|
|
bytes = []byte(v)
|
|
default:
|
|
return errors.New("type assertion failed for LocalConfigItems")
|
|
}
|
|
return json.Unmarshal(bytes, c)
|
|
}
|
|
|
|
func (c LocalConfigItems) Total() float64 {
|
|
var total float64
|
|
for _, item := range c {
|
|
total += item.UnitPrice * float64(item.Quantity)
|
|
}
|
|
return total
|
|
}
|
|
|
|
// LocalConfiguration stores configurations in local SQLite
|
|
type LocalConfiguration struct {
|
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
UUID string `gorm:"uniqueIndex;not null" json:"uuid"`
|
|
ServerID *uint `json:"server_id"` // ID on MariaDB server, NULL if local only
|
|
Name string `gorm:"not null" json:"name"`
|
|
Items LocalConfigItems `gorm:"type:text" json:"items"` // JSON stored as text in SQLite
|
|
TotalPrice *float64 `json:"total_price"`
|
|
CustomPrice *float64 `json:"custom_price"`
|
|
Notes string `json:"notes"`
|
|
IsTemplate bool `gorm:"default:false" json:"is_template"`
|
|
ServerCount int `gorm:"default:1" json:"server_count"`
|
|
PriceUpdatedAt *time.Time `gorm:"type:timestamp" json:"price_updated_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
SyncedAt *time.Time `json:"synced_at"`
|
|
SyncStatus string `gorm:"default:'local'" json:"sync_status"` // 'local', 'synced', 'modified'
|
|
OriginalUserID uint `json:"original_user_id"` // UserID from MariaDB for reference
|
|
}
|
|
|
|
func (LocalConfiguration) TableName() string {
|
|
return "local_configurations"
|
|
}
|
|
|
|
// LocalPricelist stores cached pricelists from server
|
|
type LocalPricelist struct {
|
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
ServerID uint `gorm:"not null" json:"server_id"` // ID on MariaDB server
|
|
Version string `gorm:"uniqueIndex;not null" json:"version"`
|
|
Name string `json:"name"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
SyncedAt time.Time `json:"synced_at"`
|
|
IsUsed bool `gorm:"default:false" json:"is_used"` // Used by any local configuration
|
|
}
|
|
|
|
func (LocalPricelist) TableName() string {
|
|
return "local_pricelists"
|
|
}
|
|
|
|
// LocalPricelistItem stores pricelist items
|
|
type LocalPricelistItem struct {
|
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
PricelistID uint `gorm:"not null;index" json:"pricelist_id"`
|
|
LotName string `gorm:"not null" json:"lot_name"`
|
|
Price float64 `gorm:"not null" json:"price"`
|
|
}
|
|
|
|
func (LocalPricelistItem) TableName() string {
|
|
return "local_pricelist_items"
|
|
}
|
|
|
|
// LocalComponent stores cached components for offline search
|
|
type LocalComponent struct {
|
|
LotName string `gorm:"primaryKey" json:"lot_name"`
|
|
LotDescription string `json:"lot_description"`
|
|
Category string `json:"category"`
|
|
Model string `json:"model"`
|
|
CurrentPrice *float64 `json:"current_price"`
|
|
SyncedAt time.Time `json:"synced_at"`
|
|
}
|
|
|
|
func (LocalComponent) TableName() string {
|
|
return "local_components"
|
|
}
|
|
|
|
// PendingChange stores changes that need to be synced to the server
|
|
type PendingChange struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
EntityType string `gorm:"not null;index" json:"entity_type"` // "configuration", "project", "specification"
|
|
EntityUUID string `gorm:"not null;index" json:"entity_uuid"`
|
|
Operation string `gorm:"not null" json:"operation"` // "create", "update", "delete"
|
|
Payload string `gorm:"type:text" json:"payload"` // JSON snapshot of the entity
|
|
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
|
Attempts int `gorm:"default:0" json:"attempts"` // Retry count for sync
|
|
LastError string `gorm:"type:text" json:"last_error,omitempty"`
|
|
}
|
|
|
|
func (PendingChange) TableName() string {
|
|
return "pending_changes"
|
|
}
|