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