RHF was caching 0/0 via useMemo on in-place form arrays; MoneyInput now drives Controller. Voucher list exposes source filters for manual vs automatic GL docs. Co-authored-by: Cursor <cursoragent@cursor.com>
1749 lines
53 KiB
TypeScript
1749 lines
53 KiB
TypeScript
/**
|
|
* Accounting Service API client — real backend only.
|
|
* Base: NEXT_PUBLIC_ACCOUNTING_API_URL (default http://localhost:8002)
|
|
* Always sends Authorization + X-Tenant-ID.
|
|
*/
|
|
|
|
import { getValidAccessToken, refreshSession } from "@/lib/auth";
|
|
|
|
const ACCOUNTING_BASE =
|
|
typeof window !== "undefined"
|
|
? "/api/accounting"
|
|
: process.env.ACCOUNTING_SERVICE_URL ||
|
|
process.env.NEXT_PUBLIC_ACCOUNTING_API_URL ||
|
|
"http://localhost:8002";
|
|
|
|
export class AccountingApiError extends Error {
|
|
constructor(
|
|
public status: number,
|
|
public code: string,
|
|
message: string
|
|
) {
|
|
super(message);
|
|
this.name = "AccountingApiError";
|
|
}
|
|
}
|
|
|
|
type RequestOptions = RequestInit & { tenantId: string; auth?: boolean };
|
|
|
|
async function request<T>(path: string, options: RequestOptions): Promise<T> {
|
|
const { tenantId, auth = true, headers: customHeaders, ...init } = options;
|
|
const headers = new Headers(customHeaders);
|
|
headers.set("Content-Type", "application/json");
|
|
headers.set("X-Tenant-ID", tenantId);
|
|
if (auth) {
|
|
// Never fall back to a raw stored token that may already be expired.
|
|
const token = await getValidAccessToken();
|
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
}
|
|
|
|
const url = path.startsWith("http")
|
|
? path
|
|
: `${ACCOUNTING_BASE.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
|
|
let res: Response;
|
|
try {
|
|
res = await fetch(url, { ...init, headers });
|
|
} catch (err) {
|
|
const detail = err instanceof Error ? err.message : "network_error";
|
|
throw new Error(
|
|
`اتصال به سرویس حسابداری برقرار نشد (${url}) — ${detail}`
|
|
);
|
|
}
|
|
|
|
if (res.status === 401 && auth) {
|
|
const refreshed = await refreshSession({ force: true });
|
|
if (refreshed) {
|
|
headers.set("Authorization", `Bearer ${refreshed}`);
|
|
res = await fetch(url, { ...init, headers });
|
|
}
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => ({}));
|
|
throw new AccountingApiError(
|
|
res.status,
|
|
body?.error?.code || "unknown_error",
|
|
body?.error?.message || res.statusText
|
|
);
|
|
}
|
|
if (res.status === 204) return undefined as T;
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
export type ChartOfAccounts = {
|
|
id: string;
|
|
tenant_id: string;
|
|
name: string;
|
|
code: string;
|
|
description: string | null;
|
|
is_default: boolean;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
};
|
|
|
|
export type Account = {
|
|
id: string;
|
|
tenant_id: string;
|
|
chart_id: string;
|
|
parent_id?: string | null;
|
|
code: string;
|
|
name: string;
|
|
account_type: string;
|
|
account_category: string;
|
|
level: number;
|
|
is_group: boolean;
|
|
is_postable: boolean;
|
|
status: string;
|
|
description?: string | null;
|
|
created_at: string;
|
|
};
|
|
|
|
export type Currency = {
|
|
id: string;
|
|
tenant_id: string;
|
|
code: string;
|
|
name: string;
|
|
symbol: string | null;
|
|
is_base: boolean;
|
|
is_active: boolean;
|
|
};
|
|
|
|
export type FiscalYear = {
|
|
id: string;
|
|
tenant_id: string;
|
|
name: string;
|
|
start_date: string;
|
|
end_date: string;
|
|
is_current: boolean;
|
|
is_closed: boolean;
|
|
};
|
|
|
|
export type FiscalPeriod = {
|
|
id: string;
|
|
tenant_id: string;
|
|
fiscal_year_id: string;
|
|
name: string;
|
|
period_number: number;
|
|
start_date: string;
|
|
end_date: string;
|
|
status: string;
|
|
is_current: boolean;
|
|
};
|
|
|
|
export type CostCenter = {
|
|
id: string;
|
|
tenant_id: string;
|
|
code: string;
|
|
name: string;
|
|
parent_id?: string | null;
|
|
is_active: boolean;
|
|
};
|
|
|
|
export type Project = {
|
|
id: string;
|
|
tenant_id: string;
|
|
code: string;
|
|
name: string;
|
|
start_date?: string | null;
|
|
end_date?: string | null;
|
|
is_active: boolean;
|
|
};
|
|
|
|
export type VoucherLine = {
|
|
id: string;
|
|
line_number: number;
|
|
account_id: string;
|
|
debit: string;
|
|
credit: string;
|
|
description: string | null;
|
|
cost_center_id?: string | null;
|
|
project_id?: string | null;
|
|
};
|
|
|
|
export type Voucher = {
|
|
id: string;
|
|
tenant_id: string;
|
|
fiscal_period_id: string;
|
|
voucher_number: string;
|
|
voucher_date: string;
|
|
status: string;
|
|
description: string | null;
|
|
total_debit: string;
|
|
total_credit: string;
|
|
posted_at: string | null;
|
|
source_module?: string | null;
|
|
reference_number?: string | null;
|
|
lines: VoucherLine[];
|
|
};
|
|
|
|
export type LedgerBalance = {
|
|
account_id: string;
|
|
fiscal_period_id: string;
|
|
opening_balance: string;
|
|
debit_total: string;
|
|
credit_total: string;
|
|
closing_balance: string;
|
|
current_balance: string;
|
|
};
|
|
|
|
export type SetupStatus = {
|
|
has_chart: boolean;
|
|
has_accounts: boolean;
|
|
has_currency: boolean;
|
|
has_base_currency: boolean;
|
|
has_fiscal_year: boolean;
|
|
has_fiscal_period: boolean;
|
|
has_cash_box: boolean;
|
|
has_customer: boolean;
|
|
has_voucher: boolean;
|
|
completed_steps: number;
|
|
total_steps: number;
|
|
percent: number;
|
|
};
|
|
|
|
export type CoaTemplate = {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
business_type: string;
|
|
accounts: {
|
|
code: string;
|
|
name: string;
|
|
account_type: string;
|
|
account_category: string;
|
|
level: number;
|
|
is_group: boolean;
|
|
is_postable: boolean;
|
|
parent_code?: string | null;
|
|
}[];
|
|
};
|
|
|
|
export type HealthResponse = {
|
|
status: string;
|
|
service: string;
|
|
version: string;
|
|
};
|
|
|
|
function qs(params?: Record<string, string | number | undefined | null>) {
|
|
if (!params) return "";
|
|
const sp = new URLSearchParams();
|
|
Object.entries(params).forEach(([k, v]) => {
|
|
if (v !== undefined && v !== null && v !== "") sp.set(k, String(v));
|
|
});
|
|
const s = sp.toString();
|
|
return s ? `?${s}` : "";
|
|
}
|
|
|
|
export const accountingApi = {
|
|
health: () =>
|
|
fetch(`${ACCOUNTING_BASE}/health`).then((r) => r.json() as Promise<HealthResponse>),
|
|
|
|
setup: {
|
|
status: (tenantId: string) =>
|
|
request<SetupStatus>("/api/v1/setup/status", { tenantId }),
|
|
peekNumber: (tenantId: string, documentType: string) =>
|
|
request<{
|
|
document_type: string;
|
|
prefix: string;
|
|
next_number: number;
|
|
padding: number;
|
|
preview: string;
|
|
}>(`/api/v1/setup/sequences/next${qs({ document_type: documentType })}`, { tenantId }),
|
|
allocateNumber: (tenantId: string, documentType: string) =>
|
|
request<{ document_type: string; number: string }>("/api/v1/setup/sequences/allocate", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify({ document_type: documentType }),
|
|
}),
|
|
getPostingPolicy: (tenantId: string) =>
|
|
request<{
|
|
auto_post: Record<string, boolean>;
|
|
require_balanced: boolean;
|
|
require_source_reference: boolean;
|
|
lock_after_post: boolean;
|
|
sequential_numbers: boolean;
|
|
idempotent_source: boolean;
|
|
posting_mode: string;
|
|
}>("/api/v1/setup/posting-policy", { tenantId }),
|
|
updatePostingPolicy: (
|
|
tenantId: string,
|
|
body: {
|
|
auto_post?: Record<string, boolean>;
|
|
require_balanced?: boolean;
|
|
require_source_reference?: boolean;
|
|
lock_after_post?: boolean;
|
|
sequential_numbers?: boolean;
|
|
idempotent_source?: boolean;
|
|
posting_mode?: string;
|
|
}
|
|
) =>
|
|
request<{
|
|
auto_post: Record<string, boolean>;
|
|
require_balanced: boolean;
|
|
require_source_reference: boolean;
|
|
lock_after_post: boolean;
|
|
sequential_numbers: boolean;
|
|
idempotent_source: boolean;
|
|
posting_mode: string;
|
|
}>("/api/v1/setup/posting-policy", {
|
|
tenantId,
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
charts: {
|
|
list: (tenantId: string, page = 1, pageSize = 50) =>
|
|
request<ChartOfAccounts[]>(`/api/v1/accounts/charts${qs({ page, page_size: pageSize })}`, {
|
|
tenantId,
|
|
}),
|
|
get: (tenantId: string, id: string) =>
|
|
request<ChartOfAccounts>(`/api/v1/accounts/charts/${id}`, { tenantId }),
|
|
create: (
|
|
tenantId: string,
|
|
body: { name: string; code: string; description?: string; is_default?: boolean }
|
|
) =>
|
|
request<ChartOfAccounts>("/api/v1/accounts/charts", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{ name: string; description: string; is_default: boolean; is_active: boolean }>
|
|
) =>
|
|
request<ChartOfAccounts>(`/api/v1/accounts/charts/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
archive: (tenantId: string, id: string) =>
|
|
request<ChartOfAccounts>(`/api/v1/accounts/charts/${id}/archive`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
|
|
templates: {
|
|
list: (tenantId: string) =>
|
|
request<CoaTemplate[]>("/api/v1/accounts/templates", { tenantId }),
|
|
get: (tenantId: string, id: string) =>
|
|
request<CoaTemplate>(`/api/v1/accounts/templates/${id}`, { tenantId }),
|
|
import: (
|
|
tenantId: string,
|
|
templateId: string,
|
|
body: { chart_id?: string; chart_name?: string; chart_code?: string }
|
|
) =>
|
|
request<Account[]>(`/api/v1/accounts/templates/${templateId}/import`, {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
accounts: {
|
|
list: (tenantId: string, page = 1, pageSize = 100, chartId?: string) =>
|
|
request<Account[]>(
|
|
`/api/v1/accounts${qs({ page, page_size: Math.min(pageSize, 500), chart_id: chartId })}`,
|
|
{ tenantId }
|
|
),
|
|
get: (tenantId: string, id: string) =>
|
|
request<Account>(`/api/v1/accounts/${id}`, { tenantId }),
|
|
create: (
|
|
tenantId: string,
|
|
body: {
|
|
chart_id: string;
|
|
code: string;
|
|
name: string;
|
|
account_type: string;
|
|
account_category: string;
|
|
level?: number;
|
|
is_group?: boolean;
|
|
is_postable?: boolean;
|
|
parent_id?: string | null;
|
|
description?: string;
|
|
}
|
|
) =>
|
|
request<Account>("/api/v1/accounts", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{
|
|
name: string;
|
|
account_type: string;
|
|
account_category: string;
|
|
level: number;
|
|
is_group: boolean;
|
|
is_postable: boolean;
|
|
parent_id: string | null;
|
|
description: string;
|
|
status: string;
|
|
}>
|
|
) =>
|
|
request<Account>(`/api/v1/accounts/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
archive: (tenantId: string, id: string) =>
|
|
request<Account>(`/api/v1/accounts/${id}/archive`, { tenantId, method: "POST" }),
|
|
},
|
|
|
|
currencies: {
|
|
list: (tenantId: string) =>
|
|
request<Currency[]>(`/api/v1/accounts/currencies${qs({ page_size: 100 })}`, { tenantId }),
|
|
create: (
|
|
tenantId: string,
|
|
body: { code: string; name: string; symbol?: string; decimal_places?: number; is_base?: boolean }
|
|
) =>
|
|
request<Currency>("/api/v1/accounts/currencies", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{ name: string; symbol: string; is_base: boolean; is_active: boolean }>
|
|
) =>
|
|
request<Currency>(`/api/v1/accounts/currencies/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
setBase: (tenantId: string, id: string) =>
|
|
request<Currency>(`/api/v1/accounts/currencies/${id}/set-base`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
archive: (tenantId: string, id: string) =>
|
|
request<Currency>(`/api/v1/accounts/currencies/${id}/archive`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
|
|
costCenters: {
|
|
list: (tenantId: string) =>
|
|
request<CostCenter[]>(`/api/v1/accounts/cost-centers${qs({ page_size: 100 })}`, { tenantId }),
|
|
create: (tenantId: string, body: { code: string; name: string; parent_id?: string | null }) =>
|
|
request<CostCenter>("/api/v1/accounts/cost-centers", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{ name: string; parent_id: string | null; is_active: boolean }>
|
|
) =>
|
|
request<CostCenter>(`/api/v1/accounts/cost-centers/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
archive: (tenantId: string, id: string) =>
|
|
request<CostCenter>(`/api/v1/accounts/cost-centers/${id}/archive`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
|
|
projects: {
|
|
list: (tenantId: string) =>
|
|
request<Project[]>(`/api/v1/accounts/projects${qs({ page_size: 100 })}`, { tenantId }),
|
|
create: (
|
|
tenantId: string,
|
|
body: { code: string; name: string; start_date?: string; end_date?: string }
|
|
) =>
|
|
request<Project>("/api/v1/accounts/projects", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{ name: string; start_date: string | null; end_date: string | null; is_active: boolean }>
|
|
) =>
|
|
request<Project>(`/api/v1/accounts/projects/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
archive: (tenantId: string, id: string) =>
|
|
request<Project>(`/api/v1/accounts/projects/${id}/archive`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
|
|
fiscal: {
|
|
listYears: (tenantId: string) =>
|
|
request<FiscalYear[]>("/api/v1/fiscal/years", { tenantId }),
|
|
createYear: (
|
|
tenantId: string,
|
|
body: { name: string; start_date: string; end_date: string; is_current?: boolean }
|
|
) =>
|
|
request<FiscalYear>("/api/v1/fiscal/years", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
updateYear: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{ name: string; start_date: string; end_date: string; is_current: boolean }>
|
|
) =>
|
|
request<FiscalYear>(`/api/v1/fiscal/years/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
setCurrentYear: (tenantId: string, id: string) =>
|
|
request<FiscalYear>(`/api/v1/fiscal/years/${id}/set-current`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
closeYear: (tenantId: string, id: string) =>
|
|
request<FiscalYear>(`/api/v1/fiscal/years/${id}/close`, { tenantId, method: "POST" }),
|
|
generatePeriods: (tenantId: string, id: string) =>
|
|
request<FiscalPeriod[]>(`/api/v1/fiscal/years/${id}/generate-periods`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
listPeriods: (tenantId: string) =>
|
|
request<FiscalPeriod[]>("/api/v1/fiscal/periods", { tenantId }),
|
|
createPeriod: (
|
|
tenantId: string,
|
|
body: {
|
|
fiscal_year_id: string;
|
|
name: string;
|
|
period_number: number;
|
|
start_date: string;
|
|
end_date: string;
|
|
is_current?: boolean;
|
|
}
|
|
) =>
|
|
request<FiscalPeriod>("/api/v1/fiscal/periods", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
updatePeriod: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: Partial<{ name: string; start_date: string; end_date: string; is_current: boolean; status: string }>
|
|
) =>
|
|
request<FiscalPeriod>(`/api/v1/fiscal/periods/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
setCurrentPeriod: (tenantId: string, id: string) =>
|
|
request<FiscalPeriod>(`/api/v1/fiscal/periods/${id}/set-current`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
lockPeriod: (tenantId: string, id: string) =>
|
|
request<FiscalPeriod>(`/api/v1/fiscal/periods/${id}/lock`, { tenantId, method: "POST" }),
|
|
closePeriod: (tenantId: string, id: string) =>
|
|
request<FiscalPeriod>(`/api/v1/fiscal/periods/${id}/close`, { tenantId, method: "POST" }),
|
|
reopenPeriod: (tenantId: string, id: string) =>
|
|
request<FiscalPeriod>(`/api/v1/fiscal/periods/${id}/reopen`, { tenantId, method: "POST" }),
|
|
},
|
|
|
|
vouchers: {
|
|
list: (
|
|
tenantId: string,
|
|
page = 1,
|
|
pageSize = 100,
|
|
opts?: { status?: string; source?: string }
|
|
) =>
|
|
request<Voucher[]>(
|
|
`/api/v1/posting/vouchers${qs({
|
|
page,
|
|
page_size: pageSize,
|
|
status: opts?.status || undefined,
|
|
source: opts?.source || undefined,
|
|
})}`,
|
|
{ tenantId }
|
|
),
|
|
get: (tenantId: string, id: string) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}`, { tenantId }),
|
|
create: (
|
|
tenantId: string,
|
|
body: {
|
|
fiscal_period_id: string;
|
|
voucher_number?: string;
|
|
voucher_date: string;
|
|
description?: string;
|
|
reference_number?: string;
|
|
source_module?: string;
|
|
lines: {
|
|
account_id: string;
|
|
debit: string;
|
|
credit: string;
|
|
description?: string;
|
|
cost_center_id?: string | null;
|
|
project_id?: string | null;
|
|
}[];
|
|
}
|
|
) =>
|
|
request<Voucher>("/api/v1/posting/vouchers", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: {
|
|
voucher_date?: string;
|
|
description?: string;
|
|
reference_number?: string;
|
|
lines?: {
|
|
account_id: string;
|
|
debit: string;
|
|
credit: string;
|
|
description?: string;
|
|
cost_center_id?: string | null;
|
|
project_id?: string | null;
|
|
}[];
|
|
}
|
|
) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
validate: (tenantId: string, id: string) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}/validate`, { tenantId, method: "POST" }),
|
|
post: (tenantId: string, id: string, source_module?: string) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}/post`, {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify({ source_module }),
|
|
}),
|
|
reverse: (tenantId: string, id: string) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}/reverse`, {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
}),
|
|
reverseAndEdit: (tenantId: string, id: string) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}/reverse-and-edit`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
cancel: (tenantId: string, id: string) =>
|
|
request<Voucher>(`/api/v1/posting/vouchers/${id}/cancel`, { tenantId, method: "POST" }),
|
|
auditLogs: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
operation: string;
|
|
actor_user_id: string;
|
|
voucher_number: string | null;
|
|
resource_type: string;
|
|
resource_id: string;
|
|
created_at: string;
|
|
}[]
|
|
>("/api/v1/posting/audit-logs", { tenantId }),
|
|
},
|
|
|
|
ledger: {
|
|
balances: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<LedgerBalance[]>(
|
|
`/api/v1/ledger/balances${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId }
|
|
),
|
|
trialBalance: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{
|
|
fiscal_period_id: string;
|
|
total_debit: string;
|
|
total_credit: string;
|
|
difference: string;
|
|
is_balanced: boolean;
|
|
}>(`/api/v1/ledger/trial-balance${qs({ fiscal_period_id: fiscalPeriodId })}`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
recalculate: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ recalculated_accounts: number }>(
|
|
`/api/v1/ledger/recalculate${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
},
|
|
|
|
reporting: {
|
|
trialBalance: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ id: string; report_type: string; data: unknown }>(
|
|
`/api/v1/reporting/trial-balance${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
balanceSheet: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ id: string; data: unknown }>(
|
|
`/api/v1/reporting/balance-sheet${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
incomeStatement: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ id: string; data: unknown }>(
|
|
`/api/v1/reporting/income-statement${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
subsidiaryLedger: (tenantId: string, fiscalPeriodId: string, accountId?: string) =>
|
|
request<{ id: string; report_type: string; data: unknown }>(
|
|
`/api/v1/reporting/subsidiary-ledger${qs({
|
|
fiscal_period_id: fiscalPeriodId,
|
|
account_id: accountId,
|
|
})}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
detailLedger: (tenantId: string, fiscalPeriodId: string, accountId?: string) =>
|
|
request<{ id: string; report_type: string; data: unknown }>(
|
|
`/api/v1/reporting/detail-ledger${qs({
|
|
fiscal_period_id: fiscalPeriodId,
|
|
account_id: accountId,
|
|
})}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
analytical: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ id: string; report_type: string; data: unknown }>(
|
|
`/api/v1/reporting/analytical${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
cashFlow: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ id: string; report_type: string; data: unknown }>(
|
|
`/api/v1/reporting/cash-flow${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
equityStatement: (tenantId: string, fiscalPeriodId: string) =>
|
|
request<{ id: string; report_type: string; data: unknown }>(
|
|
`/api/v1/reporting/equity-statement${qs({ fiscal_period_id: fiscalPeriodId })}`,
|
|
{ tenantId, method: "POST" }
|
|
),
|
|
export: (tenantId: string, reportId: string, export_format: "json" | "csv" | "pdf" = "json") =>
|
|
request<{ id: string; format: string; file_path: string | null }>(
|
|
`/api/v1/reporting/reports/${reportId}/export`,
|
|
{ tenantId, method: "POST", body: JSON.stringify({ export_format }) }
|
|
),
|
|
},
|
|
|
|
treasury: {
|
|
listCashBoxes: (tenantId: string) =>
|
|
request<
|
|
{ id: string; tenant_id: string; code: string; name: string; current_balance: string; is_active: boolean }[]
|
|
>("/api/v1/treasury/cash-boxes", { tenantId }),
|
|
createCashBox: (
|
|
tenantId: string,
|
|
body: { code: string; name: string; opening_balance?: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/treasury/cash-boxes", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
updateCashBox: (tenantId: string, id: string, body: { name?: string; is_active?: boolean }) =>
|
|
request<{ id: string }>(`/api/v1/treasury/cash-boxes/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listBanks: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string; is_active: boolean }[]>(
|
|
"/api/v1/treasury/banks",
|
|
{ tenantId }
|
|
),
|
|
createBank: (tenantId: string, body: { code: string; name: string }) =>
|
|
request<{ id: string }>("/api/v1/treasury/banks", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listBankAccounts: (tenantId: string) =>
|
|
request<
|
|
{ id: string; bank_id: string; account_number: string; iban: string | null; is_active: boolean }[]
|
|
>("/api/v1/treasury/bank-accounts", { tenantId }),
|
|
createBankAccount: (
|
|
tenantId: string,
|
|
body: { bank_id: string; account_number: string; iban?: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/treasury/bank-accounts", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
cashReceipt: (
|
|
tenantId: string,
|
|
body: {
|
|
cash_box_id: string;
|
|
amount: string;
|
|
debit_account_id: string;
|
|
credit_account_id: string;
|
|
description?: string;
|
|
transaction_date?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; voucher_id: string }>("/api/v1/treasury/cash-receipts", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listCheques: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
cheque_number: string;
|
|
amount: string;
|
|
issue_date: string;
|
|
due_date: string | null;
|
|
status: string;
|
|
is_incoming: boolean;
|
|
payee: string | null;
|
|
}[]
|
|
>("/api/v1/treasury/cheques", { tenantId }),
|
|
createCheque: (
|
|
tenantId: string,
|
|
body: {
|
|
cheque_number: string;
|
|
amount: string;
|
|
issue_date: string;
|
|
due_date?: string;
|
|
is_incoming?: boolean;
|
|
payee?: string;
|
|
bank_account_id?: string;
|
|
status?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; cheque_number: string }>("/api/v1/treasury/cheques", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listTransfers: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
transfer_date: string;
|
|
amount: string;
|
|
from_type: string;
|
|
from_id: string;
|
|
to_type: string;
|
|
to_id: string;
|
|
description: string | null;
|
|
}[]
|
|
>("/api/v1/treasury/transfers", { tenantId }),
|
|
createTransfer: (
|
|
tenantId: string,
|
|
body: {
|
|
transfer_date: string;
|
|
amount: string;
|
|
from_type: string;
|
|
from_id: string;
|
|
to_type: string;
|
|
to_id: string;
|
|
description?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/treasury/transfers", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listReceipts: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
receipt_number: string;
|
|
receipt_date: string;
|
|
amount: string;
|
|
payment_method: string;
|
|
description: string | null;
|
|
voucher_id?: string | null;
|
|
}[]
|
|
>("/api/v1/treasury/receipts", { tenantId }),
|
|
createReceipt: (
|
|
tenantId: string,
|
|
body: {
|
|
receipt_number: string;
|
|
receipt_date: string;
|
|
amount: string;
|
|
payment_method?: string;
|
|
cash_box_id?: string;
|
|
bank_account_id?: string;
|
|
counter_account_id?: string;
|
|
description?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; receipt_number?: string; voucher_id?: string | null; voucher_number?: string | null }>(
|
|
"/api/v1/treasury/receipts",
|
|
{
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}
|
|
),
|
|
listPayments: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
payment_number: string;
|
|
payment_date: string;
|
|
amount: string;
|
|
payment_method: string;
|
|
description: string | null;
|
|
voucher_id?: string | null;
|
|
}[]
|
|
>("/api/v1/treasury/payments", { tenantId }),
|
|
createPayment: (
|
|
tenantId: string,
|
|
body: {
|
|
payment_number: string;
|
|
payment_date: string;
|
|
amount: string;
|
|
payment_method?: string;
|
|
cash_box_id?: string;
|
|
bank_account_id?: string;
|
|
counter_account_id?: string;
|
|
description?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; payment_number?: string; voucher_id?: string | null; voucher_number?: string | null }>(
|
|
"/api/v1/treasury/payments",
|
|
{
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}
|
|
),
|
|
listReconciliations: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
bank_account_id: string;
|
|
statement_date: string;
|
|
statement_balance: string;
|
|
book_balance: string;
|
|
difference: string;
|
|
status: string;
|
|
}[]
|
|
>("/api/v1/treasury/reconciliations", { tenantId }),
|
|
createReconciliation: (
|
|
tenantId: string,
|
|
body: {
|
|
bank_account_id: string;
|
|
statement_date: string;
|
|
statement_balance: string;
|
|
book_balance: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; difference: string }>("/api/v1/treasury/reconciliations", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
ops: {
|
|
listDocuments: (tenantId: string, module?: string, docType?: string) => {
|
|
const q = new URLSearchParams();
|
|
if (module) q.set("module", module);
|
|
if (docType) q.set("doc_type", docType);
|
|
const qs = q.toString();
|
|
return request<
|
|
{
|
|
id: string;
|
|
tenant_id: string;
|
|
module: string;
|
|
doc_type: string;
|
|
number: string;
|
|
doc_date: string;
|
|
party_name: string | null;
|
|
amount: string;
|
|
status: string;
|
|
description: string | null;
|
|
}[]
|
|
>(`/api/v1/ops/documents${qs ? `?${qs}` : ""}`, { tenantId });
|
|
},
|
|
createDocument: (
|
|
tenantId: string,
|
|
body: {
|
|
module: string;
|
|
doc_type: string;
|
|
number?: string;
|
|
doc_date: string;
|
|
party_name?: string;
|
|
party_id?: string;
|
|
amount?: string;
|
|
status?: string;
|
|
description?: string;
|
|
meta_json?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/ops/documents", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
updateDocument: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: {
|
|
party_name?: string;
|
|
party_id?: string;
|
|
amount?: string;
|
|
status?: string;
|
|
description?: string;
|
|
doc_date?: string;
|
|
meta_json?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; status: string }>(`/api/v1/ops/documents/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
confirmDocument: (tenantId: string, id: string) =>
|
|
request<{ id: string; status: string; meta_json?: string | null }>(
|
|
`/api/v1/ops/documents/${id}/confirm`,
|
|
{
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
}
|
|
),
|
|
listItems: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
item_type: string;
|
|
unit: string;
|
|
quantity_on_hand: string;
|
|
unit_cost: string;
|
|
is_active: boolean;
|
|
}[]
|
|
>("/api/v1/ops/items", { tenantId }),
|
|
createItem: (
|
|
tenantId: string,
|
|
body: {
|
|
code: string;
|
|
name: string;
|
|
item_type?: string;
|
|
unit?: string;
|
|
quantity_on_hand?: string;
|
|
unit_cost?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/ops/items", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listWarehouses: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string; is_active: boolean }[]>(
|
|
"/api/v1/ops/warehouses",
|
|
{ tenantId }
|
|
),
|
|
createWarehouse: (tenantId: string, body: { code: string; name: string }) =>
|
|
request<{ id: string }>("/api/v1/ops/warehouses", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
customers: {
|
|
list: (tenantId: string) =>
|
|
request<
|
|
{ id: string; tenant_id: string; code: string; name: string; outstanding_balance: string; is_active: boolean }[]
|
|
>("/api/v1/ar-ap/customers", { tenantId }),
|
|
get: (tenantId: string, id: string) =>
|
|
request<{
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
outstanding_balance: string;
|
|
is_active: boolean;
|
|
}>(`/api/v1/ar-ap/customers/${id}`, { tenantId }),
|
|
create: (tenantId: string, body: { code?: string; name: string; credit_limit?: string }) =>
|
|
request<{ id: string; code: string; name: string }>("/api/v1/ar-ap/customers", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
update: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: { name?: string; credit_limit?: string | null; is_active?: boolean }
|
|
) =>
|
|
request<{ id: string }>(`/api/v1/ar-ap/customers/${id}`, {
|
|
tenantId,
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
aging: (tenantId: string, id: string) =>
|
|
request<{
|
|
current: string;
|
|
days_1_30: string;
|
|
days_31_60: string;
|
|
days_61_90: string;
|
|
days_90_plus: string;
|
|
total: string;
|
|
}>(`/api/v1/ar-ap/customers/${id}/aging`, { tenantId }),
|
|
listInvoices: (tenantId: string, customerId?: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
customer_id: string;
|
|
invoice_number: string;
|
|
invoice_date: string;
|
|
due_date: string | null;
|
|
total_amount: string;
|
|
remaining_amount: string;
|
|
status: string;
|
|
}[]
|
|
>(`/api/v1/ar-ap/receivable-invoices${qs({ customer_id: customerId })}`, { tenantId }),
|
|
createInvoice: (
|
|
tenantId: string,
|
|
body: {
|
|
customer_id: string;
|
|
invoice_number?: string;
|
|
invoice_date: string;
|
|
due_date?: string;
|
|
total_amount: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/ar-ap/receivable-invoices", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
suppliers: {
|
|
list: (tenantId: string) =>
|
|
request<
|
|
{ id: string; code: string; name: string; outstanding_balance: string; is_active: boolean }[]
|
|
>("/api/v1/ar-ap/suppliers", { tenantId }),
|
|
create: (tenantId: string, body: { code?: string; name: string }) =>
|
|
request<{ id: string; code: string; name: string }>("/api/v1/ar-ap/suppliers", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
assets: {
|
|
list: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string; status: string }[]>("/api/v1/assets/assets", {
|
|
tenantId,
|
|
}),
|
|
listCategories: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string; default_useful_life_months: number }[]>(
|
|
"/api/v1/assets/categories",
|
|
{ tenantId }
|
|
),
|
|
createCategory: (
|
|
tenantId: string,
|
|
body: { code: string; name: string; default_useful_life_months?: number }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/assets/categories", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
create: (
|
|
tenantId: string,
|
|
body: {
|
|
code: string;
|
|
name: string;
|
|
category_id?: string;
|
|
acquisition_cost?: string;
|
|
residual_value?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; code: string }>("/api/v1/assets/assets", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
activate: (tenantId: string, id: string) =>
|
|
request<{ id: string; status: string }>(`/api/v1/assets/assets/${id}/activate`, {
|
|
tenantId,
|
|
method: "POST",
|
|
}),
|
|
depreciate: (
|
|
tenantId: string,
|
|
id: string,
|
|
body: { expense_account_id: string; accumulated_account_id: string }
|
|
) =>
|
|
request<{ id: string; amount: string; voucher_id: string }>(
|
|
`/api/v1/assets/assets/${id}/depreciate`,
|
|
{ tenantId, method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
schedule: (tenantId: string, id: string) =>
|
|
request<{ schedule: unknown[] }>(`/api/v1/assets/assets/${id}/depreciation-schedule`, {
|
|
tenantId,
|
|
}),
|
|
listTransfers: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
asset_id: string;
|
|
transfer_date: string;
|
|
from_location: string | null;
|
|
to_location: string | null;
|
|
}[]
|
|
>("/api/v1/assets/transfers", { tenantId }),
|
|
createTransfer: (
|
|
tenantId: string,
|
|
body: { asset_id: string; transfer_date: string; from_location?: string; to_location?: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/assets/transfers", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listDisposals: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
asset_id: string;
|
|
disposal_date: string;
|
|
disposal_type: string;
|
|
sale_amount: string | null;
|
|
gain_loss: string | null;
|
|
}[]
|
|
>("/api/v1/assets/disposals", { tenantId }),
|
|
createDisposal: (
|
|
tenantId: string,
|
|
body: { asset_id: string; disposal_date: string; disposal_type?: string; sale_amount?: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/assets/disposals", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listRevaluations: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
asset_id: string;
|
|
revaluation_date: string;
|
|
old_book_value: string;
|
|
new_book_value: string;
|
|
difference: string;
|
|
}[]
|
|
>("/api/v1/assets/revaluations", { tenantId }),
|
|
createRevaluation: (
|
|
tenantId: string,
|
|
body: {
|
|
asset_id: string;
|
|
revaluation_date: string;
|
|
old_book_value: string;
|
|
new_book_value: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; difference: string }>("/api/v1/assets/revaluations", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
payroll: {
|
|
listEmployees: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string }[]>("/api/v1/payroll/employees", {
|
|
tenantId,
|
|
}),
|
|
createEmployee: (
|
|
tenantId: string,
|
|
body: {
|
|
employee_code: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
base_salary?: string;
|
|
department_id?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; employee_code: string }>("/api/v1/payroll/employees", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listDepartments: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string }[]>("/api/v1/payroll/departments", {
|
|
tenantId,
|
|
}),
|
|
createDepartment: (tenantId: string, body: { code: string; name: string }) =>
|
|
request<{ id: string }>("/api/v1/payroll/departments", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listPeriods: (tenantId: string) =>
|
|
request<{ id: string; name: string; start_date: string; end_date: string; status: string }[]>(
|
|
"/api/v1/payroll/periods",
|
|
{ tenantId }
|
|
),
|
|
createPeriod: (
|
|
tenantId: string,
|
|
body: { name: string; start_date: string; end_date: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/payroll/periods", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
calculate: (tenantId: string, body: { payroll_period_id: string; employee_id: string }) =>
|
|
request<{ id: string; gross_salary: string; net_salary: string; status: string }>(
|
|
"/api/v1/payroll/calculate",
|
|
{ tenantId, method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
post: (tenantId: string, payrollId: string) =>
|
|
request<{ id: string; status: string; voucher_id: string }>(
|
|
`/api/v1/payroll/payrolls/${payrollId}/post`,
|
|
{ tenantId, method: "POST", body: JSON.stringify({}) }
|
|
),
|
|
listContracts: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
employee_id: string;
|
|
contract_type: string;
|
|
start_date: string;
|
|
end_date: string | null;
|
|
base_salary: string;
|
|
is_active: boolean;
|
|
}[]
|
|
>("/api/v1/payroll/contracts", { tenantId }),
|
|
createContract: (
|
|
tenantId: string,
|
|
body: {
|
|
employee_id: string;
|
|
contract_type?: string;
|
|
start_date: string;
|
|
end_date?: string;
|
|
base_salary?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/payroll/contracts", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listSalaryComponents: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
component_type: string;
|
|
is_taxable: boolean;
|
|
is_active: boolean;
|
|
}[]
|
|
>("/api/v1/payroll/salary-components", { tenantId }),
|
|
createSalaryComponent: (
|
|
tenantId: string,
|
|
body: { code: string; name: string; component_type?: string; is_taxable?: boolean }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/payroll/salary-components", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listPayrolls: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
payroll_period_id: string;
|
|
employee_id: string;
|
|
gross_salary: string;
|
|
net_salary: string;
|
|
status: string;
|
|
voucher_id: string | null;
|
|
}[]
|
|
>("/api/v1/payroll/payrolls", { tenantId }),
|
|
},
|
|
|
|
compliance: {
|
|
listAudit: (tenantId: string) =>
|
|
request<
|
|
{ id: string; action: string; resource_type: string; actor_user_id: string; created_at: string }[]
|
|
>("/api/v1/compliance/audit-records", { tenantId }),
|
|
listPolicies: (tenantId: string) =>
|
|
request<{ id: string; code: string; name: string; policy_type: string; is_active: boolean }[]>(
|
|
"/api/v1/compliance/policies",
|
|
{ tenantId }
|
|
),
|
|
createPolicy: (
|
|
tenantId: string,
|
|
body: { code: string; name: string; policy_type: string; rules_config: string; country_code?: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/compliance/policies", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
requestApproval: (
|
|
tenantId: string,
|
|
body: { workflow_id: string; resource_type: string; resource_id: string }
|
|
) =>
|
|
request<{ id: string; status: string }>("/api/v1/compliance/approvals", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
approve: (tenantId: string, requestId: string) =>
|
|
request<{ id: string; status: string }>(`/api/v1/compliance/approvals/${requestId}/approve`, {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
}),
|
|
reject: (tenantId: string, requestId: string, reason: string) =>
|
|
request<{ id: string; status: string }>(`/api/v1/compliance/approvals/${requestId}/reject`, {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify({ reason }),
|
|
}),
|
|
listRisks: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
code: string;
|
|
title: string;
|
|
risk_category: string;
|
|
risk_level: string;
|
|
risk_score: number;
|
|
is_resolved: boolean;
|
|
}[]
|
|
>("/api/v1/compliance/risks", { tenantId }),
|
|
createRisk: (
|
|
tenantId: string,
|
|
body: {
|
|
code: string;
|
|
title: string;
|
|
risk_category: string;
|
|
risk_level: string;
|
|
risk_score?: number;
|
|
description?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; risk_level: string }>("/api/v1/compliance/risks", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listWorkflows: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
name: string;
|
|
resource_type: string;
|
|
min_amount: string | null;
|
|
max_amount: string | null;
|
|
is_active: boolean;
|
|
}[]
|
|
>("/api/v1/compliance/workflows", { tenantId }),
|
|
createWorkflow: (
|
|
tenantId: string,
|
|
body: { name: string; resource_type: string; min_amount?: string; max_amount?: string }
|
|
) =>
|
|
request<{ id: string }>("/api/v1/compliance/workflows", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listApprovals: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
workflow_id: string;
|
|
resource_type: string;
|
|
resource_id: string;
|
|
requested_by: string;
|
|
status: string;
|
|
current_step: number;
|
|
rejection_reason: string | null;
|
|
}[]
|
|
>("/api/v1/compliance/approvals", { tenantId }),
|
|
listGovernanceRules: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
rule_type: string;
|
|
condition_config: string;
|
|
priority: number;
|
|
is_active: boolean;
|
|
}[]
|
|
>("/api/v1/compliance/governance-rules", { tenantId }),
|
|
createGovernanceRule: (
|
|
tenantId: string,
|
|
body: {
|
|
code: string;
|
|
name: string;
|
|
rule_type?: string;
|
|
condition_config: string;
|
|
action_config?: string;
|
|
priority?: number;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/compliance/governance-rules", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listControls: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
control_type: string;
|
|
description: string | null;
|
|
is_active: boolean;
|
|
}[]
|
|
>("/api/v1/compliance/controls", { tenantId }),
|
|
createControl: (
|
|
tenantId: string,
|
|
body: {
|
|
code: string;
|
|
name: string;
|
|
control_type: string;
|
|
description?: string;
|
|
validation_config?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/compliance/controls", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
listViolations: (tenantId: string) =>
|
|
request<
|
|
{
|
|
id: string;
|
|
policy_id: string;
|
|
resource_type: string;
|
|
resource_id: string;
|
|
severity: string;
|
|
description: string;
|
|
detected_at: string;
|
|
is_resolved: boolean;
|
|
}[]
|
|
>("/api/v1/compliance/violations", { tenantId }),
|
|
createViolation: (
|
|
tenantId: string,
|
|
body: {
|
|
policy_id: string;
|
|
resource_type: string;
|
|
resource_id: string;
|
|
severity: string;
|
|
description: string;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/compliance/violations", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
settlements: {
|
|
list: (tenantId: string, partyType?: string) => {
|
|
const q = partyType ? `?party_type=${encodeURIComponent(partyType)}` : "";
|
|
return request<
|
|
{
|
|
id: string;
|
|
settlement_number: string;
|
|
settlement_date: string;
|
|
settlement_type: string;
|
|
party_type: string;
|
|
party_id: string;
|
|
total_amount: string;
|
|
status: string;
|
|
}[]
|
|
>(`/api/v1/ar-ap/settlements${q}`, { tenantId });
|
|
},
|
|
create: (
|
|
tenantId: string,
|
|
body: {
|
|
settlement_number: string;
|
|
settlement_date: string;
|
|
settlement_type: string;
|
|
party_type: string;
|
|
party_id: string;
|
|
total_amount: string;
|
|
allocations: { invoice_id?: string; amount: string }[];
|
|
}
|
|
) =>
|
|
request<{ id: string; status: string }>("/api/v1/ar-ap/settlements", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
|
|
salesAccounting: {
|
|
listProfiles: (tenantId: string) =>
|
|
request<{ id: string; name: string; document_type: string; is_default: boolean; is_active: boolean }[]>(
|
|
"/api/v1/sales-accounting/posting-profiles",
|
|
{ tenantId }
|
|
),
|
|
createProfile: (
|
|
tenantId: string,
|
|
body: {
|
|
name: string;
|
|
document_type: string;
|
|
revenue_account_id?: string;
|
|
receivable_account_id?: string;
|
|
cash_account_id?: string;
|
|
discount_account_id?: string;
|
|
tax_account_id?: string;
|
|
is_default?: boolean;
|
|
}
|
|
) =>
|
|
request<{ id: string }>("/api/v1/sales-accounting/posting-profiles", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
preview: (
|
|
tenantId: string,
|
|
body: {
|
|
document_type?: string;
|
|
total_amount: string;
|
|
discount_amount?: string;
|
|
tax_amount?: string;
|
|
profile_id?: string;
|
|
}
|
|
) =>
|
|
request<{
|
|
is_balanced: boolean;
|
|
total_debit: string;
|
|
total_credit: string;
|
|
preview_data: unknown;
|
|
}>("/api/v1/sales-accounting/preview", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
post: (
|
|
tenantId: string,
|
|
body: {
|
|
document_type?: string;
|
|
source_document_id: string;
|
|
total_amount: string;
|
|
discount_amount?: string;
|
|
tax_amount?: string;
|
|
profile_id?: string;
|
|
}
|
|
) =>
|
|
request<{ voucher_id: string; voucher_number: string; status: string }>(
|
|
"/api/v1/sales-accounting/post",
|
|
{ tenantId, method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
},
|
|
|
|
purchaseInventory: {
|
|
listProfiles: (tenantId: string) =>
|
|
request<{ id: string; name: string; is_default: boolean; is_active: boolean }[]>(
|
|
"/api/v1/purchase-inventory/posting-profiles",
|
|
{ tenantId }
|
|
),
|
|
createProfile: (
|
|
tenantId: string,
|
|
body: {
|
|
name: string;
|
|
inventory_account_id?: string;
|
|
grni_account_id?: string;
|
|
liability_account_id?: string;
|
|
is_default?: boolean;
|
|
}
|
|
) =>
|
|
request<{ id: string; name: string }>("/api/v1/purchase-inventory/posting-profiles", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
previewGoodsReceipt: (
|
|
tenantId: string,
|
|
body: { source_document_id: string; amount: string; profile_id?: string }
|
|
) =>
|
|
request<{ is_balanced: boolean; total_debit: string }>(
|
|
"/api/v1/purchase-inventory/preview/goods-receipt",
|
|
{ tenantId, method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
postGoodsReceipt: (
|
|
tenantId: string,
|
|
body: { source_document_id: string; amount: string; profile_id?: string }
|
|
) =>
|
|
request<{ voucher_id: string; voucher_number?: string; status: string }>(
|
|
"/api/v1/purchase-inventory/post/goods-receipt",
|
|
{
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}
|
|
),
|
|
previewPurchaseInvoice: (
|
|
tenantId: string,
|
|
body: { source_document_id: string; amount: string; profile_id?: string }
|
|
) =>
|
|
request<{ is_balanced: boolean; total_debit: string; total_credit?: string }>(
|
|
"/api/v1/purchase-inventory/preview/purchase-invoice",
|
|
{ tenantId, method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
postPurchaseInvoice: (
|
|
tenantId: string,
|
|
body: { source_document_id: string; amount: string; profile_id?: string }
|
|
) =>
|
|
request<{ voucher_id: string; voucher_number?: string; status: string }>(
|
|
"/api/v1/purchase-inventory/post/purchase-invoice",
|
|
{
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}
|
|
),
|
|
postPurchaseReturn: (
|
|
tenantId: string,
|
|
body: { source_document_id: string; amount: string; profile_id?: string }
|
|
) =>
|
|
request<{ voucher_id: string; voucher_number?: string; status: string }>(
|
|
"/api/v1/purchase-inventory/post/purchase-return",
|
|
{
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}
|
|
),
|
|
valuation: (
|
|
tenantId: string,
|
|
body: {
|
|
item_id: string;
|
|
quantity: string;
|
|
unit_cost: string;
|
|
method?: string;
|
|
warehouse_id?: string;
|
|
}
|
|
) =>
|
|
request<{ id: string; total_value: string }>("/api/v1/purchase-inventory/valuation", {
|
|
tenantId,
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
},
|
|
};
|