Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
/**
|
|
* Jalali (Shamsi) date helpers for Design System.
|
|
* Form/API values stay Gregorian ISO `YYYY-MM-DD`; UI is always Jalali.
|
|
*/
|
|
import { toJalaali, toGregorian, jalaaliMonthLength as jMonthLength } from "jalaali-js";
|
|
|
|
export const WEEKDAYS = ["ش", "ی", "د", "س", "چ", "پ", "ج"] as const;
|
|
|
|
export const MONTHS = [
|
|
"فروردین",
|
|
"اردیبهشت",
|
|
"خرداد",
|
|
"تیر",
|
|
"مرداد",
|
|
"شهریور",
|
|
"مهر",
|
|
"آبان",
|
|
"آذر",
|
|
"دی",
|
|
"بهمن",
|
|
"اسفند",
|
|
] as const;
|
|
|
|
export type JalaliParts = { year: number; month: number; day: number };
|
|
|
|
function pad(n: number) {
|
|
return String(n).padStart(2, "0");
|
|
}
|
|
|
|
function parseIso(iso: string): { gy: number; gm: number; gd: number } | null {
|
|
const m = iso.trim().match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
if (!m) return null;
|
|
return { gy: Number(m[1]), gm: Number(m[2]), gd: Number(m[3]) };
|
|
}
|
|
|
|
/** Today as Gregorian ISO date for API defaults. */
|
|
export function todayIso(): string {
|
|
const now = new Date();
|
|
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
}
|
|
|
|
export function isoToJalaliParts(iso?: string | null): JalaliParts {
|
|
const parsed = iso ? parseIso(iso) : null;
|
|
const d = parsed
|
|
? new Date(parsed.gy, parsed.gm - 1, parsed.gd)
|
|
: new Date();
|
|
const j = toJalaali(d.getFullYear(), d.getMonth() + 1, d.getDate());
|
|
return { year: j.jy, month: j.jm, day: j.jd };
|
|
}
|
|
|
|
export function jalaliPartsToIso(parts: JalaliParts): string {
|
|
const g = toGregorian(parts.year, parts.month, parts.day);
|
|
return `${g.gy}-${pad(g.gm)}-${pad(g.gd)}`;
|
|
}
|
|
|
|
/** Format Gregorian ISO as Jalali `YYYY/MM/DD`. */
|
|
export function formatJalali(value?: string | Date | null): string {
|
|
if (value === null || value === undefined || value === "") return "—";
|
|
let gy: number;
|
|
let gm: number;
|
|
let gd: number;
|
|
if (value instanceof Date) {
|
|
gy = value.getFullYear();
|
|
gm = value.getMonth() + 1;
|
|
gd = value.getDate();
|
|
} else {
|
|
const parsed = parseIso(String(value));
|
|
if (parsed) {
|
|
gy = parsed.gy;
|
|
gm = parsed.gm;
|
|
gd = parsed.gd;
|
|
} else {
|
|
const d = new Date(String(value));
|
|
if (Number.isNaN(d.getTime())) return String(value);
|
|
gy = d.getFullYear();
|
|
gm = d.getMonth() + 1;
|
|
gd = d.getDate();
|
|
}
|
|
}
|
|
const j = toJalaali(gy, gm, gd);
|
|
return `${j.jy}/${pad(j.jm)}/${pad(j.jd)}`;
|
|
}
|
|
|
|
/** Jalali date + local time for audit timestamps. */
|
|
export function formatJalaliDateTime(value?: string | Date | null): string {
|
|
if (value === null || value === undefined || value === "") return "—";
|
|
const d = value instanceof Date ? value : new Date(String(value));
|
|
if (Number.isNaN(d.getTime())) return String(value);
|
|
return `${formatJalali(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
}
|
|
|
|
export function jalaliMonthLength(year: number, month: number): number {
|
|
return jMonthLength(year, month);
|
|
}
|
|
|
|
/** Weekday index Sat=0 … Fri=6 for Jalali calendar grid. */
|
|
export function jalaliWeekdaySat0(year: number, month: number, day: number): number {
|
|
const g = toGregorian(year, month, day);
|
|
const jsDay = new Date(g.gy, g.gm - 1, g.gd).getDay(); // 0=Sun
|
|
return (jsDay + 1) % 7;
|
|
}
|
|
|
|
export function shiftJalaliMonth(
|
|
year: number,
|
|
month: number,
|
|
delta: number
|
|
): { year: number; month: number } {
|
|
let m = month + delta;
|
|
let y = year;
|
|
while (m < 1) {
|
|
m += 12;
|
|
y -= 1;
|
|
}
|
|
while (m > 12) {
|
|
m -= 12;
|
|
y += 1;
|
|
}
|
|
return { year: y, month: m };
|
|
}
|