Files
ERP_Distribution/distribution_fe/lib/api/finance-reports.ts
T
HarithaRandunu ca81e9bc42 feat: add General Ledger integration and financial reporting features
- Implemented General Ledger client component for managing GL integration.
- Created a dedicated page for General Ledger configuration.
- Developed Finance Report client component for generating various financial reports.
- Added report blocks for structured display of financial data.
- Introduced API endpoints for fetching GL accounts and financial reports.
- Updated navigation to include links for GL integration and financial reports.
- Enhanced permissions to manage GL and view financial reports.
- Updated appsettings to include General Ledger API configuration.
2026-10-20 18:54:30 +05:30

111 lines
3.7 KiB
TypeScript

import { api } from '@/lib/api'
export type FinanceReportType =
| 'TrialBalance'
| 'GeneralLedger'
| 'ProfitAndLoss'
| 'BalanceSheet'
| 'CashFlow'
| 'BudgetVsActual'
| 'TaxSummary'
export interface FinanceReportParams {
asOfDate?: string
periodStart?: string
periodEnd?: string
accountCode?: string
budgetId?: number
}
export type FileFormat = 'Pdf' | 'Csv'
const cleanParams = (params: FinanceReportParams) =>
Object.fromEntries(Object.entries(params).filter(([, v]) => v !== undefined && v !== ''))
// Reports come from the General Ledger service through the Distribution backend (/reports/finance/{type}).
export interface GlAccountOption {
accountCode: string
accountName: string
}
export const financeReports = {
/** Active GL accounts for the account dropdown. */
accounts: () => api.get<GlAccountOption[]>('/reports/finance/accounts').then(r => r.data),
get: (type: FinanceReportType, params: FinanceReportParams) =>
api.get<unknown>(`/reports/finance/${type}`, { params: cleanParams(params) }).then(r => r.data),
/** Downloads the GL-rendered PDF or CSV and hands it to the browser. */
async download(type: FinanceReportType, params: FinanceReportParams, format: FileFormat) {
const res = await api.get<Blob>(`/reports/finance/${type}`, {
params: { ...cleanParams(params), outputFormat: format },
responseType: 'blob',
})
const disposition = String(res.headers['content-disposition'] ?? '')
const name = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition)?.[1] ?? `${type}.${format.toLowerCase()}`
const url = URL.createObjectURL(res.data)
const a = document.createElement('a')
a.href = url
a.download = decodeURIComponent(name)
a.click()
URL.revokeObjectURL(url)
},
}
export interface GlStatus {
glReachable: boolean
setupDone: boolean
openingBalancePosted: boolean
closedYears: number[]
mapping: Record<string, string | null>
posted: number
failed: number
}
export interface GlPostingRow {
id: string
event: string
sourceId: string
reference: string
status: 'Pending' | 'Posted' | 'Failed'
glJournalNo: string | null
error: string | null
attempts: number
lastAttemptAt: string | null
createdAt: string
}
export interface OpeningBalanceLine {
/** A key such as 'Inventory' or 'ShareCapital', or 'BankAccount' together with bankAccountId. */
account: string
bankAccountId?: string
/** The account's normal balance (>= 0). Debit/credit side is decided by the backend. */
amount: number
}
export interface YearEndPreview {
year: number
startDate: string
endDate: string
totalIncome: number
totalExpenses: number
netProfit: number
closable: boolean
reason: string | null
alreadyClosed: boolean
}
export const generalLedger = {
yearEndPreview: (year: number) =>
api.get<YearEndPreview>('/gl/year-end/preview', { params: { year } }).then(r => r.data),
closeYear: (year: number) =>
api.post<{ year: number; glJournalNo: string | null; netProfit: number; periodsLocked: number }>('/gl/year-end/close', { year }).then(r => r.data),
postOpeningBalances: (date: string, narration: string, lines: OpeningBalanceLine[]) =>
api.post<{ status: string; glJournalNo: string | null }>('/gl/opening-balances', { date, narration, lines }).then(r => r.data),
status: () => api.get<GlStatus>('/gl/status').then(r => r.data),
setup: (year?: number) => api.post<{ done: boolean; actions: string[] }>('/gl/setup', { year }).then(r => r.data),
postings: (status?: GlPostingRow['status']) =>
api.get<GlPostingRow[]>('/gl/postings', { params: { status } }).then(r => r.data),
retry: () => api.post<{ posted: number }>('/gl/postings/retry').then(r => r.data),
}