rebrand: письма и серверные строки ALL MARKET

- EmailTemplates (invite/low-stock/weekly-summary): шапка с текстовым
  вордмарком «all market» (бренд-цвета, всегда рендерится — без внешних
  картинок и привязки к домену) + подпись «ALL MARKET — система учёта торговли».
- Имя отправителя по умолчанию, темы/тела системных писем, экспорт-README,
  дневная сводка, тест SMTP → ALL MARKET.
- Цвета писем и домены не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-08 17:34:38 +05:00
parent 57f36d8dee
commit 03bdf4d4f1
11 changed files with 33 additions and 15 deletions

View file

@ -115,7 +115,7 @@ public async Task RunAsync(Guid exportId, CancellationToken ct = default)
_db.OrgAuditLogs.IgnoreQueryFilters().Where(x => x.OrganizationId == orgId), jsonOpts, ct); _db.OrgAuditLogs.IgnoreQueryFilters().Where(x => x.OrganizationId == orgId), jsonOpts, ct);
// README для пользователя. // README для пользователя.
var readme = $@"# Экспорт данных Food Market var readme = $@"# Экспорт данных ALL MARKET
Экспорт запрошен: {rec.CreatedAt:O} Экспорт запрошен: {rec.CreatedAt:O}
Организация: {orgId} Организация: {orgId}
@ -152,14 +152,14 @@ await using (var s = rEntry.Open())
{ {
var publicBase = _cfg["App:PublicBaseUrl"]?.TrimEnd('/') ?? "https://admin.food-market.kz"; var publicBase = _cfg["App:PublicBaseUrl"]?.TrimEnd('/') ?? "https://admin.food-market.kz";
var link = $"{publicBase}/api/org/export/download/{token}"; var link = $"{publicBase}/api/org/export/download/{token}";
var body = $@"<p>Ваш экспорт данных Food Market готов.</p> var body = $@"<p>Ваш экспорт данных ALL MARKET готов.</p>
<p>Скачать (действительно 24 часа):</p> <p>Скачать (действительно 24 часа):</p>
<p><a href=""{link}"">{link}</a></p> <p><a href=""{link}"">{link}</a></p>
<p>Размер архива: {ms.Length / 1024:N0} KB</p> <p>Размер архива: {ms.Length / 1024:N0} KB</p>
<p>Если вы не запрашивали экспорт — сообщите администратору организации.</p>"; <p>Если вы не запрашивали экспорт — сообщите администратору организации.</p>";
try try
{ {
await _email.SendAsync(rec.NotifyEmail, "Ваш экспорт данных Food Market готов", body, ct); await _email.SendAsync(rec.NotifyEmail, "Ваш экспорт данных ALL MARKET готов", body, ct);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -109,7 +109,7 @@ select new
string Money(decimal d) => $"{d:N0} ₸"; string Money(decimal d) => $"{d:N0} ₸";
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
sb.Append($"<b>Food Market — сводка за {yStart:dd.MM}</b>\n"); sb.Append($"<b>ALL MARKET — сводка за {yStart:dd.MM}</b>\n");
sb.Append($"<i>{Esc(orgName)}</i>\n\n"); sb.Append($"<i>{Esc(orgName)}</i>\n\n");
sb.Append($"💰 Выручка: <b>{Money(revenue)}</b>\n"); sb.Append($"💰 Выручка: <b>{Money(revenue)}</b>\n");
sb.Append($"🧾 Чеков: <b>{count}</b>\n"); sb.Append($"🧾 Чеков: <b>{count}</b>\n");

View file

@ -114,7 +114,7 @@ private async Task<CheckResult> CheckSmtp(bool sendEmail, CancellationToken ct)
$"конфиг есть ({s.SmtpHost}); тестовое письмо не отправлялось (sendTestEmail=false)"); $"конфиг есть ({s.SmtpHost}); тестовое письмо не отправлялось (sendTestEmail=false)");
} }
// Живой тест — отправляем письмо на FromEmail (сам себе). // Живой тест — отправляем письмо на FromEmail (сам себе).
await _email.SendAsync(s.FromEmail!, "Food Market — diagnostic ping", await _email.SendAsync(s.FromEmail!, "ALL MARKET — diagnostic ping",
"Это тест из /admin/diagnostic. Игнорируйте.", ct); "Это тест из /admin/diagnostic. Игнорируйте.", ct);
sw.Stop(); sw.Stop();
return new("SMTP", CheckStatus.Ok, sw.ElapsedMilliseconds, return new("SMTP", CheckStatus.Ok, sw.ElapsedMilliseconds,

View file

@ -84,13 +84,13 @@ public async Task<IActionResult> Forgot([FromBody] ForgotInput input, Cancellati
var resetUrl = BuildResetUrl(user.Email!, token); var resetUrl = BuildResetUrl(user.Email!, token);
var body = var body =
$"Здравствуйте.\n\n" + $"Здравствуйте.\n\n" +
$"Кто-то запросил восстановление пароля для вашего аккаунта Food Market ({user.Email}).\n" + $"Кто-то запросил восстановление пароля для вашего аккаунта ALL MARKET ({user.Email}).\n" +
$"Если это были вы — перейдите по ссылке (действительна 1 час):\n\n" + $"Если это были вы — перейдите по ссылке (действительна 1 час):\n\n" +
$"{resetUrl}\n\n" + $"{resetUrl}\n\n" +
$"Если вы не запрашивали восстановление — просто игнорируйте это письмо.\n"; $"Если вы не запрашивали восстановление — просто игнорируйте это письмо.\n";
try try
{ {
await _email.SendAsync(user.Email!, "Food Market — восстановление пароля", body, ct); await _email.SendAsync(user.Email!, "ALL MARKET — восстановление пароля", body, ct);
_logger.LogInformation("Forgot-password email sent to {Email}", user.Email); _logger.LogInformation("Forgot-password email sent to {Email}", user.Email);
} }
catch (EmailNotConfiguredException ex) catch (EmailNotConfiguredException ex)

View file

@ -90,7 +90,7 @@ public async Task<IActionResult> Submit([FromBody] FeedbackInput input, Cancella
var ua = HttpContext.Request.Headers["User-Agent"].ToString(); var ua = HttpContext.Request.Headers["User-Agent"].ToString();
var ip = HttpContext.Connection.RemoteIpAddress?.ToString(); var ip = HttpContext.Connection.RemoteIpAddress?.ToString();
var subject = $"Food Market feedback [{input.Category}] — {org}"; var subject = $"ALL MARKET feedback [{input.Category}] — {org}";
var body = var body =
$"Категория: {input.Category}\n" + $"Категория: {input.Category}\n" +
$"Организация: {org}\n" + $"Организация: {org}\n" +

View file

@ -192,7 +192,7 @@ public async Task<ActionResult<EmployeeCreateResult>> Create([FromBody] Employee
try try
{ {
var orgName = await _db.Organizations.Where(o => o.Id == orgId) var orgName = await _db.Organizations.Where(o => o.Id == orgId)
.Select(o => o.Name).FirstOrDefaultAsync(ct) ?? "Food Market"; .Select(o => o.Name).FirstOrDefaultAsync(ct) ?? "ALL MARKET";
var loginUrl = (Request.Scheme + "://" + Request.Host.Value + "/login"); var loginUrl = (Request.Scheme + "://" + Request.Host.Value + "/login");
var (subject, html, text) = _templates.Render("invite", new Dictionary<string, string?> var (subject, html, text) = _templates.Render("invite", new Dictionary<string, string?>
{ {

View file

@ -115,8 +115,8 @@ public async Task<IActionResult> TestSend([FromBody] TestSendInput input, Cancel
try try
{ {
await _email.SendAsync(input.ToEmail.Trim(), await _email.SendAsync(input.ToEmail.Trim(),
string.IsNullOrWhiteSpace(input.Subject) ? "Food Market — тестовое сообщение" : input.Subject, string.IsNullOrWhiteSpace(input.Subject) ? "ALL MARKET — тестовое сообщение" : input.Subject,
string.IsNullOrWhiteSpace(input.Body) ? "Тестовое письмо от Food Market." : input.Body, string.IsNullOrWhiteSpace(input.Body) ? "Тестовое письмо от ALL MARKET." : input.Body,
ct); ct);
return Ok(new { ok = true, sentTo = input.ToEmail }); return Ok(new { ok = true, sentTo = input.ToEmail });
} }

View file

@ -3,14 +3,17 @@ Subject: Приглашение в {{organizationName}}
<html lang="ru"> <html lang="ru">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Приглашение в Food Market</title> <title>Приглашение в ALL MARKET</title>
</head> </head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 24px; color: #1f2937;"> <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 24px; color: #1f2937;">
<div style="padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; margin-bottom: 20px;">
<span style="font-size: 22px; font-weight: 600; color: #0DB70D; letter-spacing: -0.5px;">all</span><span style="font-size: 22px; font-weight: 300; color: #4C4F54;"> market</span>
</div>
<h2 style="color: #0f766e;">Добро пожаловать в {{organizationName}}</h2> <h2 style="color: #0f766e;">Добро пожаловать в {{organizationName}}</h2>
<p>Здравствуйте, {{employeeName}}.</p> <p>Здравствуйте, {{employeeName}}.</p>
<p>Для вас создан доступ к системе Food Market. Войдите по адресу:</p> <p>Для вас создан доступ к системе ALL MARKET. Войдите по адресу:</p>
<p style="margin: 20px 0;"> <p style="margin: 20px 0;">
<a href="{{loginUrl}}" style="background: #0f766e; color: white; padding: 10px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">Открыть Food Market</a> <a href="{{loginUrl}}" style="background: #0f766e; color: white; padding: 10px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">Открыть ALL MARKET</a>
</p> </p>
<table style="border-collapse: collapse; background: #f9fafb; padding: 16px; border-radius: 6px;"> <table style="border-collapse: collapse; background: #f9fafb; padding: 16px; border-radius: 6px;">
<tr><td style="padding: 4px 12px;"><strong>Логин</strong></td><td style="padding: 4px 12px;"><code>{{email}}</code></td></tr> <tr><td style="padding: 4px 12px;"><strong>Логин</strong></td><td style="padding: 4px 12px;"><code>{{email}}</code></td></tr>
@ -20,5 +23,8 @@ Subject: Приглашение в {{organizationName}}
<p style="margin-top: 16px; color: #6b7280; font-size: 13px;"> <p style="margin-top: 16px; color: #6b7280; font-size: 13px;">
После первого входа смените пароль в личном кабинете. Если вы не ожидали это письмо — просто игнорируйте его, доступ без первого входа не активируется. После первого входа смените пароль в личном кабинете. Если вы не ожидали это письмо — просто игнорируйте его, доступ без первого входа не активируется.
</p> </p>
<p style="margin-top: 28px; padding-top: 16px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
<strong style="color: #4C4F54;">ALL MARKET</strong> — система учёта торговли
</p>
</body> </body>
</html> </html>

View file

@ -6,6 +6,9 @@ Subject: Низкие остатки · {{organizationName}}
<title>Низкие остатки</title> <title>Низкие остатки</title>
</head> </head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;"> <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;">
<div style="padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; margin-bottom: 20px;">
<span style="font-size: 22px; font-weight: 600; color: #0DB70D; letter-spacing: -0.5px;">all</span><span style="font-size: 22px; font-weight: 300; color: #4C4F54;"> market</span>
</div>
<h2 style="color: #b91c1c;">Низкие остатки</h2> <h2 style="color: #b91c1c;">Низкие остатки</h2>
<p>{{productCount}} товаров опустились ниже MinStock. Время сделать заказ.</p> <p>{{productCount}} товаров опустились ниже MinStock. Время сделать заказ.</p>
@ -15,5 +18,8 @@ Subject: Низкие остатки · {{organizationName}}
Полный список — в разделе <a href="{{stockUrl}}">Остатки</a>. Полный список — в разделе <a href="{{stockUrl}}">Остатки</a>.
Отключить уведомления можно в настройках организации. Отключить уведомления можно в настройках организации.
</p> </p>
<p style="margin-top: 28px; padding-top: 16px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
<strong style="color: #4C4F54;">ALL MARKET</strong> — система учёта торговли
</p>
</body> </body>
</html> </html>

View file

@ -6,6 +6,9 @@ Subject: Итоги недели · {{organizationName}}
<title>Еженедельная сводка</title> <title>Еженедельная сводка</title>
</head> </head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;"> <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;">
<div style="padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; margin-bottom: 20px;">
<span style="font-size: 22px; font-weight: 600; color: #0DB70D; letter-spacing: -0.5px;">all</span><span style="font-size: 22px; font-weight: 300; color: #4C4F54;"> market</span>
</div>
<h2 style="color: #0f766e;">Итоги недели</h2> <h2 style="color: #0f766e;">Итоги недели</h2>
<p>{{periodFrom}} — {{periodTo}}</p> <p>{{periodFrom}} — {{periodTo}}</p>
@ -32,5 +35,8 @@ Subject: Итоги недели · {{organizationName}}
<p style="margin-top: 32px; color: #6b7280; font-size: 13px;"> <p style="margin-top: 32px; color: #6b7280; font-size: 13px;">
Подробная аналитика — в разделе <a href="{{reportsUrl}}">Отчёты</a>. Подробная аналитика — в разделе <a href="{{reportsUrl}}">Отчёты</a>.
</p> </p>
<p style="margin-top: 28px; padding-top: 16px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
<strong style="color: #4C4F54;">ALL MARKET</strong> — система учёта торговли
</p>
</body> </body>
</html> </html>

View file

@ -48,7 +48,7 @@ public Task SendAsync(string toEmail, string subject, string body, CancellationT
} }
var msg = new MimeMessage(); var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(s.FromName ?? "Food Market", s.FromEmail)); msg.From.Add(new MailboxAddress(s.FromName ?? "ALL MARKET", s.FromEmail));
msg.To.Add(MailboxAddress.Parse(toEmail)); msg.To.Add(MailboxAddress.Parse(toEmail));
msg.Subject = subject; msg.Subject = subject;
if (htmlBody is not null) if (htmlBody is not null)