diff --git a/internal/handlers/pricelist.go b/internal/handlers/pricelist.go
index d45f032..83c9fb4 100644
--- a/internal/handlers/pricelist.go
+++ b/internal/handlers/pricelist.go
@@ -116,6 +116,7 @@ func (h *PricelistHandler) Create(c *gin.Context) {
changes = &pricelist.PriceChangeSummary{
Changed: []pricelist.PriceChangeItem{},
Missing: []pricelist.PriceChangeItem{},
+ Added: []pricelist.PriceChangeItem{},
}
}
c.JSON(http.StatusCreated, gin.H{
@@ -182,6 +183,7 @@ func (h *PricelistHandler) CreateWithProgress(c *gin.Context) {
changes = &pricelist.PriceChangeSummary{
Changed: []pricelist.PriceChangeItem{},
Missing: []pricelist.PriceChangeItem{},
+ Added: []pricelist.PriceChangeItem{},
}
}
diff --git a/internal/services/pricelist/service.go b/internal/services/pricelist/service.go
index fa6e93b..bc346fa 100644
--- a/internal/services/pricelist/service.go
+++ b/internal/services/pricelist/service.go
@@ -51,6 +51,7 @@ type PriceChangeSummary struct {
PreviousPricelistVersion string `json:"previous_pricelist_version,omitempty"`
Changed []PriceChangeItem `json:"changed"`
Missing []PriceChangeItem `json:"missing"`
+ Added []PriceChangeItem `json:"added"`
}
func NewService(db *gorm.DB, repo *repository.PricelistRepository, componentRepo *repository.ComponentRepository, pricingSvc *pricing.Service) *Service {
@@ -524,7 +525,7 @@ func (s *Service) StreamItemsForExport(pricelistID uint, batchSize int, callback
// BuildPriceChangeSummary compares a newly created pricelist with the previous pricelist of the same source.
func (s *Service) BuildPriceChangeSummary(pricelistID uint) (*PriceChangeSummary, error) {
if s.db == nil {
- return &PriceChangeSummary{Changed: []PriceChangeItem{}, Missing: []PriceChangeItem{}}, nil
+ return &PriceChangeSummary{Changed: []PriceChangeItem{}, Missing: []PriceChangeItem{}, Added: []PriceChangeItem{}}, nil
}
var current models.Pricelist
@@ -539,7 +540,7 @@ func (s *Service) BuildPriceChangeSummary(pricelistID uint) (*PriceChangeSummary
First(&previous).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
- return &PriceChangeSummary{Changed: []PriceChangeItem{}, Missing: []PriceChangeItem{}}, nil
+ return &PriceChangeSummary{Changed: []PriceChangeItem{}, Missing: []PriceChangeItem{}, Added: []PriceChangeItem{}}, nil
}
return nil, fmt.Errorf("loading previous pricelist: %w", err)
}
@@ -568,8 +569,14 @@ func (s *Service) BuildPriceChangeSummary(pricelistID uint) (*PriceChangeSummary
curMap[strings.TrimSpace(it.LotName)] = it.Price
}
+ prevMap := make(map[string]float64, len(prevItems))
+ for _, it := range prevItems {
+ prevMap[strings.TrimSpace(it.LotName)] = it.Price
+ }
+
changed := make([]PriceChangeItem, 0)
missing := make([]PriceChangeItem, 0)
+ added := make([]PriceChangeItem, 0)
const eps = 0.000001
for _, prev := range prevItems {
lotName := strings.TrimSpace(prev.LotName)
@@ -606,10 +613,28 @@ func (s *Service) BuildPriceChangeSummary(pricelistID uint) (*PriceChangeSummary
})
}
+ for _, cur := range curItems {
+ lotName := strings.TrimSpace(cur.LotName)
+ if lotName == "" {
+ continue
+ }
+ if _, exists := prevMap[lotName]; !exists {
+ priceCopy := cur.Price
+ added = append(added, PriceChangeItem{
+ LotName: lotName,
+ OldPrice: 0,
+ NewPrice: &priceCopy,
+ Diff: nil,
+ ChangeType: "added",
+ })
+ }
+ }
+
return &PriceChangeSummary{
PreviousPricelistID: previous.ID,
PreviousPricelistVersion: previous.Version,
Changed: changed,
Missing: missing,
+ Added: added,
}, nil
}
diff --git a/web/templates/admin_pricing.html b/web/templates/admin_pricing.html
index 72b43e2..2eed909 100644
--- a/web/templates/admin_pricing.html
+++ b/web/templates/admin_pricing.html
@@ -1982,18 +1982,18 @@ function formatPricelistChangePrice(value) {
return Number(value).toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
-function renderPricelistChangesTable(items, { showDiff = true } = {}) {
+function renderPricelistChangesTable(items, { showOldPrice = true, showDiff = true } = {}) {
if (!items || items.length === 0) return '
Нет данных
';
const rows = items.map(item => {
const t = item.change_type || '';
- const rowClass = t === 'increased' ? 'bg-red-50' : (t === 'decreased' ? 'bg-green-50' : '');
+ const rowClass = t === 'increased' ? 'bg-red-50' : (t === 'decreased' ? 'bg-green-50' : (t === 'added' ? 'bg-blue-50' : ''));
const diffValue = Number(item.diff || 0);
const diffClass = diffValue > 0 ? 'text-red-700' : (diffValue < 0 ? 'text-green-700' : 'text-gray-700');
const diffText = (showDiff && item.diff != null) ? `${diffValue > 0 ? '+' : ''}${formatPricelistChangePrice(diffValue)}` : '-';
return `
| ${escapeHtml(item.lot_name || '')} |
- ${formatPricelistChangePrice(item.old_price)} |
+ ${showOldPrice ? `${formatPricelistChangePrice(item.old_price)} | ` : ''}
${item.new_price == null ? '-' : formatPricelistChangePrice(item.new_price)} |
${diffText} |
@@ -2005,7 +2005,7 @@ function renderPricelistChangesTable(items, { showDiff = true } = {}) {
| Позиция |
- Было |
+ ${showOldPrice ? 'Было | ' : ''}
Стало |
Изм. |
@@ -2020,9 +2020,10 @@ function openPricelistPriceChangesModal(pricelist) {
const changes = pricelist?.price_changes || {};
const changed = Array.isArray(changes.changed) ? changes.changed : [];
const missing = Array.isArray(changes.missing) ? changes.missing : [];
+ const added = Array.isArray(changes.added) ? changes.added : [];
const prevVersion = changes.previous_pricelist_version || null;
document.getElementById('pricelist-price-changes-summary').innerHTML = prevVersion
- ? `Сравнение с прайслистом ${escapeHtml(prevVersion)}. Изменилось: ${changed.length}, пропало (нет котировок): ${missing.length}.`
+ ? `Сравнение с прайслистом ${escapeHtml(prevVersion)}. Изменилось: ${changed.length}, пропало: ${missing.length}, добавлено: ${added.length}.`
: 'Предыдущий прайслист для сравнения не найден.';
document.getElementById('pricelist-price-changes-content').innerHTML = `
@@ -2033,6 +2034,10 @@ function openPricelistPriceChangesModal(pricelist) {
Пропали котировки (цена была раньше)
${renderPricelistChangesTable(missing, { showDiff: false })}
+
+
Новые позиции
+ ${renderPricelistChangesTable(added, { showOldPrice: false, showDiff: false })}
+
`;
document.getElementById('pricelist-price-changes-modal').classList.remove('hidden');
document.getElementById('pricelist-price-changes-modal').classList.add('flex');
diff --git a/web/templates/pricelists.html b/web/templates/pricelists.html
index f9cc276..559c43f 100644
--- a/web/templates/pricelists.html
+++ b/web/templates/pricelists.html
@@ -212,13 +212,13 @@
return Number(value).toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
- function renderPriceChangesTable(items, { showNewPrice = true, showDiff = true } = {}) {
+ function renderPriceChangesTable(items, { showOldPrice = true, showNewPrice = true, showDiff = true } = {}) {
if (!items || items.length === 0) return 'Нет данных
';
const rows = items.map(item => {
const changeType = item.change_type || '';
const rowClass = changeType === 'increased'
? 'bg-red-50'
- : (changeType === 'decreased' ? 'bg-green-50' : '');
+ : (changeType === 'decreased' ? 'bg-green-50' : (changeType === 'added' ? 'bg-blue-50' : ''));
const diffValue = Number(item.diff || 0);
const diffText = showDiff && item.diff !== null && item.diff !== undefined
? `${diffValue > 0 ? '+' : ''}${formatPrice(diffValue)}`
@@ -227,7 +227,7 @@
return `
| ${escapeHtml(item.lot_name || '')} |
- ${formatPrice(item.old_price)} |
+ ${showOldPrice ? `${formatPrice(item.old_price)} | ` : ''}
${showNewPrice ? `${item.new_price == null ? '-' : formatPrice(item.new_price)} | ` : ''}
${showDiff ? `${diffText} | ` : ''}
@@ -240,7 +240,7 @@
| Позиция |
- Было |
+ ${showOldPrice ? 'Было | ' : ''}
${showNewPrice ? 'Стало | ' : ''}
${showDiff ? 'Изм. | ' : ''}
@@ -255,15 +255,17 @@
const changes = pl?.price_changes || {};
const changed = Array.isArray(changes.changed) ? changes.changed : [];
const missing = Array.isArray(changes.missing) ? changes.missing : [];
+ const added = Array.isArray(changes.added) ? changes.added : [];
const prevVersion = changes.previous_pricelist_version || null;
document.getElementById('price-changes-summary').innerHTML = prevVersion
- ? `Сравнение с прайслистом ${escapeHtml(prevVersion)}. Изменилось: ${changed.length}, пропало (нет котировок): ${missing.length}.`
+ ? `Сравнение с прайслистом ${escapeHtml(prevVersion)}. Изменилось: ${changed.length}, пропало: ${missing.length}, добавлено: ${added.length}.`
: 'Предыдущий прайслист для сравнения не найден.';
let html = '';
html += `Изменившиеся цены
${renderPriceChangesTable(changed)}`;
html += `Пропали котировки (цена была раньше)
${renderPriceChangesTable(missing, { showNewPrice: true, showDiff: false })}`;
+ html += `Новые позиции
${renderPriceChangesTable(added, { showOldPrice: false, showNewPrice: true, showDiff: false })}`;
document.getElementById('price-changes-content').innerHTML = html;
document.getElementById('price-changes-modal').classList.remove('hidden');