feat: Accounting web module — COA, journals, expenses, accounts, reports + nav (FIBEROPS-234-239)

- Chart of Accounts (FIBEROPS-234): type-filtered table, add/seed/toggle-active
- Journal Entries (FIBEROPS-235): double-entry form with debit=credit validation, read-only view modal
- Expenses (FIBEROPS-236): date-range filtered table, record expense against expense accounts
- Company Accounts + Transfers (FIBEROPS-237): balance cards, add account modal, transfer modal, transfers table
- Financial Reports (FIBEROPS-238): Trial Balance, P&L, Balance Sheet, Cash Flow with date pickers
- Sidebar nav entry (FIBEROPS-239): Accounting link with BookOpen icon, placed between Reports and Settings
- AccountingNav horizontal sub-nav component shared across all accounting pages
- New types: Account, JournalEntry, Expense, CompanyAccount, Transfer and report types added to src/types/index.ts
This commit is contained in:
2026-03-31 23:35:29 +00:00
parent 89585ab645
commit b719d87e1b
8 changed files with 1597 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import { BookOpen, BookText, Receipt, Building2, BarChart3 } from "lucide-react";
const NAV_ITEMS = [
{ href: "/accounting", label: "Chart of Accounts", icon: BookOpen, exact: true },
{ href: "/accounting/journal-entries", label: "Journal Entries", icon: BookText },
{ href: "/accounting/expenses", label: "Expenses", icon: Receipt },
{ href: "/accounting/company-accounts", label: "Company Accounts", icon: Building2 },
{ href: "/accounting/reports", label: "Reports", icon: BarChart3 },
];
export function AccountingNav() {
const pathname = usePathname();
return (
<nav className="flex gap-1 border-b border-gray-200 mb-6 overflow-x-auto">
{NAV_ITEMS.map(({ href, label, icon: Icon, exact }) => {
const isActive = exact ? pathname === href : pathname === href || pathname.startsWith(href + "/");
return (
<Link
key={href}
href={href}
className={cn(
"flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 -mb-px whitespace-nowrap transition-colors",
isActive
? "border-blue-600 text-blue-700"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
)}
>
<Icon className="h-4 w-4" />
{label}
</Link>
);
})}
</nav>
);
}

View File

@@ -17,6 +17,7 @@ import {
Wifi,
Briefcase,
BarChart2,
BookOpen,
X,
} from "lucide-react";
@@ -32,6 +33,7 @@ const navItems = [
{ href: "/users", label: "Users", icon: UserCog },
{ href: "/audit-log", label: "Audit Log", icon: ClipboardList },
{ href: "/reports", label: "Reports", icon: BarChart2 },
{ href: "/accounting", label: "Accounting", icon: BookOpen },
{ href: "/settings", label: "Settings", icon: Settings },
];

View File

@@ -211,6 +211,110 @@ export interface LegacyPaginatedResponse<T> {
limit: number;
}
// ─── Accounting ────────────────────────────────────────────────────────────────
export type AccountType = 'ASSET' | 'LIABILITY' | 'EQUITY' | 'REVENUE' | 'EXPENSE';
export interface Account {
id: string;
code: string;
name: string;
type: AccountType;
isActive: boolean;
balance?: number;
createdAt: string;
}
export interface JournalEntryLine {
id?: string;
accountId: string;
account?: Account;
debit: number;
credit: number;
memo?: string;
}
export interface JournalEntry {
id: string;
date: string;
reference?: string;
description: string;
sourceType?: string;
lines: JournalEntryLine[];
totalDebit?: number;
createdAt: string;
}
export interface Expense {
id: string;
date: string;
vendor?: string;
accountId: string;
account?: Account;
amount: number;
description?: string;
createdAt: string;
}
export type CompanyAccountType = 'CASH' | 'BANK' | 'EWALLET';
export interface CompanyAccount {
id: string;
name: string;
type: CompanyAccountType;
bankName?: string;
accountNumber?: string;
balance: number;
createdAt: string;
}
export interface Transfer {
id: string;
fromAccountId: string;
fromAccount?: CompanyAccount;
toAccountId: string;
toAccount?: CompanyAccount;
amount: number;
date: string;
note?: string;
createdAt: string;
}
export interface TrialBalanceLine {
code: string;
name: string;
debit: number;
credit: number;
balance: number;
}
export interface ProfitLossReport {
revenue: Array<{ name: string; amount: number }>;
expenses: Array<{ name: string; amount: number }>;
totalRevenue: number;
totalExpenses: number;
netIncome: number;
}
export interface BalanceSheetReport {
assets: Array<{ name: string; amount: number }>;
liabilities: Array<{ name: string; amount: number }>;
equity: Array<{ name: string; amount: number }>;
totalAssets: number;
totalLiabilities: number;
totalEquity: number;
}
export interface CashFlowReport {
operating: Array<{ name: string; amount: number }>;
investing: Array<{ name: string; amount: number }>;
financing: Array<{ name: string; amount: number }>;
netOperating: number;
netInvesting: number;
netFinancing: number;
netCashFlow: number;
}
export interface Lead {
id: string;
firstName: string;