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) {