Lead Radar: исходный код

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuriandClaude Opus 5.5 committed 2026-10-25 23:11:04 +04:00
commit 9749da5ae7
201 files changed
+97685

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
import { z } from 'zod';
import { CURRENCIES } from './filters.ts';
// Вознаграждение агента. Владелец компании задаёт каждому: оклад, процент с комиссии по его
// сделкам или оклад + процент. По этим условиям считается, кому сколько платить за период.
export const AgentPay = z.object({
/** fixed — оклад; percent — процент со сделок; mixed — оклад + процент. */
mode: z.enum(['fixed', 'percent', 'mixed']),
/** Оклад в месяц. */
fixed: z.number().min(0).max(100_000_000).default(0),
/** Процент от комиссии агентства по сделкам продажи (и аренды, если для неё не задан свой). */
percent: z.number().min(0).max(100).default(0),
/** Процент по сделкам аренды; null — тот же, что для продажи. */
percentRent: z.number().min(0).max(100).nullable().default(null),
/** Валюта оклада и итоговой выплаты. */
currency: z.enum(CURRENCIES).default('USD'),
/** План на месяц: число сделок и сумма комиссии агента в валюте условий; null — показатель не задан. */
planDeals: z.number().int().min(1).max(10_000).nullable().default(null),
planCommission: z.number().positive().max(100_000_000).nullable().default(null),
/** Бонус за выполненный план: fixed — сумма; percent — надбавка к проценту на всю комиссию агента за месяц. */
bonusMode: z.enum(['none', 'fixed', 'percent']).default('none'),
bonusValue: z.number().min(0).max(100_000_000).default(0),
});
export type AgentPay = z.infer<typeof AgentPay>;
/** Сколько месяцев в периоде: полный календарный месяц — 1, половина — 0,5. Даты — «ГГГГ-ММ-ДД», включительно. */
export function monthsBetween(from: string, to: string): number {
const day = (s: string) => new Date(`${s}T00:00:00Z`);
const end = day(to);
let total = 0;
for (let cursor = day(from); cursor <= end; ) {
const y = cursor.getUTCFullYear();
const m = cursor.getUTCMonth();
const daysInMonth = new Date(Date.UTC(y, m + 1, 0)).getUTCDate();
const last = new Date(Math.min(Date.UTC(y, m, daysInMonth), end.getTime()));
total += (last.getUTCDate() - cursor.getUTCDate() + 1) / daysInMonth;
cursor = new Date(Date.UTC(y, m + 1, 1));
}
return Math.round(total * 100) / 100;
}
/** Вид сделки: от него зависит процент агента. */
export const DEAL_KINDS = ['sale', 'rent'] as const;
export type DealKind = (typeof DEAL_KINDS)[number];
/** Процент агента по сделке данного вида; 0 — процента в условиях нет. */
export function payPercent(pay: AgentPay | null | undefined, kind: DealKind): number {
if (!pay || pay.mode === 'fixed') return 0;
return kind === 'rent' ? (pay.percentRent ?? pay.percent) : pay.percent;
}
/**
* Выплата агенту за период в валюте его условий: оклад за число месяцев и процент с его части
* комиссии по сделкам продажи и аренды (`commission` — уже в валюте условий).
*/
export function agentPayout(pay: AgentPay | null | undefined, commission: Record<DealKind, number>, months: number, deals = 0) {
const round = (n: number) => Math.round(n * 100) / 100;
const fixed = pay && pay.mode !== 'percent' ? round(pay.fixed * months) : 0;
const shareSale = round((commission.sale * payPercent(pay, 'sale')) / 100);
const shareRent = round((commission.rent * payPercent(pay, 'rent')) / 100);
const share = round(shareSale + shareRent);
const all = commission.sale + commission.rent;
const planDone = planIsDone(pay, deals, all, months);
const bonus = !pay || !planDone || pay.bonusMode === 'none' ? 0 : round(pay.bonusMode === 'fixed' ? pay.bonusValue : (all * pay.bonusValue) / 100);
return { fixed, shareSale, shareRent, share, planDone, bonus, total: round(fixed + share + bonus) };
}
/** План выполнен, когда достигнуты все заданные показатели; null — плана нет. За период из нескольких месяцев план кратно больше. */
export function planIsDone(pay: AgentPay | null | undefined, deals: number, commission: number, months: number): boolean | null {
if (!pay || (pay.planDeals == null && pay.planCommission == null)) return null;
const dealsOk = pay.planDeals == null || deals >= pay.planDeals * months;
const commissionOk = pay.planCommission == null || commission >= pay.planCommission * months;
return dealsOk && commissionOk;
}