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

@@ -21,13 +21,23 @@ func NewPricelistRepository(db *gorm.DB) *PricelistRepository {
// List returns pricelists with pagination
func (r *PricelistRepository) List(offset, limit int) ([]models.PricelistSummary, int64, error) {
return r.ListBySource("", offset, limit)
}
// ListBySource returns pricelists filtered by source when provided.
func (r *PricelistRepository) ListBySource(source string, offset, limit int) ([]models.PricelistSummary, int64, error) {
query := r.db.Model(&models.Pricelist{})
if source != "" {
query = query.Where("source = ?", source)
}
var total int64
if err := r.db.Model(&models.Pricelist{}).Count(&total).Error; err != nil {
if err := query.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("counting pricelists: %w", err)
}
var pricelists []models.Pricelist
if err := r.db.Order("created_at DESC").Offset(offset).Limit(limit).Find(&pricelists).Error; err != nil {
if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&pricelists).Error; err != nil {
return nil, 0, fmt.Errorf("listing pricelists: %w", err)
}
@@ -36,13 +46,23 @@ func (r *PricelistRepository) List(offset, limit int) ([]models.PricelistSummary
// ListActive returns active pricelists with pagination.
func (r *PricelistRepository) ListActive(offset, limit int) ([]models.PricelistSummary, int64, error) {
return r.ListActiveBySource("", offset, limit)
}
// ListActiveBySource returns active pricelists filtered by source when provided.
func (r *PricelistRepository) ListActiveBySource(source string, offset, limit int) ([]models.PricelistSummary, int64, error) {
query := r.db.Model(&models.Pricelist{}).Where("is_active = ?", true)
if source != "" {
query = query.Where("source = ?", source)
}
var total int64
if err := r.db.Model(&models.Pricelist{}).Where("is_active = ?", true).Count(&total).Error; err != nil {
if err := query.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("counting active pricelists: %w", err)
}
var pricelists []models.Pricelist
if err := r.db.Where("is_active = ?", true).Order("created_at DESC").Offset(offset).Limit(limit).Find(&pricelists).Error; err != nil {
if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&pricelists).Error; err != nil {
return nil, 0, fmt.Errorf("listing active pricelists: %w", err)
}
@@ -68,6 +88,7 @@ func (r *PricelistRepository) toSummaries(pricelists []models.Pricelist) []model
summaries[i] = models.PricelistSummary{
ID: pl.ID,
Source: pl.Source,
Version: pl.Version,
Notification: pl.Notification,
CreatedAt: pl.CreatedAt,
@@ -102,8 +123,13 @@ func (r *PricelistRepository) GetByID(id uint) (*models.Pricelist, error) {
// GetByVersion returns a pricelist by version string
func (r *PricelistRepository) GetByVersion(version string) (*models.Pricelist, error) {
return r.GetBySourceAndVersion(string(models.PricelistSourceEstimate), version)
}
// GetBySourceAndVersion returns a pricelist by source/version.
func (r *PricelistRepository) GetBySourceAndVersion(source, version string) (*models.Pricelist, error) {
var pricelist models.Pricelist
if err := r.db.Where("version = ?", version).First(&pricelist).Error; err != nil {
if err := r.db.Where("source = ? AND version = ?", source, version).First(&pricelist).Error; err != nil {
return nil, fmt.Errorf("getting pricelist by version: %w", err)
}
return &pricelist, nil
@@ -111,8 +137,13 @@ func (r *PricelistRepository) GetByVersion(version string) (*models.Pricelist, e
// GetLatestActive returns the most recent active pricelist
func (r *PricelistRepository) GetLatestActive() (*models.Pricelist, error) {
return r.GetLatestActiveBySource(string(models.PricelistSourceEstimate))
}
// GetLatestActiveBySource returns the most recent active pricelist by source.
func (r *PricelistRepository) GetLatestActiveBySource(source string) (*models.Pricelist, error) {
var pricelist models.Pricelist
if err := r.db.Where("is_active = ?", true).Order("created_at DESC").First(&pricelist).Error; err != nil {
if err := r.db.Where("is_active = ? AND source = ?", true, source).Order("created_at DESC").First(&pricelist).Error; err != nil {
return nil, fmt.Errorf("getting latest pricelist: %w", err)
}
return &pricelist, nil
@@ -228,12 +259,17 @@ func (r *PricelistRepository) SetActive(id uint, isActive bool) error {
// GenerateVersion generates a new version string in format YYYY-MM-DD-NNN
func (r *PricelistRepository) GenerateVersion() (string, error) {
return r.GenerateVersionBySource(string(models.PricelistSourceEstimate))
}
// GenerateVersionBySource generates a new version string in format YYYY-MM-DD-NNN scoped by source.
func (r *PricelistRepository) GenerateVersionBySource(source string) (string, error) {
today := time.Now().Format("2006-01-02")
var last models.Pricelist
err := r.db.Model(&models.Pricelist{}).
Select("version").
Where("version LIKE ?", today+"-%").
Where("source = ? AND version LIKE ?", source, today+"-%").
Order("version DESC").
Limit(1).
Take(&last).Error
@@ -257,6 +293,19 @@ func (r *PricelistRepository) GenerateVersion() (string, error) {
return fmt.Sprintf("%s-%03d", today, n+1), nil
}
// GetPriceForLotBySource returns item price for a lot from latest active pricelist of source.
func (r *PricelistRepository) GetPriceForLotBySource(source, lotName string) (float64, uint, error) {
latest, err := r.GetLatestActiveBySource(source)
if err != nil {
return 0, 0, err
}
price, err := r.GetPriceForLot(latest.ID, lotName)
if err != nil {
return 0, 0, err
}
return price, latest.ID, nil
}
// CanWrite checks if the current database user has INSERT permission on qt_pricelists
func (r *PricelistRepository) CanWrite() bool {
canWrite, _ := r.CanWriteDebug()

View File

@@ -13,9 +13,9 @@ import (
func TestGenerateVersion_FirstOfDay(t *testing.T) {
repo := newTestPricelistRepository(t)
version, err := repo.GenerateVersion()
version, err := repo.GenerateVersionBySource(string(models.PricelistSourceEstimate))
if err != nil {
t.Fatalf("GenerateVersion returned error: %v", err)
t.Fatalf("GenerateVersionBySource returned error: %v", err)
}
today := time.Now().Format("2006-01-02")
@@ -30,8 +30,8 @@ func TestGenerateVersion_UsesMaxSuffixNotCount(t *testing.T) {
today := time.Now().Format("2006-01-02")
seed := []models.Pricelist{
{Version: fmt.Sprintf("%s-001", today), CreatedBy: "test", IsActive: true},
{Version: fmt.Sprintf("%s-003", today), CreatedBy: "test", IsActive: true},
{Source: string(models.PricelistSourceEstimate), Version: fmt.Sprintf("%s-001", today), CreatedBy: "test", IsActive: true},
{Source: string(models.PricelistSourceEstimate), Version: fmt.Sprintf("%s-003", today), CreatedBy: "test", IsActive: true},
}
for _, pl := range seed {
if err := repo.Create(&pl); err != nil {
@@ -39,9 +39,9 @@ func TestGenerateVersion_UsesMaxSuffixNotCount(t *testing.T) {
}
}
version, err := repo.GenerateVersion()
version, err := repo.GenerateVersionBySource(string(models.PricelistSourceEstimate))
if err != nil {
t.Fatalf("GenerateVersion returned error: %v", err)
t.Fatalf("GenerateVersionBySource returned error: %v", err)
}
want := fmt.Sprintf("%s-004", today)
@@ -50,6 +50,31 @@ func TestGenerateVersion_UsesMaxSuffixNotCount(t *testing.T) {
}
}
func TestGenerateVersion_IsolatedBySource(t *testing.T) {
repo := newTestPricelistRepository(t)
today := time.Now().Format("2006-01-02")
seed := []models.Pricelist{
{Source: string(models.PricelistSourceEstimate), Version: fmt.Sprintf("%s-009", today), CreatedBy: "test", IsActive: true},
{Source: string(models.PricelistSourceWarehouse), Version: fmt.Sprintf("%s-002", today), CreatedBy: "test", IsActive: true},
}
for _, pl := range seed {
if err := repo.Create(&pl); err != nil {
t.Fatalf("seed insert failed: %v", err)
}
}
version, err := repo.GenerateVersionBySource(string(models.PricelistSourceWarehouse))
if err != nil {
t.Fatalf("GenerateVersionBySource returned error: %v", err)
}
want := fmt.Sprintf("%s-003", today)
if version != want {
t.Fatalf("expected %s, got %s", want, version)
}
}
func newTestPricelistRepository(t *testing.T) *PricelistRepository {
t.Helper()