From 0cb8ad3d11ff893b2f55e76b2023c3cf8cbfe615 Mon Sep 17 00:00:00 2001 From: ilia Date: Sat, 9 May 2026 12:07:54 -0400 Subject: [PATCH] Document ledger reprints, optional dueDate, Limmud FSU invoice data Add --print to regenerate PDFs from invoices.json without ledger writes. Support optional dueDate on ledger rows (default remains invoice date + 14 days) and persist dueDate when saving new production rows. Data: limmud_fsu_canada client with invoiceFileName, cruise_event_av_it product, 2026-LFSU01 sample invoice, Levkin contact fields in sender.json. README and project.md describe CLI, schema, and May 2026 changelog. Co-authored-by: Cursor --- README.md | 170 ++++++++++++++---------- data/client.json | 22 +++ data/invoices.json | 69 ++++++++++ data/products.json | 5 + data/sender.json | 5 +- index.js | 104 ++++++++++++++- project.md | 324 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 626 insertions(+), 73 deletions(-) create mode 100644 project.md diff --git a/README.md b/README.md index 119838e..3c319b9 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,150 @@ # Invoice Generator -A powerful and flexible command-line tool for generating PDF invoices from simple JSON data files. It features an interactive mode for ease of use and a robust, non-interactive mode for scripting and automation. +A command-line tool for generating professional PDF invoices from JSON data files. Features interactive and non-interactive modes with preview-first workflow. -## Features +## Quick Start -- **Interactive & Non-Interactive Modes**: Use the simple interactive wizard (`npm start`) or pass arguments directly for scripted use. -- **Safe Preview-First Workflow**: Always generates a development preview first. You must confirm before the official invoice is created and recorded, preventing accidental invoice generation. -- **Data-Driven**: Senders, clients, and products are managed in simple `.json` files, making them easy to update without changing code. -- **Automatic Invoice Numbering**: Invoice numbers are automatically incremented based on the year and existing records. The sequence resets annually. -- **Organized Output**: Generated PDFs are neatly stored in folders organized by sender (`invoice//`). -- **Unit Tested**: Core logic is verified with unit tests using Jest. +1. **Install dependencies:** -## Prerequisites + ```bash + npm install + ``` -- [Node.js](https://nodejs.org/) (v16 or higher recommended) -- [npm](https://www.npmjs.com/) (comes with Node.js) +2. **Generate an invoice:** + ```bash + npm start + ``` -## Setup +## Usage Options -1. Clone the repository or download the source code. -2. Open your terminal in the project directory. -3. Install the dependencies: - ```bash - npm install - ``` - -## Usage - -There are two primary ways to generate an invoice: - -### 1. Interactive Mode (Recommended for most users) - -Run the interactive wizard. The tool will guide you through selecting a sender, client, and one or more products. +### Interactive Mode (Recommended) ```bash npm start ``` -The script will first generate a **development preview** PDF. After you review it, you will be prompted to confirm whether you want to create the final **production** invoice. +Guided wizard to select sender, client, and products. -### 2. Non-Interactive / Scripted Mode - -You can also generate an invoice by passing command-line arguments. This is useful for automation and scripting. - -The basic command is: -`npm run generate -- --sender --client --products ` - -**Example:** +### Non-Interactive Mode ```bash -npm run generate -- --sender company1 --client company4 --products linkedin_scraper web_design +npm run generate -- --sender levkin --client levitin --products linkedin_scraper web_design ``` -Like the interactive mode, this will also generate a preview and prompt for confirmation before creating the production invoice. - -### Running Tests - -To run the automated tests for the project: +### Test Mode ```bash -npm test +npm run test-invoice ``` -## How the Data Files Work +Uses first available options for testing. -All data is managed in the `data/` directory. +### Reprint an existing invoice (from ledger) -### `sender.json` +Generates a PDF from `data/invoices.json` only — **does not** change the ledger or run the preview prompt: -Stores your company or sender profiles. Each key represents a unique sender. +```bash +node index.js --print 2026-LFSU01 +``` + +Output path: `invoice//_.pdf`. The filename prefix is the client’s optional `invoiceFileName` in `client.json`, otherwise the client key. + +**Due date on the PDF:** Each ledger row can include optional `"dueDate": "YYYY-MM-DD"`. If omitted, the tool uses invoice `date` plus 14 days. + +All data is stored in the `data/` directory: + +### `data/sender.json` - Your Company Profiles ```json { - "company1": { "company": "Your Company", "address": ... }, - "company2": { "company": "Another Profile", "address": ... } + "levkin": { + "company": "Levkin Inc.", + "contactName": "Ilia Dobkin", + "phone": "647 987 2792", + "email": "idobkin@gmail.com", + "address": "6 Keefer Court", + "zip": "L4J 5Y4", + "city": "Thornhill, ON", + "country": "Canada", + "taxName": "HST", + "taxNumber": "79438 2895 RT0001", + "paymentInfo": "Bank transfer details..." + } } ``` -### `client.json` - -Stores your client profiles. The `invoiceFileName` key is optional; if provided, it will be used for the generated PDF's filename. +### `data/client.json` - Client Information ```json { - "company3": { "company": "First Client", "address": ... }, - "company4": { "company": "Second Client", "invoiceFileName": "second_client_invoices", "address": ... } + "levitin": { + "company": "LEVITIN EMPLOYMENT LAWYERS", + "address": "#2809 - 330 Richmond Street West", + "zip": "M5V 0M4", + "city": "Toronto, ON", + "country": "Canada" + } } ``` -### `products.json` - -A catalog of the services or products you offer. +### `data/products.json` - Services/Products Catalog ```json { "linkedin_scraper": { "description": "LinkedIn Scraper Bot", "price": 250, - "taxRate": 0 + "taxRate": 13 }, - "web_design": { "description": "Website Design", "price": 1200, "taxRate": 0 } + "web_design": { + "description": "Website Design", + "price": 1200, + "taxRate": 13 + } } ``` -### `invoices.json` +## Workflow -This file is your ledger. It's **automatically updated** when you create a production invoice. It tracks all generated invoices to determine the next sequential number. You should not need to edit this file manually. +1. **Preview Generation**: Creates a development preview PDF first +2. **Review**: Check the preview in `invoice//_-DEV-PREVIEW.pdf` +3. **Confirmation**: Choose to create the production invoice or keep only the preview +4. **Production**: Generates final invoice with sequential numbering and saves to ledger + +## Output Structure -```json -{ - "invoices": [ - { - "number": "2024-0001", - "date": "2024-07-31", - ... - } - ] -} ``` +invoice/ +├── levkin/ +│ ├── levitin_2025-0007.pdf # Production invoice +│ └── levitin_2025-DEV-PREVIEW.pdf # Preview +└── dobit/ + └── ... +``` + +## Testing + +```bash +npm test +``` + +Tests cover invoice numbering logic including year resets and sequential ordering. + +## Key Features + +- **Safe Preview-First**: Always generates preview before production +- **Automatic Numbering**: Sequential invoice numbers (YYYY-0001, YYYY-0002...) +- **Year Reset**: Invoice numbers reset annually +- **Organized Output**: PDFs stored by sender in separate folders +- **Tax Support**: Configurable tax rates per product +- **Payment Info**: Custom payment instructions in footer +- **Ledger Tracking**: All invoices automatically recorded in `data/invoices.json` +- **Reprint mode** (`--print`): PDF from ledger without editing it +- **Optional `dueDate`** on ledger rows for custom payment deadlines when reprinting + +See [project.md](project.md) for full schema, CLI details, and changelog. + +## Requirements + +- Node.js v16+ +- npm diff --git a/data/client.json b/data/client.json index af3064e..62e7352 100644 --- a/data/client.json +++ b/data/client.json @@ -12,5 +12,27 @@ "zip": "M5V 0M4", "city": "Toronto, ON", "country": "Canada" + }, + "jrcc": { + "company": "JRCC - Jewish Russian Community Centre", + "address": "5987 Bathurst St #3", + "zip": "M2R 1Z3", + "city": "North York, ON", + "country": "Canada" + }, + "innosphere": { + "company": "Innosphere SDG Ltd.", + "address": "49 Norfolk Street - Suite 300", + "zip": "N1H 4J1", + "city": "Guelph, Ontario", + "country": "Canada" + }, + "limmud_fsu_canada": { + "company": "Limmud FSU Canada", + "address": "9600 Bathurst St", + "zip": "L6A 3Z8", + "city": "Maple, Ontario", + "country": "Canada", + "invoiceFileName": "limmud_fsuc" } } diff --git a/data/invoices.json b/data/invoices.json index 426ad9b..c097e40 100644 --- a/data/invoices.json +++ b/data/invoices.json @@ -118,6 +118,75 @@ "subtotal": 250, "taxTotal": 32.5, "total": 282.5 + }, + { + "number": "2026-JRCC10", + "date": "2026-04-30", + "sender": "Levkin Inc.", + "client": "JRCC - Jewish Russian Community Centre", + "products": [ + { + "description": "Design and development of PunimTag Web: full-stack facial recognition photo management platform (FastAPI backend, React frontend) with DeepFace-based face detection and identification, PostgreSQL/SQLite data storage, Redis/RQ background processing, advanced search and tagging, and deployment-ready configuration and documentation.", + "price": 10000, + "quantity": 1, + "taxRate": 13 + } + ], + "subtotal": 10000, + "taxTotal": 1300, + "total": 11300 + }, + { + "number": "2026-INNO16", + "date": "2026-03-31", + "sender": "Levkin Inc.", + "client": "Innosphere SDG Ltd.", + "products": [ + { + "description": "Javascript Developer - Mar 1 - Mar 31, 2026", + "price": 85, + "quantity": 136, + "taxRate": 13 + } + ], + "subtotal": 11560, + "taxTotal": 1502.8, + "total": 13062.8 + }, + { + "number": "2026-INNO17", + "date": "2026-04-30", + "sender": "Levkin Inc.", + "client": "Innosphere SDG Ltd.", + "products": [ + { + "description": "Javascript Developer - Apr 1 - Apr 30, 2026", + "price": 85, + "quantity": 176, + "taxRate": 13 + } + ], + "subtotal": 14960, + "taxTotal": 1944.8, + "total": 16904.8 + }, + { + "number": "2026-LFSU01", + "date": "2026-03-22", + "dueDate": "2026-05-31", + "sender": "Levkin Inc.", + "client": "Limmud FSU Canada", + "products": [ + { + "description": "March 15–22, 2026 — Cruise event IT — audio/video and stage tech: microphones, speakers, video projection and signage, presenter feeds, cabling and rack setup, sound checks and on-site troubleshooting for onboard Limmud FSU programming.", + "price": 500, + "quantity": 1, + "taxRate": 13 + } + ], + "subtotal": 500, + "taxTotal": 65, + "total": 565 } ] } \ No newline at end of file diff --git a/data/products.json b/data/products.json index c74c971..0bedb68 100644 --- a/data/products.json +++ b/data/products.json @@ -8,5 +8,10 @@ "description": "Website Design", "price": 1200, "taxRate": 13 + }, + "cruise_event_av_it": { + "description": "March 15–22, 2026 — Cruise event IT — audio/video and stage tech: microphones, mixing, amplifiers and speakers, video projection and signage, presenter feeds, cabling and rack setup, sound checks and on-site troubleshooting for onboard Limmud FSU programming.", + "price": 500, + "taxRate": 13 } } diff --git a/data/sender.json b/data/sender.json index bc9663b..47b8227 100644 --- a/data/sender.json +++ b/data/sender.json @@ -1,13 +1,16 @@ { "levkin": { "company": "Levkin Inc.", + "contactName": "Ilia Dobkin", + "phone": "647 987 2792", + "email": "idobkin@gmail.com", "address": "6 Keefer Court", "zip": "L4J 5Y4", "city": "Thornhill, ON", "country": "Canada", "taxName": "HST", "taxNumber": "79438 2895 RT0001", - "paymentInfo": "
All payments to be made by bank transfer to the following account:

