Disable push quick replies and clarify payroll component labels
This commit is contained in:
parent
29d8b05393
commit
2277d58967
@ -24,11 +24,11 @@ class VacationPayInstructionStatus(models.TextChoices):
|
||||
|
||||
|
||||
class PayrollComponentKind(models.TextChoices):
|
||||
EARNING = "earning", "Начисление"
|
||||
ADJUSTMENT_CREDIT = "adjustment_credit", "Коррекция +"
|
||||
ADJUSTMENT_DEBIT = "adjustment_debit", "Коррекция −"
|
||||
DEDUCTION = "deduction", "Удержание"
|
||||
PAYMENT = "payment", "Выплата"
|
||||
EARNING = "earning", "Начислить сотруднику"
|
||||
ADJUSTMENT_CREDIT = "adjustment_credit", "Положительная корректировка"
|
||||
ADJUSTMENT_DEBIT = "adjustment_debit", "Отрицательная корректировка"
|
||||
DEDUCTION = "deduction", "Удержать"
|
||||
PAYMENT = "payment", "Уже выплачено"
|
||||
|
||||
|
||||
class PayrollComponentPeriodScope(models.TextChoices):
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
KIND_CHOICES = [
|
||||
("earning", "Начислить сотруднику"),
|
||||
("adjustment_credit", "Положительная корректировка"),
|
||||
("adjustment_debit", "Отрицательная корректировка"),
|
||||
("deduction", "Удержать"),
|
||||
("payment", "Уже выплачено"),
|
||||
]
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("finance", "0029_alter_payrollcustomcomponent_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="payrollcustomcomponent",
|
||||
name="kind",
|
||||
field=models.CharField(
|
||||
choices=KIND_CHOICES,
|
||||
max_length=24,
|
||||
verbose_name="Влияние на расчёт",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="payrollstatementline",
|
||||
name="kind",
|
||||
field=models.CharField(
|
||||
choices=KIND_CHOICES,
|
||||
max_length=24,
|
||||
verbose_name="Тип",
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -18,7 +18,6 @@ from notifications.chat_suppression import suppress_for_active_chat
|
||||
from notifications.push_actions import (
|
||||
build_push_avatar_token,
|
||||
build_push_read_token,
|
||||
build_push_reply_token,
|
||||
)
|
||||
|
||||
|
||||
@ -154,7 +153,6 @@ class PushNotificationSender(BaseNotificationSender):
|
||||
"message_id": message_id,
|
||||
"chat_name": source_data.get("chat_name"),
|
||||
"chat_type": source_data.get("chat_type"),
|
||||
"can_reply": source_data.get("can_reply", True),
|
||||
"actor_name": actor_name,
|
||||
},
|
||||
}
|
||||
@ -171,40 +169,19 @@ class PushNotificationSender(BaseNotificationSender):
|
||||
chat_id=int(chat_id),
|
||||
message_id=int(message_id),
|
||||
)
|
||||
reply_token = build_push_reply_token(
|
||||
notification_id=notification.id,
|
||||
user_id=notification.recipient_id,
|
||||
chat_id=int(chat_id),
|
||||
message_id=int(message_id),
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
read_token = ""
|
||||
reply_token = ""
|
||||
|
||||
if read_token and reply_token:
|
||||
actions = []
|
||||
if show_preview and source_data.get("can_reply", True):
|
||||
actions.append({
|
||||
"action": "reply",
|
||||
"type": "text",
|
||||
"title": "Ответить",
|
||||
"placeholder": "Введите ответ…",
|
||||
})
|
||||
actions.append({
|
||||
if read_token:
|
||||
message_data["actions"] = [{
|
||||
"action": "mark-read",
|
||||
"type": "button",
|
||||
"title": "Прочитано",
|
||||
})
|
||||
message_data["actions"] = actions
|
||||
}]
|
||||
message_data["data"].update({
|
||||
"mark_read_url": "/api/v1/notifications/push/action/read/",
|
||||
"mark_read_token": read_token,
|
||||
})
|
||||
if show_preview and source_data.get("can_reply", True):
|
||||
message_data["data"].update({
|
||||
"reply_url": "/api/v1/notifications/push/action/reply/",
|
||||
"reply_token": reply_token,
|
||||
})
|
||||
|
||||
actor_icon = (
|
||||
self._get_actor_avatar_url(notification)
|
||||
|
||||
@ -99,21 +99,15 @@ class TestPushNotificationSenderPayload:
|
||||
assert payload["data"]["actor_name"] == "Рубцова Олеся"
|
||||
assert payload["data"]["mark_read_url"].endswith("/push/action/read/")
|
||||
assert payload["data"]["mark_read_token"]
|
||||
assert payload["data"]["reply_url"].endswith("/push/action/reply/")
|
||||
assert payload["data"]["reply_token"]
|
||||
assert payload["actions"] == [
|
||||
{
|
||||
"action": "reply",
|
||||
"type": "text",
|
||||
"title": "Ответить",
|
||||
"placeholder": "Введите ответ…",
|
||||
},
|
||||
{
|
||||
"action": "mark-read",
|
||||
"type": "button",
|
||||
"title": "Прочитано",
|
||||
},
|
||||
]
|
||||
assert "reply_url" not in payload["data"]
|
||||
assert "reply_token" not in payload["data"]
|
||||
assert payload["tag"] == "chat-123"
|
||||
assert "icon" not in payload
|
||||
assert "chat_new_message" not in json.dumps(payload)
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
* нативные уведомления даже когда страница закрыта.
|
||||
*/
|
||||
|
||||
const SW_VERSION = '2.4.0';
|
||||
const SW_VERSION = '2.4.1';
|
||||
const SW_APP_KIND = self.CORP_SW_APP_KIND || 'main';
|
||||
const STATIC_CACHE = `eusrr-static-${SW_VERSION}`;
|
||||
const PRECACHE_URLS = [
|
||||
@ -30,13 +30,6 @@ const DEFAULT_NOTIFICATION_OPTIONS = {
|
||||
renotify: true,
|
||||
};
|
||||
|
||||
function supportsNativeInlineReply() {
|
||||
const userAgent = self.navigator?.userAgent || '';
|
||||
const isChromium = /(?:Chrome|Chromium|Edg|OPR)\//.test(userAgent);
|
||||
const isSupportedPlatform = /(?:Windows NT|Android|CrOS)/.test(userAgent);
|
||||
return isChromium && isSupportedPlatform;
|
||||
}
|
||||
|
||||
function buildNotificationData(payload = {}) {
|
||||
const notificationData = {
|
||||
title: payload.title || payload.head || 'Новое уведомление',
|
||||
@ -58,7 +51,7 @@ function buildNotificationData(payload = {}) {
|
||||
if (payload.image) notificationData.image = payload.image;
|
||||
if (payload.actions) {
|
||||
notificationData.actions = payload.actions.filter(
|
||||
(action) => action.type !== 'text' || supportsNativeInlineReply(),
|
||||
(action) => action.action !== 'reply',
|
||||
);
|
||||
}
|
||||
return notificationData;
|
||||
@ -139,48 +132,6 @@ async function markPushNotificationRead(notification) {
|
||||
}));
|
||||
}
|
||||
|
||||
async function sendPushQuickReply(notification, reply) {
|
||||
const data = notification.data || {};
|
||||
const normalizedReply = typeof reply === 'string' ? reply.trim() : '';
|
||||
if (!normalizedReply) {
|
||||
throw new Error('Native notification did not provide reply text');
|
||||
}
|
||||
if (!data.reply_url || !data.reply_token) {
|
||||
throw new Error('Push notification has no signed reply action');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
new URL(data.reply_url, self.location.origin).toString(),
|
||||
{
|
||||
method: 'POST',
|
||||
credentials: 'omit',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
token: data.reply_token,
|
||||
reply: normalizedReply,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Push reply failed with HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const notifications = await self.registration.getNotifications({ includeTriggered: true });
|
||||
notifications
|
||||
.filter((item) => sameIdentity(item.data?.chat_id, data.chat_id))
|
||||
.forEach((item) => item.close());
|
||||
|
||||
const windowClients = await clients.matchAll({ type: 'window', includeUncontrolled: true });
|
||||
windowClients.forEach((client) => client.postMessage({
|
||||
type: 'PUSH_CHAT_QUICK_REPLIED',
|
||||
chatId: data.chat_id,
|
||||
messageId: result.message_id,
|
||||
replyToId: data.message_id,
|
||||
notificationId: data.notification_id,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Событие установки Service Worker
|
||||
*/
|
||||
@ -329,19 +280,6 @@ self.addEventListener('message', (event) => {
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
console.log('[SW] Notification clicked:', event.notification.tag);
|
||||
|
||||
if (event.action === 'reply') {
|
||||
if (typeof event.reply !== 'string' || !event.reply.trim()) {
|
||||
console.warn('[SW] Native inline reply is unavailable on this platform');
|
||||
return;
|
||||
}
|
||||
event.waitUntil(
|
||||
sendPushQuickReply(event.notification, event.reply).catch((error) => {
|
||||
console.error('[SW] Failed to send push reply:', error);
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.action === 'mark-read') {
|
||||
event.waitUntil(
|
||||
markPushNotificationRead(event.notification).catch((error) => {
|
||||
|
||||
@ -48,11 +48,11 @@ const statusMeta: Record<PayrollPeriodTableStatus, { label: string; className: s
|
||||
};
|
||||
|
||||
const kindLabels = {
|
||||
earning: "Начисление",
|
||||
adjustment_credit: "Корректировка +",
|
||||
adjustment_debit: "Корректировка −",
|
||||
deduction: "Удержание",
|
||||
payment: "Выплата",
|
||||
earning: "Начислить сотруднику",
|
||||
adjustment_credit: "Положительная корректировка",
|
||||
adjustment_debit: "Отрицательная корректировка",
|
||||
deduction: "Удержать",
|
||||
payment: "Уже выплачено",
|
||||
};
|
||||
|
||||
const kindHelpItems: Array<{
|
||||
@ -708,7 +708,7 @@ function calculationHelps(pointPolicy: PayrollPeriodTable["calculation_rules"]["
|
||||
},
|
||||
deductions: {
|
||||
title: "Удержано",
|
||||
logic: "Сумма всех действующих строк с типом «Удержание» за выбранный период.",
|
||||
logic: "Сумма всех действующих строк с типом «Удержать» за выбранный период.",
|
||||
formula: "Удержано = Σ удержаний",
|
||||
manual: "Нет. Итог меняется через строки удержаний.",
|
||||
},
|
||||
@ -720,8 +720,8 @@ function calculationHelps(pointPolicy: PayrollPeriodTable["calculation_rules"]["
|
||||
},
|
||||
payments: {
|
||||
title: "Выплачено",
|
||||
logic: "Деньги, которые уже фактически выдали сотруднику в этом периоде. Сюда попадают «Аванс», «Фактическая выплата» и другие операции с типом «Выплата». Начисления, премии, корректировки и удержания сюда не входят.",
|
||||
formula: "Выплачено = Аванс + Фактическая выплата + Σ прочих операций типа «Выплата»",
|
||||
logic: "Деньги, которые уже фактически выдали сотруднику в этом периоде. Сюда попадают «Аванс», «Фактическая выплата» и другие операции с типом «Уже выплачено». Начисления, премии, корректировки и удержания сюда не входят.",
|
||||
formula: "Выплачено = Аванс + Фактическая выплата + Σ прочих операций типа «Уже выплачено»",
|
||||
manual: "Сам итог не редактируется. Введите сумму в ячейке «Аванс» или «Фактическая выплата» — она уменьшит остаток к выплате.",
|
||||
},
|
||||
payable: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user