diff --git a/internal/handlers/export.go b/internal/handlers/export.go index 63ba70f..c943d94 100644 --- a/internal/handlers/export.go +++ b/internal/handlers/export.go @@ -150,7 +150,7 @@ func (h *ExportHandler) ExportConfigCSV(c *gin.Context) { uuid := c.Param("uuid") // Get config before streaming (can return JSON error) - config, err := h.configService.GetByUUID(uuid, h.dbUsername) + config, err := h.configService.GetByUUIDNoAuth(uuid) if err != nil { RespondError(c, http.StatusNotFound, "resource not found", err) return @@ -232,7 +232,7 @@ func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) { return } - config, err := h.configService.GetByUUID(uuid, h.dbUsername) + config, err := h.configService.GetByUUIDNoAuth(uuid) if err != nil { RespondError(c, http.StatusNotFound, "resource not found", err) return diff --git a/internal/handlers/export_test.go b/internal/handlers/export_test.go index da8cf1d..e12f3c0 100644 --- a/internal/handlers/export_test.go +++ b/internal/handlers/export_test.go @@ -26,6 +26,10 @@ func (m *mockConfigService) GetByUUID(uuid string, ownerUsername string) (*model return m.config, m.err } +func (m *mockConfigService) GetByUUIDNoAuth(uuid string) (*models.Configuration, error) { + return m.config, m.err +} + func TestExportCSV_Success(t *testing.T) { gin.SetMode(gin.TestMode) diff --git a/internal/services/configuration.go b/internal/services/configuration.go index 8fcaec6..b64e50e 100644 --- a/internal/services/configuration.go +++ b/internal/services/configuration.go @@ -18,6 +18,7 @@ var ( // Used by handlers to work with both ConfigurationService and LocalConfigurationService type ConfigurationGetter interface { GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) + GetByUUIDNoAuth(uuid string) (*models.Configuration, error) } type ConfigurationService struct { diff --git a/web/templates/configs.html b/web/templates/configs.html index 2d3488d..735553e 100644 --- a/web/templates/configs.html +++ b/web/templates/configs.html @@ -247,7 +247,7 @@ function renderConfigs(configs) { configs.forEach(c => { const date = new Date(c.created_at).toLocaleDateString('ru-RU'); - const total = c.total_price ? '$' + c.total_price.toLocaleString('en-US', {minimumFractionDigits: 2}) : '—'; + const total = c.total_price ? '$' + c.total_price.toLocaleString('ru-RU', {minimumFractionDigits: 2}) : '—'; const serverCount = c.server_count ? c.server_count : 1; const author = c.owner_username || (c.user && c.user.username) || '—'; const projectName = c.project_uuid && projectNameByUUID[c.project_uuid] @@ -258,7 +258,7 @@ function renderConfigs(configs) { let pricePerUnit = '—'; if (c.total_price && serverCount > 0) { const unitPrice = c.total_price / serverCount; - pricePerUnit = '$' + unitPrice.toLocaleString('en-US', {minimumFractionDigits: 2}); + pricePerUnit = '$' + unitPrice.toLocaleString('ru-RU', {minimumFractionDigits: 2}); } html += ''; diff --git a/web/templates/index.html b/web/templates/index.html index 6f03bbf..9f105d1 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2584,7 +2584,7 @@ function formatDiffPercent(baseTotal, compareTotal, compareLabel) { } const pct = ((baseTotal - compareTotal) / compareTotal) * 100; const sign = pct > 0 ? '+' : ''; - return `${sign}${pct.toFixed(1)}% от ${compareLabel}`; + return `${sign}${pct.toFixed(1).replace('.', ',')}% от ${compareLabel}`; } function getTotalClass(current, references) { @@ -2709,7 +2709,7 @@ function calculateCustomPrice() { // Show discount info discountInfoEl.classList.remove('hidden'); - discountPercentEl.textContent = discountPercent.toFixed(1) + '%'; + discountPercentEl.textContent = discountPercent.toFixed(1).replace('.', ',') + '%'; // Update discount color based on value const discountEl = discountPercentEl; @@ -4272,7 +4272,7 @@ function applyCustomPrice(table) { if (est <= 0) return ''; const pct = ((est - custom) / est * 100); const sign = pct >= 0 ? '-' : '+'; - return ` (${sign}${Math.abs(pct).toFixed(1)}%)`; + return ` (${sign}${Math.abs(pct).toFixed(1).replace('.', ',')}%)`; }; const _pctClass = (custom, est) => custom <= est ? 'text-green-600' : 'text-red-600'; @@ -4365,7 +4365,7 @@ function setPricingCustomPriceFromVendor() { const totalEl = document.getElementById('pricing-total-buy-vendor'); if (hasAny) { document.getElementById('pricing-custom-price-buy').value = total.toFixed(2); - const pct = estimateTotal > 0 ? ` (-${((estimateTotal - total) / estimateTotal * 100).toFixed(1)}%)` : ''; + const pct = estimateTotal > 0 ? ` (-${((estimateTotal - total) / estimateTotal * 100).toFixed(1).replace('.', ',')}%)` : ''; totalEl.textContent = formatCurrency(total) + pct; totalEl.className = totalEl.className.replace(/\btext-(?:green|red)-\d+\b/g, '').trim(); totalEl.classList.add(total <= estimateTotal ? 'text-green-600' : 'text-red-600'); diff --git a/web/templates/pricelist_detail.html b/web/templates/pricelist_detail.html index 44b5427..6757457 100644 --- a/web/templates/pricelist_detail.html +++ b/web/templates/pricelist_detail.html @@ -243,7 +243,7 @@ const descMax = stock ? 30 : 60; const html = items.map(item => { - const price = item.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + const price = item.price.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const description = item.lot_description || '-'; const truncatedDesc = description.length > descMax ? description.substring(0, descMax) + '...' : description; diff --git a/web/templates/project_detail.html b/web/templates/project_detail.html index 6898383..1a1f4f4 100644 --- a/web/templates/project_detail.html +++ b/web/templates/project_detail.html @@ -339,7 +339,7 @@ function escapeHtml(text) { function formatMoneyNoDecimals(value) { const safe = Number.isFinite(Number(value)) ? Number(value) : 0; - return '$' + Math.round(safe).toLocaleString('en-US'); + return '$' + Math.round(safe).toLocaleString('ru-RU'); } function resolveProjectTrackerURL(projectData) { diff --git a/web/templates/projects.html b/web/templates/projects.html index 6c1f30d..c585fbd 100644 --- a/web/templates/projects.html +++ b/web/templates/projects.html @@ -81,7 +81,7 @@ function escapeHtml(text) { } function formatMoney(v) { - return '$' + (v || 0).toLocaleString('en-US', {minimumFractionDigits: 2}); + return '$' + (v || 0).toLocaleString('ru-RU', {minimumFractionDigits: 2}); } function formatDateTime(value) {