TorbatYar/frontend/lib/accounting-api.ts
Mortezakoohjani 7d66932da8 Complete Accounting sidebar modules with ops CRUD, BFF proxy, and treasury APIs.
Wire nested nav pages for phases 5.1-5.11 to real list/create endpoints, fix COA import connectivity via same-origin proxy, and add operational migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 16:10:06 +03:30

1146 lines
35 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 { getStoredToken, 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) {
const token = (await getValidAccessToken()) || getStoredToken();
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();
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;
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 }),
},
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 = 50) =>
request<Voucher[]>(`/api/v1/posting/vouchers${qs({ page, page_size: pageSize })}`, {
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({}),
}),
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" }
),
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;
}[]
>("/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;
description?: string;
}
) =>
request<{ id: string }>("/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;
}[]
>("/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;
description?: string;
}
) =>
request<{ id: string }>("/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),
}),
confirmDocument: (tenantId: string, id: string) =>
request<{ id: string; status: string }>(`/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,
}),
},
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({}) }
),
},
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),
}),
},
};