- Levkin Inc. - Ilia Dobkin
- 6 Keefer Court, ON, L4J 5Y4
- Account: 5004295
- Transit: 31822
- 2600 Simcoe St. N. Unit 1, Oshawa, ON, L1L 0R1 - Inst: 004
- SWIFT Code: TDOMCATTTOR
" + "paymentInfo": "
All payments to be made by bank transfer to the following account:

- Levkin Inc. - Ilia Dobkin
- 6 Keefer Court, ON, L4J 5Y4
- Account: 5004295
- Transit: 31822
- Inst: 004
- 2600 Simcoe St. N. Unit 1, Oshawa, ON, L1L 0R1
- SWIFT Code: TDOMCATTTOR
" }, "dobit": { "company": "DOBIT Consulting", diff --git a/index.js b/index.js index 7dd34e2..42e0dfe 100644 --- a/index.js +++ b/index.js @@ -78,7 +78,7 @@ function saveInvoiceRecord(invoiceData, invoiceProducts) { return sum + (p.price * p.quantity * taxRate) / 100; }, 0); - invoicesData.invoices.push({ + const ledgerRow = { number: invoiceData.information.number, date: invoiceData.information.date, sender: invoiceData.sender.company, @@ -92,7 +92,11 @@ function saveInvoiceRecord(invoiceData, invoiceProducts) { subtotal: parseFloat(subtotal.toFixed(2)), taxTotal: parseFloat(taxTotal.toFixed(2)), total: parseFloat((subtotal + taxTotal).toFixed(2)), - }); + }; + if (invoiceData.information.dueDate) { + ledgerRow.dueDate = invoiceData.information.dueDate; + } + invoicesData.invoices.push(ledgerRow); try { fs.writeFileSync(FILES.INVOICES, JSON.stringify(invoicesData, null, 2)); console.log("✅ Invoice record saved to invoices.json."); @@ -108,6 +112,11 @@ async function main() { type: "boolean", describe: "Run in interactive mode", }) + .option("print", { + type: "string", + describe: + "Generate a PDF for an existing invoice number (from data/invoices.json) without modifying the ledger", + }) .option("sender", { type: "string", describe: "Sender key from sender.json", @@ -132,6 +141,97 @@ async function main() { const clients = loadJson(FILES.CLIENTS); const productsData = loadJson(FILES.PRODUCTS); + // --- Print existing invoice (no prompts, no ledger update) --- + if (argv.print) { + const invoiceNumber = argv.print; + const invoicesData = loadJson(FILES.INVOICES); + const record = invoicesData.invoices.find((inv) => inv.number === invoiceNumber); + if (!record) { + console.error(`Invoice not found in invoices.json: ${invoiceNumber}`); + process.exit(1); + } + + const resolvedSenderKey = + Object.keys(senders).find((k) => senders[k]?.company === record.sender) || null; + const resolvedClientKey = + Object.keys(clients).find((k) => clients[k]?.company === record.client) || null; + + if (!resolvedSenderKey) { + console.error( + `Could not match sender company "${record.sender}" to a key in sender.json.` + ); + process.exit(1); + } + if (!resolvedClientKey) { + console.error( + `Could not match client company "${record.client}" to a key in client.json.` + ); + process.exit(1); + } + + const sender = senders[resolvedSenderKey]; + const client = clients[resolvedClientKey]; + + const invoiceProducts = (record.products || []).map((p) => ({ + quantity: p.quantity ?? 1, + description: p.description, + "tax-rate": p.taxRate ?? 0, + price: p.price, + })); + if (invoiceProducts.length === 0) { + console.error(`Invoice ${invoiceNumber} has no products.`); + process.exit(1); + } + + const senderForInvoice = { ...sender }; + if (sender.taxNumber) { + senderForInvoice.custom1 = `${sender.taxName || "Tax"} Number: ${ + sender.taxNumber + }`; + } + + let dueDateStr; + if (record.dueDate) { + dueDateStr = record.dueDate; + } else { + const due = new Date(record.date); + due.setDate(due.getDate() + 14); + dueDateStr = due.toISOString().split("T")[0]; + } + + const commonData = { + sender: senderForInvoice, + client, + products: invoiceProducts, + bottomNotice: sender.paymentInfo + ? `Thank you for your business!


