Refine notification template copy
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import { isManagerRole } from './access.js';
|
||||
|
||||
const DELIVERY_CHANNELS = ['EMAIL', 'TELEGRAM', 'MAX'];
|
||||
|
||||
function splitBody(text) {
|
||||
return String(text ?? '')
|
||||
.split('\n')
|
||||
@@ -20,22 +18,6 @@ function createChannelPreview(channel, fields = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function createMissingChannelPreview(channel) {
|
||||
return {
|
||||
channel,
|
||||
implemented: false,
|
||||
subject: null,
|
||||
body: ['В коде не реализовано.'],
|
||||
buttonText: null,
|
||||
buttonUrl: null,
|
||||
};
|
||||
}
|
||||
|
||||
function buildChannelMatrix(implementedChannels) {
|
||||
const implementedMap = new Map(implementedChannels.map((item) => [item.channel, item]));
|
||||
return DELIVERY_CHANNELS.map((channel) => implementedMap.get(channel) ?? createMissingChannelPreview(channel));
|
||||
}
|
||||
|
||||
export function buildFrontendAppUrl(path) {
|
||||
const baseUrl = String(
|
||||
process.env.TELEGRAM_MINI_APP_URL ||
|
||||
@@ -90,44 +72,93 @@ export function buildLoginCodeEmailTemplate({ code, expiresAt }) {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMessengerLoginTemplate({ buttonUrl }) {
|
||||
function formatOrderStatusLabel(status) {
|
||||
if (status === 'NEW' || status === 'MANAGER_PROCESSING') {
|
||||
return 'Заявка';
|
||||
}
|
||||
if (status === 'WAITING_DOUBLE_CONFIRM' || status === 'CONFIRMED') {
|
||||
return 'Предложение';
|
||||
}
|
||||
if (status === 'IN_PROGRESS') {
|
||||
return 'В работе';
|
||||
}
|
||||
if (status === 'COMPLETED') {
|
||||
return 'Завершен';
|
||||
}
|
||||
if (status === 'CLIENT_REJECTED' || status === 'MANAGER_REJECTED') {
|
||||
return 'Отклонен';
|
||||
}
|
||||
if (status === 'MANAGER_BLOCKED') {
|
||||
return 'Пауза';
|
||||
}
|
||||
return String(status || '').trim() || 'Статус обновлен';
|
||||
}
|
||||
|
||||
function formatWithdrawalStatusLabel(status) {
|
||||
if (status === 'APPROVED') {
|
||||
return 'одобрена';
|
||||
}
|
||||
if (status === 'REJECTED') {
|
||||
return 'отклонена';
|
||||
}
|
||||
return 'обновлена';
|
||||
}
|
||||
|
||||
export function buildMessengerLoginTemplate({ buttonUrl, expiresAt = null }) {
|
||||
const body = ['Для входа в личный кабинет перейдите по ссылке.'];
|
||||
if (expiresAt) {
|
||||
const expiresText = new Date(expiresAt).toLocaleString('ru-RU', { hour12: false });
|
||||
body.push(`Ссылка действует до: ${expiresText}`);
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Вход подтвержден. Нажмите кнопку, чтобы открыть личный кабинет.',
|
||||
message: body.join('\n'),
|
||||
buttonText: 'Открыть кабинет',
|
||||
buttonUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildOrderStatusNotificationTemplate({ orderId, orderCode, status, note, role }) {
|
||||
const message = `Заказ ${orderCode} изменил статус: ${status}.${note ? `\nКомментарий: ${note}` : ''}`;
|
||||
const body = [
|
||||
`Статус заказа ${orderCode}: ${formatOrderStatusLabel(status)}.`,
|
||||
...(note ? [`Комментарий: ${note}`] : []),
|
||||
];
|
||||
|
||||
return {
|
||||
message,
|
||||
subject: `Изменение статуса заказа ${orderCode}`,
|
||||
body,
|
||||
message: body.join('\n'),
|
||||
buttonText: 'Открыть заказ',
|
||||
buttonUrl: buildFrontendAppUrl(buildUserOrderPath(orderId, role)),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAutoBonusNotificationTemplate({ amount, orderCode }) {
|
||||
export function buildBonusCreditTemplate({ amount }) {
|
||||
const normalizedAmount = Number(amount);
|
||||
return {
|
||||
message: `Начислен бонус: ${amount} за заказ ${orderCode}.`,
|
||||
message: `Начислен бонус: ${Number.isFinite(normalizedAmount) ? normalizedAmount : amount}.`,
|
||||
buttonText: 'Открыть бонусную программу',
|
||||
buttonUrl: buildFrontendAppUrl(buildBonusProgramPath('auto-balance')),
|
||||
buttonUrl: buildFrontendAppUrl(buildBonusProgramPath('balance')),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildManualBonusNotificationTemplate({ amount, reason }) {
|
||||
return {
|
||||
message: `Начислен бонус: ${amount}. Причина: ${reason}`,
|
||||
buttonText: 'Открыть бонусную программу',
|
||||
buttonUrl: buildFrontendAppUrl(buildBonusProgramPath('manual-balance')),
|
||||
};
|
||||
export function buildAutoBonusNotificationTemplate({ amount }) {
|
||||
return buildBonusCreditTemplate({ amount });
|
||||
}
|
||||
|
||||
export function buildManualBonusNotificationTemplate({ amount }) {
|
||||
return buildBonusCreditTemplate({ amount });
|
||||
}
|
||||
|
||||
export function buildWithdrawalReviewNotificationTemplate({ status, reviewComment }) {
|
||||
const body = [
|
||||
`Ваша заявка на вывод вознаграждения ${formatWithdrawalStatusLabel(status)}.`,
|
||||
...(reviewComment ? [`Комментарий: ${reviewComment}`] : []),
|
||||
];
|
||||
|
||||
return {
|
||||
message: `Заявка на вывод вознаграждения обновлена: ${status}.${reviewComment ? ` Комментарий: ${reviewComment}` : ''}`,
|
||||
buttonText: 'Проверить бонусную программу',
|
||||
message: body.join('\n'),
|
||||
buttonText: 'Открыть бонусную программу',
|
||||
buttonUrl: buildFrontendAppUrl(buildBonusProgramPath('withdrawal-review')),
|
||||
};
|
||||
}
|
||||
@@ -139,6 +170,7 @@ export function getNotificationTemplatesCatalog() {
|
||||
});
|
||||
const messengerLoginTemplate = buildMessengerLoginTemplate({
|
||||
buttonUrl: 'https://fregat.dsrptlab.com/login?login_token=demo-token',
|
||||
expiresAt: '2026-04-06T15:35:00.000Z',
|
||||
});
|
||||
const orderStatusTemplate = buildOrderStatusNotificationTemplate({
|
||||
orderId: 'demo-order-id',
|
||||
@@ -147,13 +179,8 @@ export function getNotificationTemplatesCatalog() {
|
||||
note: 'заказ передан в производство',
|
||||
role: 'CLIENT',
|
||||
});
|
||||
const autoBonusTemplate = buildAutoBonusNotificationTemplate({
|
||||
const bonusTemplate = buildBonusCreditTemplate({
|
||||
amount: 1250,
|
||||
orderCode: 'FRG-2401',
|
||||
});
|
||||
const manualBonusTemplate = buildManualBonusNotificationTemplate({
|
||||
amount: 500,
|
||||
reason: 'ручное начисление менеджером',
|
||||
});
|
||||
const withdrawalReviewTemplate = buildWithdrawalReviewNotificationTemplate({
|
||||
status: 'APPROVED',
|
||||
@@ -164,17 +191,17 @@ export function getNotificationTemplatesCatalog() {
|
||||
{
|
||||
id: 'login-code-email',
|
||||
title: 'Код входа по email',
|
||||
channels: buildChannelMatrix([
|
||||
channels: [
|
||||
createChannelPreview('EMAIL', {
|
||||
subject: loginTemplate.subject,
|
||||
body: loginTemplate.body,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'messenger-login-confirmed',
|
||||
title: 'Подтверждение входа через мессенджер',
|
||||
channels: buildChannelMatrix([
|
||||
title: 'Привязка мессенджера',
|
||||
channels: [
|
||||
createChannelPreview('TELEGRAM', {
|
||||
body: splitBody(messengerLoginTemplate.message),
|
||||
buttonText: messengerLoginTemplate.buttonText,
|
||||
@@ -185,12 +212,16 @@ export function getNotificationTemplatesCatalog() {
|
||||
buttonText: messengerLoginTemplate.buttonText,
|
||||
buttonUrl: messengerLoginTemplate.buttonUrl,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'order-status-update',
|
||||
title: 'Изменение статуса заказа',
|
||||
channels: buildChannelMatrix([
|
||||
channels: [
|
||||
createChannelPreview('EMAIL', {
|
||||
subject: orderStatusTemplate.subject,
|
||||
body: orderStatusTemplate.body,
|
||||
}),
|
||||
createChannelPreview('TELEGRAM', {
|
||||
body: splitBody(orderStatusTemplate.message),
|
||||
buttonText: orderStatusTemplate.buttonText,
|
||||
@@ -201,44 +232,28 @@ export function getNotificationTemplatesCatalog() {
|
||||
buttonText: orderStatusTemplate.buttonText,
|
||||
buttonUrl: orderStatusTemplate.buttonUrl,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'auto-referral-bonus',
|
||||
title: 'Автоматическое начисление реферального бонуса',
|
||||
channels: buildChannelMatrix([
|
||||
id: 'bonus-credit',
|
||||
title: 'Начислен бонус',
|
||||
channels: [
|
||||
createChannelPreview('TELEGRAM', {
|
||||
body: splitBody(autoBonusTemplate.message),
|
||||
buttonText: autoBonusTemplate.buttonText,
|
||||
buttonUrl: autoBonusTemplate.buttonUrl,
|
||||
body: splitBody(bonusTemplate.message),
|
||||
buttonText: bonusTemplate.buttonText,
|
||||
buttonUrl: bonusTemplate.buttonUrl,
|
||||
}),
|
||||
createChannelPreview('MAX', {
|
||||
body: splitBody(autoBonusTemplate.message),
|
||||
buttonText: autoBonusTemplate.buttonText,
|
||||
buttonUrl: autoBonusTemplate.buttonUrl,
|
||||
body: splitBody(bonusTemplate.message),
|
||||
buttonText: bonusTemplate.buttonText,
|
||||
buttonUrl: bonusTemplate.buttonUrl,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
id: 'manual-bonus-credit',
|
||||
title: 'Ручное начисление бонуса',
|
||||
channels: buildChannelMatrix([
|
||||
createChannelPreview('TELEGRAM', {
|
||||
body: splitBody(manualBonusTemplate.message),
|
||||
buttonText: manualBonusTemplate.buttonText,
|
||||
buttonUrl: manualBonusTemplate.buttonUrl,
|
||||
}),
|
||||
createChannelPreview('MAX', {
|
||||
body: splitBody(manualBonusTemplate.message),
|
||||
buttonText: manualBonusTemplate.buttonText,
|
||||
buttonUrl: manualBonusTemplate.buttonUrl,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'reward-withdrawal-review',
|
||||
title: 'Решение по заявке на вывод бонусов',
|
||||
channels: buildChannelMatrix([
|
||||
title: 'Заявка на вывод вознаграждения',
|
||||
channels: [
|
||||
createChannelPreview('TELEGRAM', {
|
||||
body: splitBody(withdrawalReviewTemplate.message),
|
||||
buttonText: withdrawalReviewTemplate.buttonText,
|
||||
@@ -249,7 +264,7 @@ export function getNotificationTemplatesCatalog() {
|
||||
buttonText: withdrawalReviewTemplate.buttonText,
|
||||
buttonUrl: withdrawalReviewTemplate.buttonUrl,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -549,23 +549,36 @@ async function collectNotificationHistory(context, userId, channel, limit) {
|
||||
orderId: event.orderId,
|
||||
}));
|
||||
|
||||
const bonusHistory = bonuses.map((bonus) => ({
|
||||
const bonusHistory = bonuses.map((bonus) => {
|
||||
const template = buildManualBonusNotificationTemplate({
|
||||
amount: toFloat(bonus.amount),
|
||||
});
|
||||
|
||||
return {
|
||||
id: `BONUS_${bonus.id}_${channel}`,
|
||||
channel,
|
||||
title: 'Реферальный бонус',
|
||||
message: `Начисление ${toFloat(bonus.amount)}. Причина: ${bonus.reason}`,
|
||||
title: 'Начислен бонус',
|
||||
message: template.message,
|
||||
createdAt: bonus.createdAt,
|
||||
orderId: bonus.orderId,
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
const withdrawalHistory = withdrawals.map((withdrawal) => ({
|
||||
const withdrawalHistory = withdrawals.map((withdrawal) => {
|
||||
const template = buildWithdrawalReviewNotificationTemplate({
|
||||
status: withdrawal.status,
|
||||
reviewComment: withdrawal.reviewComment,
|
||||
});
|
||||
|
||||
return {
|
||||
id: `WITHDRAW_${withdrawal.id}_${channel}`,
|
||||
channel,
|
||||
title: 'Заявка на вывод вознаграждения',
|
||||
message: `Статус: ${withdrawal.status}.${withdrawal.reviewComment ? ` Комментарий: ${withdrawal.reviewComment}` : ''}`,
|
||||
message: template.message,
|
||||
createdAt: withdrawal.updatedAt,
|
||||
orderId: null,
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
return [...eventHistory, ...bonusHistory, ...withdrawalHistory]
|
||||
.sort(byCreatedAtDesc)
|
||||
|
||||
@@ -430,6 +430,7 @@ app.post('/bot/messenger-login', async (req, res) => {
|
||||
if (!skipDispatch) {
|
||||
const template = buildMessengerLoginTemplate({
|
||||
buttonUrl: loginUrl,
|
||||
expiresAt: login.expiresAt,
|
||||
});
|
||||
const dispatch = await sendMessengerMessage({
|
||||
type: channel,
|
||||
|
||||
Reference in New Issue
Block a user