ca81e9bc42
- 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.
111 lines
3.7 KiB
TypeScript
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),
|
|
}
|