@@ -1063,10 +1180,10 @@ function PayrollComponentSettingsModal({
- {pointBaseAssessment.negative.length ? (
+ {!isScheduled && pointBaseAssessment.negative.length ? (
- ) : pointBaseAssessment.zero.length ? (
+ ) : !isScheduled && pointBaseAssessment.zero.length ? (
0}
+ disabled={
+ pending ||
+ (!isScheduled && pointBaseAssessment.negative.length > 0)
+ }
>
{pending ? (
@@ -1435,7 +1555,27 @@ function calculationHelps(
function componentCalculationHelp(
component: PayrollPeriodTableComponent,
+ components: PayrollPeriodTableComponent[] = [],
+ periods: PayrollAdminPeriod[] = [],
): CalculationHelp {
+ if (component.kind === "scheduled_change") {
+ const target = components.find(
+ (item) => item.code === component.scheduled_target_component_code,
+ );
+ const applicationPeriod = periods.find(
+ (item) => item.id === component.scheduled_application_period_id,
+ );
+ const targetLabel = target?.label || "выбранная колонка";
+ const periodLabel =
+ applicationPeriod?.name || applicationPeriod?.code || "выбранный период";
+ return {
+ title: component.label,
+ logic: `Это внутренний маркер. В текущем периоде он не меняет расчёт и не показывается сотруднику. В периоде «${periodLabel}» сумма один раз прибавится к колонке «${targetLabel}»; отрицательная сумма уменьшит её. Несколько маркеров складываются.`,
+ formula: `Текущий период: ${component.label} → без влияния\n\n${periodLabel}:\n${targetLabel} = обычное значение ${targetLabel} + Σ «${component.label}»\n\nПри штатном переносе в следующие периоды переносится уже полученное значение ${targetLabel}, сам маркер не переносится`,
+ manual:
+ "Да. В ячейке маркера задаётся итоговая знаковая сумма для сотрудника. После применения в целевой ячейке отображается отметка с источником изменения.",
+ };
+ }
const flow = {
earning: {
effect:
@@ -1462,6 +1602,10 @@ function componentCalculationHelp(
"увеличивает «Выплачено» и тем самым уменьшает остаток «К выплате»; на начисленную зарплату не влияет",
formula: `${component.label} → Выплачено (+) → К выплате (−)`,
},
+ scheduled_change: {
+ effect: "не влияет на текущий период",
+ formula: `${component.label} → без влияния`,
+ },
}[component.kind];
const decreasesCarryover = ["earning", "adjustment_credit"].includes(
component.kind,
@@ -1713,6 +1857,7 @@ function InlineEditableCell({
commentCount,
onOpenComment,
cellClassName = "",
+ annotation,
}: {
value: string | null;
formattedValue: string;
@@ -1722,6 +1867,7 @@ function InlineEditableCell({
commentCount: number;
onOpenComment: () => void;
cellClassName?: string;
+ annotation?: { label: string; title: string };
}) {
const original = editableNumber(value);
const [draft, setDraft] = useState(original);
@@ -1729,10 +1875,6 @@ function InlineEditableCell({
const [cellError, setCellError] = useState(null);
const cancelledRef = useRef(false);
- useEffect(() => {
- if (!pending) setDraft(editableNumber(value));
- }, [pending, value]);
-
if (!onSave)
return (
- {formattedValue}
+ {formattedValue}
+ {annotation ? (
+
+ {annotation.label}
+
+ ) : null}
);
@@ -1795,6 +1945,14 @@ function InlineEditableCell({
}
}}
/>
+ {annotation ? (
+
+ {annotation.label}
+
+ ) : null}
{pending ? (
{
@@ -2477,8 +2641,27 @@ export function PayrollPeriodTableView({
const component = componentsByColumnId.get(column.id);
const cellVisualClass = columnVisualClass(column, "body");
if (component) {
+ const scheduledChanges = (row.applied_scheduled_changes || []).filter(
+ (item) => item.target_component_code === component.code,
+ );
+ const scheduledDelta = scheduledChanges.reduce(
+ (sum, item) => sum + Number(item.amount),
+ 0,
+ );
+ const annotation = scheduledChanges.length
+ ? {
+ label: `изм. ${scheduledDelta > 0 ? "+" : ""}${scheduledDelta}`,
+ title: scheduledChanges
+ .map(
+ (item) =>
+ `${item.source_component_name} (${item.source_period_code}): ${Number(item.amount) > 0 ? "+" : ""}${item.amount}`,
+ )
+ .join("\n"),
+ }
+ : undefined;
return (
);
@@ -2499,6 +2683,7 @@ export function PayrollPeriodTableView({
case columnIds.rateAmount:
return (
editingComponent && onUpdateComponent
diff --git a/frontend/src/lib/api/finance.ts b/frontend/src/lib/api/finance.ts
index 9c3c03be..25857e00 100644
--- a/frontend/src/lib/api/finance.ts
+++ b/frontend/src/lib/api/finance.ts
@@ -5,7 +5,8 @@ export type PayrollLineKind =
| "adjustment_credit"
| "adjustment_debit"
| "deduction"
- | "payment";
+ | "payment"
+ | "scheduled_change";
export type PayrollComponentPeriodScope =
| "current_only"
@@ -400,6 +401,8 @@ export type PayrollPeriodTableComponent = {
scope_period_id: number | null;
is_custom: boolean;
highlight_color: PayrollColumnHighlightColor;
+ scheduled_target_component_code: string;
+ scheduled_application_period_id: number | null;
};
export type PayrollCellCommentAuthor = {
@@ -459,6 +462,14 @@ export type PayrollPeriodTableRow = {
point_delta: string | null;
point_amount: string | null;
component_amounts: Record;
+ applied_scheduled_changes: Array<{
+ target_component_code: string;
+ amount: string;
+ source_period_id: number;
+ source_period_code: string;
+ source_component_code: string;
+ source_component_name: string;
+ }>;
totals_preliminary: boolean;
gross_before_adjustments: string | null;
adjustment_total: string | null;
@@ -625,6 +636,8 @@ export type PayrollComponent = {
visible_to_employee: boolean;
is_custom: boolean;
highlight_color: PayrollColumnHighlightColor;
+ scheduled_target_component_code: string;
+ scheduled_application_period_id: number | null;
};
export type PayrollColumnHighlightColor =
@@ -644,6 +657,8 @@ export type PayrollCustomComponentWrite = {
visible_to_employee: boolean;
highlight_color: PayrollColumnHighlightColor;
period_scope: PayrollComponentPeriodScope;
+ scheduled_target_component_code: string;
+ scheduled_application_period_id: number | null;
};
export type PayrollAdminPayRate = {