WIP: save current pricing and pricelist changes

This commit is contained in:
Mikhail Chusavitin
2026-02-06 19:07:22 +03:00
parent 29035ddc5a
commit b965c6bb95
18 changed files with 1263 additions and 182 deletions

View File

@@ -385,10 +385,9 @@ func (l *LocalDB) EnsureComponentPricesFromPricelists() error {
return nil
}
// If we have components but no prices, we should load prices from pricelists
// Find the latest pricelist
// If we have components but no prices, load from latest estimate pricelist.
var latestPricelist LocalPricelist
if err := l.db.Order("created_at DESC").First(&latestPricelist).Error; err != nil {
if err := l.db.Where("source = ?", "estimate").Order("created_at DESC").First(&latestPricelist).Error; err != nil {
if err == gorm.ErrRecordNotFound {
slog.Warn("no pricelists found in local database")
return nil

View File

@@ -139,6 +139,7 @@ func PricelistToLocal(pl *models.Pricelist) *LocalPricelist {
return &LocalPricelist{
ServerID: pl.ID,
Source: pl.Source,
Version: pl.Version,
Name: name,
CreatedAt: pl.CreatedAt,
@@ -151,6 +152,7 @@ func PricelistToLocal(pl *models.Pricelist) *LocalPricelist {
func LocalToPricelist(local *LocalPricelist) *models.Pricelist {
return &models.Pricelist{
ID: local.ServerID,
Source: local.Source,
Version: local.Version,
Notification: local.Name,
CreatedAt: local.CreatedAt,

View File

@@ -523,7 +523,16 @@ func (l *LocalDB) CountLocalPricelists() int64 {
// GetLatestLocalPricelist returns the most recently synced pricelist
func (l *LocalDB) GetLatestLocalPricelist() (*LocalPricelist, error) {
var pricelist LocalPricelist
if err := l.db.Order("created_at DESC").First(&pricelist).Error; err != nil {
if err := l.db.Where("source = ?", "estimate").Order("created_at DESC").First(&pricelist).Error; err != nil {
return nil, err
}
return &pricelist, nil
}
// GetLatestLocalPricelistBySource returns the most recently synced pricelist for a source.
func (l *LocalDB) GetLatestLocalPricelistBySource(source string) (*LocalPricelist, error) {
var pricelist LocalPricelist
if err := l.db.Where("source = ?", source).Order("created_at DESC").First(&pricelist).Error; err != nil {
return nil, err
}
return &pricelist, nil
@@ -541,7 +550,16 @@ func (l *LocalDB) GetLocalPricelistByServerID(serverID uint) (*LocalPricelist, e
// GetLocalPricelistByVersion returns a local pricelist by version string.
func (l *LocalDB) GetLocalPricelistByVersion(version string) (*LocalPricelist, error) {
var pricelist LocalPricelist
if err := l.db.Where("version = ?", version).First(&pricelist).Error; err != nil {
if err := l.db.Where("version = ? AND source = ?", version, "estimate").First(&pricelist).Error; err != nil {
return nil, err
}
return &pricelist, nil
}
// GetLocalPricelistBySourceAndVersion returns a local pricelist by source and version string.
func (l *LocalDB) GetLocalPricelistBySourceAndVersion(source, version string) (*LocalPricelist, error) {
var pricelist LocalPricelist
if err := l.db.Where("source = ? AND version = ?", source, version).First(&pricelist).Error; err != nil {
return nil, err
}
return &pricelist, nil
@@ -561,6 +579,7 @@ func (l *LocalDB) SaveLocalPricelist(pricelist *LocalPricelist) error {
return l.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "server_id"}},
DoUpdates: clause.Assignments(map[string]interface{}{
"source": pricelist.Source,
"version": pricelist.Version,
"name": pricelist.Name,
"created_at": pricelist.CreatedAt,

View File

@@ -53,6 +53,11 @@ var localMigrations = []localMigration{
name: "Use unique server_id for local pricelists and allow duplicate versions",
run: fixLocalPricelistIndexes,
},
{
id: "2026_02_06_pricelist_source",
name: "Backfill source for local pricelists and create source indexes",
run: backfillLocalPricelistSource,
},
}
func runLocalMigrations(db *gorm.DB) error {
@@ -205,7 +210,7 @@ func ensureDefaultProjectTx(tx *gorm.DB, ownerUsername string) (*LocalProject, e
func backfillConfigurationPricelists(tx *gorm.DB) error {
var latest LocalPricelist
if err := tx.Order("created_at DESC").First(&latest).Error; err != nil {
if err := tx.Where("source = ?", "estimate").Order("created_at DESC").First(&latest).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
@@ -292,3 +297,22 @@ func fixLocalPricelistIndexes(tx *gorm.DB) error {
return nil
}
func backfillLocalPricelistSource(tx *gorm.DB) error {
if err := tx.Exec(`
UPDATE local_pricelists
SET source = 'estimate'
WHERE source IS NULL OR source = ''
`).Error; err != nil {
return fmt.Errorf("backfill local_pricelists.source: %w", err)
}
if err := tx.Exec(`
CREATE INDEX IF NOT EXISTS idx_local_pricelists_source_created_at
ON local_pricelists(source, created_at DESC)
`).Error; err != nil {
return fmt.Errorf("ensure idx_local_pricelists_source_created_at: %w", err)
}
return nil
}

View File

@@ -128,9 +128,10 @@ func (LocalConfigurationVersion) TableName() string {
type LocalPricelist struct {
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
ServerID uint `gorm:"not null;uniqueIndex" json:"server_id"` // ID on MariaDB server
Source string `gorm:"not null;default:'estimate';index:idx_local_pricelists_source_created_at,priority:1" json:"source"`
Version string `gorm:"not null;index" json:"version"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
CreatedAt time.Time `gorm:"index:idx_local_pricelists_source_created_at,priority:2,sort:desc" json:"created_at"`
SyncedAt time.Time `json:"synced_at"`
IsUsed bool `gorm:"default:false" json:"is_used"` // Used by any local configuration
}