${sender.paymentInfo}` + : "Thank you for your business!", + settings: { currency: "CAD" }, + translate: { + taxNotation: sender.taxName || "VAT", + number: "Invoice", + }, + }; + + const printData = { + ...commonData, + information: { + number: record.number, + date: record.date, + dueDate: dueDateStr, + }, + mode: "production", + }; + + const fileNameKey = client.invoiceFileName || resolvedClientKey; + const outFileName = `${fileNameKey}_${record.number}.pdf`; + const outFilePath = path.join(FOLDERS.INVOICE, resolvedSenderKey, outFileName); + + console.log(`\nPrinting invoice ${record.number}...`); + const ok = await createInvoice(printData, outFilePath); + process.exit(ok ? 0 : 1); + } + // Test mode: use first available options if (argv.test) { senderKey = Object.keys(senders)[0]; diff --git a/project.md b/project.md new file mode 100644 index 0000000..657eb05 --- /dev/null +++ b/project.md @@ -0,0 +1,324 @@ +# Invoice Generator - Project Documentation + +## Project Overview + +A Node.js command-line application for generating professional PDF invoices. The system uses a data-driven approach where all business information (senders, clients, products) is stored in JSON files, making it easy to maintain without code changes. + +## Architecture + +### Core Components + +``` +invoice/ +├── index.js # Main application entry point +├── data/ # JSON data files +│ ├── sender.json # Company/sender profiles +│ ├── client.json # Client information +│ ├── products.json # Product/service catalog +│ └── invoices.json # Invoice ledger (auto-generated) +├── invoice/ # Generated PDF output +│ └── / # Organized by sender +└── test/ + └── invoice.test.js # Unit tests +``` + +### Dependencies + +**Production:** + +- `easyinvoice` (v3.0.47) - PDF generation library +- `inquirer` (v8.2.4) - Interactive command-line prompts +- `yargs` (v17.7.2) - Command-line argument parsing + +**Development:** + +- `jest` (v29.7.0) - Testing framework + +## Data Flow + +### 1. Data Loading + +```javascript +// Loads all JSON files from data/ directory +const senders = loadJson(FILES.SENDERS); +const clients = loadJson(FILES.CLIENTS); +const productsData = loadJson(FILES.PRODUCTS); +``` + +### 2. User Input Processing + +- **Interactive Mode**: Uses `inquirer` prompts for sender, client, and product selection +- **Non-Interactive Mode**: Processes command-line arguments via `yargs` +- **Test Mode**: Automatically selects first available options + +### 3. Invoice Generation Process + +```mermaid +graph TD + A[Load Data] --> B[Validate Inputs] + B --> C[Generate Preview PDF] + C --> D[User Review] + D --> E{Proceed?} + E -->|Yes| F[Generate Production PDF] + E -->|No| G[Keep Preview Only] + F --> H[Save to Ledger] + H --> I[Update invoices.json] +``` + +### 4. File Output + +- **Preview**: `invoice//_-DEV-PREVIEW.pdf` +- **Production**: `invoice//_-.pdf` + +## Key Functions + +### `getNextInvoiceNumber(year, invoicesData)` + +- Generates sequential invoice numbers (YYYY-0001, YYYY-0002...) +- Resets numbering annually +- Handles unordered invoice numbers correctly + +### `createInvoice(invoiceData, invoiceFilePath)` + +- Uses `easyinvoice` library to generate PDF +- Creates directory structure if needed +- Returns success/failure status + +### `saveInvoiceRecord(invoiceData, invoiceProducts)` + +- Calculates totals (subtotal, tax, grand total) +- Appends invoice record to `invoices.json` +- Persists `dueDate` from `invoiceData.information.dueDate` when set, for accurate future `--print` output +- Maintains complete invoice history + +## Data Schema + +### Sender Schema + +```json +{ + "company": "string", + "contactName": "string (optional)", + "phone": "string (optional)", + "email": "string (optional)", + "address": "string", + "zip": "string", + "city": "string", + "country": "string", + "taxName": "string (optional)", + "taxNumber": "string (optional)", + "paymentInfo": "string (optional)" +} +``` + +### Client Schema + +```json +{ + "company": "string", + "address": "string", + "zip": "string", + "city": "string", + "country": "string", + "invoiceFileName": "string (optional)" +} +``` + +### Product Schema + +```json +{ + "description": "string", + "price": "number", + "taxRate": "number (optional, default 0)" +} +``` + +### Invoice Record Schema + +```json +{ + "number": "string (YYYY-0001 or custom e.g. 2026-LFSU01)", + "date": "string (YYYY-MM-DD)", + "dueDate": "string (YYYY-MM-DD, optional — see Due dates below)", + "sender": "string (must match sender.json company name)", + "client": "string (must match client.json company name)", + "products": [ + { + "description": "string", + "price": "number", + "quantity": "number", + "taxRate": "number" + } + ], + "subtotal": "number", + "taxTotal": "number", + "total": "number" +} +``` + +**Due dates:** When you run `node index.js --print `, the PDF due date is taken from `dueDate` if present. Otherwise it defaults to `date` plus 14 days. New production invoices still compute due as “today + 14” in the UI flow; `saveInvoiceRecord` stores that value on the ledger row as `dueDate` when present so reprints stay consistent. You can edit `dueDate` in `invoices.json` for any row (for example, payment terms through end of month). + +## Command-Line Interface + +### Available Commands + +```bash +npm start # Interactive mode +npm run generate # Non-interactive mode +npm test # Run unit tests +npm run test-invoice # Test mode (dev only) +``` + +### Reprinting from the ledger (no prompts, no ledger changes) + +```bash +node index.js --print +``` + +Looks up `invoice-number` in `data/invoices.json`, resolves sender and client by matching `company` strings to `sender.json` / `client.json`, and writes `invoice//_.pdf`. The client’s optional `invoiceFileName` is used as the filename prefix when set (for example `limmud_fsuc_2026-LFSU01.pdf`). + +### Command-Line Options + +```bash +--interactive # Force interactive mode +--print # PDF only from invoices.json; does not append ledger +--sender # Sender key from sender.json +--client # Client key from client.json +--products # Product keys from products.json +--test # Test mode (uses first available options) +--help # Show help +``` + +## Testing Strategy + +### Unit Tests (`test/invoice.test.js`) + +- **Invoice Numbering**: Tests sequential numbering logic +- **Year Reset**: Verifies numbering resets annually +- **Unordered Numbers**: Handles gaps in invoice sequence + +### Test Coverage + +- `getNextInvoiceNumber()` function +- Edge cases for invoice numbering +- Year boundary scenarios + +## Error Handling + +### File System Errors + +- Missing JSON files trigger helpful error messages +- Invalid JSON syntax is caught and reported +- Directory creation failures are handled gracefully + +### Data Validation + +- Invalid sender/client/product keys are rejected +- Missing required fields trigger validation errors +- Tax rate calculations handle missing values + +### PDF Generation Errors + +- `easyinvoice` failures are caught and logged +- File write errors are handled with user feedback + +## Configuration Options + +### Currency + +- Default: CAD (Canadian Dollar) +- Set in `commonData.settings.currency` + +### Tax Configuration + +- Per-product tax rates in `products.json` +- Tax name customization in `sender.json` +- Automatic tax calculations + +### Payment Information + +- Custom payment instructions in `sender.paymentInfo` +- Rendered in invoice footer +- Supports HTML formatting + +## File Organization + +### Input Files (`data/`) + +- **sender.json**: Company profiles and tax information +- **client.json**: Client contact details +- **products.json**: Service/product catalog with pricing +- **invoices.json**: Auto-generated invoice ledger + +### Output Files (`invoice/`) + +- Organized by sender name +- Preview files marked with `-DEV-PREVIEW` +- Production files with sequential numbering +- Automatic directory creation + +## Development Workflow + +### Adding New Data + +1. Edit appropriate JSON file in `data/` +2. Use interactive mode to test: `npm start` +3. Verify preview generation +4. Create production invoice if satisfied + +### Testing Changes + +1. Run unit tests: `npm test` +2. Test invoice generation: `npm run test-invoice` +3. Verify file output structure +4. Check ledger updates + +### Code Modifications + +- Main logic in `index.js` +- Test cases in `test/invoice.test.js` +- Data validation in main function +- Error handling throughout + +## Security Considerations + +- No external API calls +- Local file system only +- No sensitive data in code +- JSON validation for data integrity +- Error boundaries prevent crashes + +## Performance + +- Minimal dependencies +- Synchronous file operations +- No database overhead +- Fast PDF generation +- Efficient JSON parsing + +## Changelog (May 2026) + +- **`--print`**: Regenerate PDFs from `data/invoices.json` without modifying the ledger. +- **Optional ledger `dueDate`**: Overrides the default “invoice date + 14 days” when printing; stored on new rows when production creates `information.dueDate`. +- **Data**: Client `limmud_fsu_canada` (Limmud FSU Canada) with `invoiceFileName` for PDF naming; product `cruise_event_av_it` ($500 + 13% HST); sample invoice `2026-LFSU01` (Levkin Inc. → Limmud FSU Canada). +- **Sender**: Optional `contactName`, `phone`, and `email` on `levkin` in `sender.json` (and schema support for other senders). + +## Future Enhancements + +### Potential Features + +- Multiple currency support +- Invoice templates +- Email integration +- Export to accounting software +- Invoice status tracking +- Payment reminders + +### Technical Improvements + +- TypeScript migration +- Database integration +- Web interface +- API endpoints +- Docker containerization