diff --git a/TAX_REPORTS.md b/TAX_REPORTS.md
new file mode 100644
index 0000000..9f4f3b1
--- /dev/null
+++ b/TAX_REPORTS.md
@@ -0,0 +1,342 @@
+# Tax Calculator & Financial Report Generator
+
+## Overview
+
+The Tax Calculator & Financial Report Generator feature provides comprehensive tax estimation and financial reporting capabilities for ExpenseFlow users. It includes tax calculations for multiple countries (India, US), regime comparison, tax-deductible expense tracking, and PDF report generation.
+
+## Features
+
+### 1. Tax Calculator
+- **Multi-country support**: India (Old & New Regime), US tax brackets
+- **Automatic tax calculation**: Based on income and expenses
+- **Deduction tracking**: Track tax-deductible expenses (80C, 80D, etc.)
+- **Surcharge & Cess**: Automatic calculation for high-income earners
+- **Regime comparison**: Compare old vs new tax regime to find optimal choice
+
+### 2. Financial Reports
+- **Income Statement**: Summary of income and expenses with savings rate
+- **Expense Summary**: Detailed breakdown by category with trends
+- **Profit & Loss Statement**: Traditional P&L format
+- **Tax Report**: Tax liability with deductions breakdown
+- **Category Breakdown**: In-depth category analysis
+- **Monthly Comparison**: Month-over-month trends
+- **Annual Summary**: Comprehensive yearly overview
+
+### 3. PDF Export
+- Professional PDF reports using PDFKit
+- Branded headers with ExpenseFlow logo
+- Detailed tables and breakdowns
+- Download or share reports
+
+## API Endpoints
+
+### Tax Endpoints
+
+#### GET /api/tax/profile
+Get user's tax profile for a specific year.
+
+Query Parameters:
+- `taxYear` (optional): Tax year (default: current year)
+
+Response:
+```json
+{
+ "success": true,
+ "data": {
+ "country": "IN",
+ "regime": "new",
+ "taxBrackets": [...],
+ "standardDeduction": 75000,
+ "availableDeductions": [...]
+ }
+}
+```
+
+#### PUT /api/tax/profile
+Update tax profile settings.
+
+Request Body:
+```json
+{
+ "country": "IN",
+ "regime": "new",
+ "tdsDeducted": 50000,
+ "advanceTaxPaid": 25000,
+ "customDeductions": [
+ {
+ "name": "PPF Investment",
+ "section": "80C",
+ "amount": 150000
+ }
+ ]
+}
+```
+
+#### GET /api/tax/calculate
+Calculate tax liability for a tax year.
+
+Query Parameters:
+- `taxYear` (optional): Tax year to calculate
+
+Response:
+```json
+{
+ "success": true,
+ "data": {
+ "grossIncome": 1500000,
+ "standardDeduction": 75000,
+ "totalDeductions": 225000,
+ "taxableIncome": 1200000,
+ "baseTax": 108000,
+ "surcharge": 0,
+ "cess": 4320,
+ "totalTax": 112320,
+ "effectiveRate": 7.49,
+ "taxPayable": 37320
+ }
+}
+```
+
+#### GET /api/tax/compare-regimes
+Compare old vs new tax regime.
+
+Response:
+```json
+{
+ "success": true,
+ "data": {
+ "newRegime": {
+ "taxableIncome": 1200000,
+ "totalTax": 112320,
+ "effectiveRate": 7.49
+ },
+ "oldRegime": {
+ "taxableIncome": 950000,
+ "totalTax": 89180,
+ "effectiveRate": 5.94
+ },
+ "recommendation": "old",
+ "savings": 23140,
+ "message": "Old regime saves you ₹23,140"
+ }
+}
+```
+
+#### GET /api/tax/summary
+Get tax summary for dashboard display.
+
+#### GET /api/tax/deductible-categories
+Get list of tax-deductible expense categories.
+
+#### POST /api/tax/auto-tag
+Auto-tag an expense as tax-deductible.
+
+Request Body:
+```json
+{
+ "description": "Health insurance premium",
+ "category": "healthcare",
+ "amount": 25000
+}
+```
+
+### Report Endpoints
+
+#### POST /api/reports/generate
+Generate a financial report.
+
+Request Body:
+```json
+{
+ "reportType": "expense_summary",
+ "startDate": "2024-01-01",
+ "endDate": "2024-12-31",
+ "currency": "INR"
+}
+```
+
+Report Types:
+- `income_statement`
+- `expense_summary`
+- `profit_loss`
+- `tax_report`
+- `category_breakdown`
+- `monthly_comparison`
+- `annual_summary`
+
+#### GET /api/reports
+Get list of user's reports.
+
+Query Parameters:
+- `page` (optional): Page number (default: 1)
+- `limit` (optional): Items per page (default: 10)
+- `reportType` (optional): Filter by report type
+- `status` (optional): Filter by status (ready, processing, failed)
+
+#### GET /api/reports/:id
+Get a specific report by ID.
+
+#### GET /api/reports/:id/pdf
+Download report as PDF.
+
+#### DELETE /api/reports/:id
+Delete a report.
+
+#### POST /api/reports/quick/:type
+Generate quick reports with preset date ranges.
+
+Types:
+- `this-month`
+- `last-month`
+- `this-quarter`
+- `this-year`
+- `last-year`
+- `financial-year` (Indian FY: April to March)
+
+## Tax Brackets
+
+### India - New Regime (FY 2024-25)
+| Income Slab | Rate |
+|------------|------|
+| ₹0 - ₹3,00,000 | 0% |
+| ₹3,00,001 - ₹7,00,000 | 5% |
+| ₹7,00,001 - ₹10,00,000 | 10% |
+| ₹10,00,001 - ₹12,00,000 | 15% |
+| ₹12,00,001 - ₹15,00,000 | 20% |
+| Above ₹15,00,000 | 30% |
+
+### India - Old Regime
+| Income Slab | Rate |
+|------------|------|
+| ₹0 - ₹2,50,000 | 0% |
+| ₹2,50,001 - ₹5,00,000 | 5% |
+| ₹5,00,001 - ₹10,00,000 | 20% |
+| Above ₹10,00,000 | 30% |
+
+### Surcharge (India)
+- 10% for income > ₹50 Lakhs
+- 15% for income > ₹1 Crore
+- 25% for income > ₹2 Crore
+- 37% for income > ₹5 Crore
+
+### Health & Education Cess
+4% on total tax + surcharge
+
+## Deduction Sections (India)
+
+| Section | Description | Max Limit |
+|---------|-------------|-----------|
+| 80C | Investments (PPF, ELSS, LIC, etc.) | ₹1,50,000 |
+| 80CCD(1B) | Additional NPS contribution | ₹50,000 |
+| 80D | Health insurance premium | ₹25,000 - ₹1,00,000 |
+| 80E | Education loan interest | No limit |
+| 80G | Donations | Varies |
+| 80TTA | Savings account interest | ₹10,000 |
+| HRA | House Rent Allowance | As per rules |
+
+## Data Models
+
+### TaxProfile
+Stores user's tax configuration including country, regime, brackets, and deductions.
+
+### TaxCategory
+Maps expense categories to tax-deductible sections with deduction percentages.
+
+### FinancialReport
+Stores generated reports with all breakdown data for quick retrieval.
+
+## Usage Examples
+
+### Calculate Tax
+```javascript
+// Client-side
+const response = await fetch('/api/tax/calculate?taxYear=2024', {
+ headers: { 'Authorization': `Bearer ${token}` }
+});
+const { data } = await response.json();
+console.log(`Tax payable: ₹${data.taxPayable}`);
+```
+
+### Generate Report
+```javascript
+const response = await fetch('/api/reports/generate', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ reportType: 'annual_summary',
+ startDate: '2024-04-01',
+ endDate: '2025-03-31'
+ })
+});
+```
+
+### Download PDF
+```javascript
+const response = await fetch(`/api/reports/${reportId}/pdf`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+});
+const blob = await response.blob();
+const url = window.URL.createObjectURL(blob);
+// Trigger download
+```
+
+## Frontend Integration
+
+Include the tax-reports.js script and use the TaxReportsManager class:
+
+```html
+
+
+```
+
+## Files Created
+
+### Models
+- `models/TaxProfile.js` - Tax profile schema and default brackets
+- `models/TaxCategory.js` - Tax category mappings
+- `models/FinancialReport.js` - Report storage schema
+
+### Services
+- `services/taxService.js` - Tax calculation logic
+- `services/reportService.js` - Report generation
+- `services/pdfService.js` - PDF generation with PDFKit
+
+### Routes
+- `routes/tax.js` - Tax API endpoints
+- `routes/reports.js` - Reports API endpoints
+
+### Middleware
+- `middleware/taxValidator.js` - Request validation
+
+### Frontend
+- `public/tax-reports.js` - Client-side manager
+- `public/tax-reports.html` - Tax calculator UI
+
+## Notes
+
+1. **Tax Calculations**: This feature provides estimates only. Users should consult tax professionals for actual filing.
+
+2. **Financial Year**: Indian financial year runs April to March. The system handles this automatically.
+
+3. **PDF Generation**: Uses PDFKit library (already included in package.json).
+
+4. **Currency Support**: Supports INR, USD, EUR, GBP for reports.
+
+5. **Performance**: Reports are cached in MongoDB for quick retrieval. Regenerate for updated data.
diff --git a/middleware/taxValidator.js b/middleware/taxValidator.js
new file mode 100644
index 0000000..f6f5044
--- /dev/null
+++ b/middleware/taxValidator.js
@@ -0,0 +1,138 @@
+const Joi = require('joi');
+
+// Tax profile schema
+const taxProfileSchema = Joi.object({
+ country: Joi.string().valid('IN', 'US', 'UK', 'CA', 'AU').default('IN'),
+ regime: Joi.string().valid('old', 'new').default('new'),
+ filingStatus: Joi.string().valid('single', 'married_joint', 'married_separate', 'head_of_household').default('single'),
+ standardDeduction: Joi.number().min(0),
+ tdsDeducted: Joi.number().min(0).default(0),
+ advanceTaxPaid: Joi.number().min(0).default(0),
+ customDeductions: Joi.array().items(
+ Joi.object({
+ name: Joi.string().required().max(100),
+ section: Joi.string().required().max(20),
+ amount: Joi.number().min(0).required(),
+ description: Joi.string().max(500)
+ })
+ )
+});
+
+// Tax calculation options
+const taxCalculationSchema = Joi.object({
+ taxYear: Joi.number().integer().min(2020).max(new Date().getFullYear() + 1),
+ customDeductions: Joi.array().items(
+ Joi.object({
+ name: Joi.string().required(),
+ section: Joi.string(),
+ amount: Joi.number().min(0).required()
+ })
+ )
+});
+
+// Report generation schema
+const reportGenerationSchema = Joi.object({
+ reportType: Joi.string().valid(
+ 'income_statement',
+ 'expense_summary',
+ 'profit_loss',
+ 'tax_report',
+ 'category_breakdown',
+ 'monthly_comparison',
+ 'annual_summary'
+ ).required(),
+ startDate: Joi.date().iso(),
+ endDate: Joi.date().iso().min(Joi.ref('startDate')),
+ currency: Joi.string().valid('INR', 'USD', 'EUR', 'GBP').default('INR'),
+ includeForecasts: Joi.boolean().default(false),
+ workspaceId: Joi.string().regex(/^[a-fA-F0-9]{24}$/)
+});
+
+// Report list query schema
+const reportListSchema = Joi.object({
+ page: Joi.number().integer().min(1).default(1),
+ limit: Joi.number().integer().min(1).max(50).default(10),
+ reportType: Joi.string().valid(
+ 'income_statement',
+ 'expense_summary',
+ 'profit_loss',
+ 'tax_report',
+ 'category_breakdown',
+ 'monthly_comparison',
+ 'annual_summary'
+ ),
+ status: Joi.string().valid('ready', 'processing', 'failed')
+});
+
+// Deduction category schema
+const deductionCategorySchema = Joi.object({
+ code: Joi.string().required().max(20),
+ name: Joi.string().required().max(100),
+ description: Joi.string().max(500),
+ section: Joi.string().max(20),
+ maxDeductionLimit: Joi.number().min(0),
+ type: Joi.string().valid('deductible', 'partially_deductible', 'non_deductible').default('deductible'),
+ deductiblePercentage: Joi.number().min(0).max(100).default(100),
+ keywords: Joi.array().items(Joi.string()),
+ categoryMappings: Joi.array().items(
+ Joi.object({
+ expenseCategory: Joi.string().required(),
+ deductiblePercentage: Joi.number().min(0).max(100).default(100)
+ })
+ )
+});
+
+// Expense tax tagging schema
+const expenseTaxTagSchema = Joi.object({
+ expenseId: Joi.string().regex(/^[a-fA-F0-9]{24}$/).required(),
+ isTaxDeductible: Joi.boolean().required(),
+ taxCategory: Joi.string().max(50),
+ section: Joi.string().max(20),
+ deductiblePercentage: Joi.number().min(0).max(100)
+});
+
+// Validation middleware factory
+const validate = (schema, property = 'body') => {
+ return (req, res, next) => {
+ const { error, value } = schema.validate(req[property], {
+ abortEarly: false,
+ stripUnknown: true
+ });
+
+ if (error) {
+ const errors = error.details.map(detail => ({
+ field: detail.path.join('.'),
+ message: detail.message
+ }));
+
+ return res.status(400).json({
+ success: false,
+ error: 'Validation failed',
+ details: errors
+ });
+ }
+
+ req[property] = value;
+ next();
+ };
+};
+
+// Middleware exports
+module.exports = {
+ validateTaxProfile: validate(taxProfileSchema),
+ validateTaxCalculation: validate(taxCalculationSchema),
+ validateReportGeneration: validate(reportGenerationSchema),
+ validateReportList: validate(reportListSchema, 'query'),
+ validateDeductionCategory: validate(deductionCategorySchema),
+ validateExpenseTaxTag: validate(expenseTaxTagSchema),
+
+ // Schema exports for reuse
+ schemas: {
+ taxProfileSchema,
+ taxCalculationSchema,
+ reportGenerationSchema,
+ reportListSchema,
+ deductionCategorySchema,
+ expenseTaxTagSchema
+ }
+};
diff --git a/models/FinancialReport.js b/models/FinancialReport.js
new file mode 100644
index 0000000..251ae14
--- /dev/null
+++ b/models/FinancialReport.js
@@ -0,0 +1,198 @@
+const mongoose = require('mongoose');
+
+const reportSectionSchema = new mongoose.Schema({
+ title: String,
+ data: mongoose.Schema.Types.Mixed,
+ chartType: {
+ type: String,
+ enum: ['bar', 'pie', 'line', 'table', 'summary', 'none']
+ }
+}, { _id: false });
+
+const financialReportSchema = new mongoose.Schema({
+ user: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'User',
+ required: true
+ },
+ reportType: {
+ type: String,
+ required: true,
+ enum: [
+ 'income_statement',
+ 'profit_loss',
+ 'expense_summary',
+ 'tax_summary',
+ 'monthly_report',
+ 'quarterly_report',
+ 'annual_report',
+ 'custom'
+ ]
+ },
+ title: {
+ type: String,
+ required: true,
+ trim: true,
+ maxlength: 200
+ },
+ description: {
+ type: String,
+ trim: true,
+ maxlength: 500
+ },
+ dateRange: {
+ startDate: {
+ type: Date,
+ required: true
+ },
+ endDate: {
+ type: Date,
+ required: true
+ }
+ },
+ period: {
+ type: String,
+ enum: ['daily', 'weekly', 'monthly', 'quarterly', 'yearly', 'custom'],
+ default: 'monthly'
+ },
+ currency: {
+ type: String,
+ default: 'INR',
+ uppercase: true
+ },
+ summary: {
+ totalIncome: {
+ type: Number,
+ default: 0
+ },
+ totalExpenses: {
+ type: Number,
+ default: 0
+ },
+ netAmount: {
+ type: Number,
+ default: 0
+ },
+ savingsRate: {
+ type: Number,
+ default: 0
+ },
+ taxableIncome: {
+ type: Number,
+ default: 0
+ },
+ estimatedTax: {
+ type: Number,
+ default: 0
+ },
+ deductibleExpenses: {
+ type: Number,
+ default: 0
+ }
+ },
+ incomeBreakdown: [{
+ category: String,
+ amount: Number,
+ percentage: Number,
+ count: Number
+ }],
+ expenseBreakdown: [{
+ category: String,
+ amount: Number,
+ percentage: Number,
+ count: Number,
+ taxDeductible: Boolean,
+ deductibleAmount: Number
+ }],
+ monthlyTrends: [{
+ month: String,
+ year: Number,
+ income: Number,
+ expenses: Number,
+ net: Number
+ }],
+ taxDeductions: [{
+ section: String,
+ name: String,
+ amount: Number,
+ maxLimit: Number,
+ utilized: Number
+ }],
+ topExpenses: [{
+ description: String,
+ amount: Number,
+ category: String,
+ date: Date
+ }],
+ sections: [reportSectionSchema],
+ metadata: {
+ generatedAt: {
+ type: Date,
+ default: Date.now
+ },
+ generationTime: Number, // in milliseconds
+ expenseCount: Number,
+ incomeCount: Number,
+ version: {
+ type: String,
+ default: '1.0'
+ }
+ },
+ pdfUrl: {
+ type: String,
+ default: null
+ },
+ pdfGeneratedAt: Date,
+ isArchived: {
+ type: Boolean,
+ default: false
+ },
+ tags: [{
+ type: String,
+ trim: true
+ }]
+}, {
+ timestamps: true
+});
+
+// Indexes
+financialReportSchema.index({ user: 1, reportType: 1, createdAt: -1 });
+financialReportSchema.index({ user: 1, 'dateRange.startDate': 1, 'dateRange.endDate': 1 });
+financialReportSchema.index({ user: 1, isArchived: 1 });
+
+// Virtual for formatted date range
+financialReportSchema.virtual('formattedDateRange').get(function() {
+ const start = this.dateRange.startDate.toLocaleDateString('en-US', {
+ month: 'short', day: 'numeric', year: 'numeric'
+ });
+ const end = this.dateRange.endDate.toLocaleDateString('en-US', {
+ month: 'short', day: 'numeric', year: 'numeric'
+ });
+ return `${start} - ${end}`;
+});
+
+// Method to calculate health score
+financialReportSchema.methods.calculateHealthScore = function() {
+ const savingsRate = this.summary.savingsRate;
+ const expenseRatio = this.summary.totalIncome > 0
+ ? (this.summary.totalExpenses / this.summary.totalIncome) * 100
+ : 100;
+
+ let score = 50; // Base score
+
+ // Savings rate contribution (up to 30 points)
+ if (savingsRate >= 30) score += 30;
+ else if (savingsRate >= 20) score += 25;
+ else if (savingsRate >= 10) score += 15;
+ else if (savingsRate > 0) score += 5;
+
+ // Expense ratio contribution (up to 20 points)
+ if (expenseRatio <= 50) score += 20;
+ else if (expenseRatio <= 70) score += 15;
+ else if (expenseRatio <= 90) score += 10;
+ else if (expenseRatio <= 100) score += 5;
+
+ return Math.min(100, Math.max(0, score));
+};
+
+module.exports = mongoose.model('FinancialReport', financialReportSchema);
diff --git a/models/TaxCategory.js b/models/TaxCategory.js
new file mode 100644
index 0000000..65ca296
--- /dev/null
+++ b/models/TaxCategory.js
@@ -0,0 +1,235 @@
+const mongoose = require('mongoose');
+
+const taxCategoryMappingSchema = new mongoose.Schema({
+ expenseCategory: {
+ type: String,
+ required: true
+ },
+ taxCategory: {
+ type: String,
+ required: true
+ },
+ deductionCode: {
+ type: String,
+ trim: true
+ },
+ deductiblePercentage: {
+ type: Number,
+ default: 100,
+ min: 0,
+ max: 100
+ }
+}, { _id: false });
+
+const taxCategorySchema = new mongoose.Schema({
+ code: {
+ type: String,
+ required: true,
+ unique: true,
+ trim: true
+ },
+ name: {
+ type: String,
+ required: true,
+ trim: true
+ },
+ description: {
+ type: String,
+ trim: true
+ },
+ type: {
+ type: String,
+ required: true,
+ enum: ['deductible', 'non_deductible', 'partially_deductible', 'income', 'exempt']
+ },
+ country: {
+ type: String,
+ required: true,
+ default: 'IN',
+ uppercase: true
+ },
+ applicableTo: [{
+ type: String,
+ enum: ['individual', 'business', 'self_employed', 'all']
+ }],
+ categoryMappings: [taxCategoryMappingSchema],
+ maxDeductionLimit: {
+ type: Number,
+ default: null
+ },
+ requiresDocumentation: {
+ type: Boolean,
+ default: true
+ },
+ section: {
+ type: String,
+ trim: true
+ },
+ keywords: [{
+ type: String,
+ lowercase: true
+ }],
+ isActive: {
+ type: Boolean,
+ default: true
+ }
+}, {
+ timestamps: true
+});
+
+// Indexes
+taxCategorySchema.index({ code: 1, country: 1 });
+taxCategorySchema.index({ type: 1, country: 1 });
+taxCategorySchema.index({ keywords: 1 });
+
+// Static method to get default tax categories
+taxCategorySchema.statics.getDefaultCategories = function(country = 'IN') {
+ const categories = [
+ // Indian Tax Categories
+ {
+ code: 'BUSINESS_EXPENSE',
+ name: 'Business Expenses',
+ description: 'Expenses incurred for business operations',
+ type: 'deductible',
+ country: 'IN',
+ applicableTo: ['business', 'self_employed'],
+ section: '37(1)',
+ keywords: ['business', 'office', 'supplies', 'equipment'],
+ categoryMappings: [
+ { expenseCategory: 'utilities', taxCategory: 'BUSINESS_EXPENSE', deductionCode: '37', deductiblePercentage: 100 },
+ { expenseCategory: 'transport', taxCategory: 'BUSINESS_EXPENSE', deductionCode: '37', deductiblePercentage: 50 }
+ ]
+ },
+ {
+ code: 'MEDICAL_EXPENSE',
+ name: 'Medical Expenses',
+ description: 'Healthcare and medical treatment costs',
+ type: 'deductible',
+ country: 'IN',
+ applicableTo: ['all'],
+ section: '80D/80DDB',
+ maxDeductionLimit: 100000,
+ keywords: ['medical', 'health', 'hospital', 'medicine', 'doctor', 'healthcare'],
+ categoryMappings: [
+ { expenseCategory: 'healthcare', taxCategory: 'MEDICAL_EXPENSE', deductionCode: '80D', deductiblePercentage: 100 }
+ ]
+ },
+ {
+ code: 'EDUCATION_EXPENSE',
+ name: 'Education Expenses',
+ description: 'Educational and professional development costs',
+ type: 'deductible',
+ country: 'IN',
+ applicableTo: ['all'],
+ section: '80E',
+ keywords: ['education', 'tuition', 'course', 'training', 'school', 'college'],
+ categoryMappings: [
+ { expenseCategory: 'other', taxCategory: 'EDUCATION_EXPENSE', deductionCode: '80E', deductiblePercentage: 100 }
+ ]
+ },
+ {
+ code: 'CHARITABLE_DONATION',
+ name: 'Charitable Donations',
+ description: 'Donations to approved charitable organizations',
+ type: 'deductible',
+ country: 'IN',
+ applicableTo: ['all'],
+ section: '80G',
+ keywords: ['donation', 'charity', 'ngo', 'trust', 'foundation'],
+ categoryMappings: [
+ { expenseCategory: 'other', taxCategory: 'CHARITABLE_DONATION', deductionCode: '80G', deductiblePercentage: 50 }
+ ]
+ },
+ {
+ code: 'HOME_OFFICE',
+ name: 'Home Office Expenses',
+ description: 'Expenses for home-based work setup',
+ type: 'partially_deductible',
+ country: 'IN',
+ applicableTo: ['self_employed', 'business'],
+ section: '37(1)',
+ keywords: ['home office', 'work from home', 'internet', 'furniture'],
+ categoryMappings: [
+ { expenseCategory: 'utilities', taxCategory: 'HOME_OFFICE', deductionCode: '37', deductiblePercentage: 40 }
+ ]
+ },
+ {
+ code: 'TRAVEL_BUSINESS',
+ name: 'Business Travel',
+ description: 'Travel expenses for business purposes',
+ type: 'deductible',
+ country: 'IN',
+ applicableTo: ['business', 'self_employed'],
+ section: '37(1)',
+ keywords: ['travel', 'flight', 'hotel', 'business trip', 'conference'],
+ categoryMappings: [
+ { expenseCategory: 'transport', taxCategory: 'TRAVEL_BUSINESS', deductionCode: '37', deductiblePercentage: 100 }
+ ]
+ },
+ {
+ code: 'PERSONAL_EXPENSE',
+ name: 'Personal Expenses',
+ description: 'Non-deductible personal expenses',
+ type: 'non_deductible',
+ country: 'IN',
+ applicableTo: ['all'],
+ keywords: ['personal', 'food', 'entertainment', 'shopping'],
+ categoryMappings: [
+ { expenseCategory: 'food', taxCategory: 'PERSONAL_EXPENSE', deductionCode: null, deductiblePercentage: 0 },
+ { expenseCategory: 'entertainment', taxCategory: 'PERSONAL_EXPENSE', deductionCode: null, deductiblePercentage: 0 },
+ { expenseCategory: 'shopping', taxCategory: 'PERSONAL_EXPENSE', deductionCode: null, deductiblePercentage: 0 }
+ ]
+ },
+ {
+ code: 'INSURANCE_PREMIUM',
+ name: 'Insurance Premiums',
+ description: 'Life and health insurance premiums',
+ type: 'deductible',
+ country: 'IN',
+ applicableTo: ['all'],
+ section: '80C/80D',
+ maxDeductionLimit: 150000,
+ keywords: ['insurance', 'premium', 'life insurance', 'health insurance'],
+ categoryMappings: []
+ },
+ // US Tax Categories
+ {
+ code: 'US_BUSINESS_EXPENSE',
+ name: 'Business Expenses',
+ description: 'Ordinary and necessary business expenses',
+ type: 'deductible',
+ country: 'US',
+ applicableTo: ['business', 'self_employed'],
+ keywords: ['business', 'office', 'supplies'],
+ categoryMappings: [
+ { expenseCategory: 'utilities', taxCategory: 'US_BUSINESS_EXPENSE', deductiblePercentage: 100 }
+ ]
+ },
+ {
+ code: 'US_MEDICAL',
+ name: 'Medical Expenses',
+ description: 'Medical expenses exceeding 7.5% of AGI',
+ type: 'partially_deductible',
+ country: 'US',
+ applicableTo: ['all'],
+ keywords: ['medical', 'health', 'doctor', 'hospital'],
+ categoryMappings: [
+ { expenseCategory: 'healthcare', taxCategory: 'US_MEDICAL', deductiblePercentage: 100 }
+ ]
+ },
+ {
+ code: 'US_CHARITY',
+ name: 'Charitable Contributions',
+ description: 'Donations to qualified organizations',
+ type: 'deductible',
+ country: 'US',
+ applicableTo: ['all'],
+ keywords: ['charity', 'donation', 'nonprofit'],
+ categoryMappings: []
+ }
+ ];
+
+ return categories.filter(c => c.country === country || country === 'ALL');
+};
+
+module.exports = mongoose.model('TaxCategory', taxCategorySchema);
diff --git a/models/TaxProfile.js b/models/TaxProfile.js
new file mode 100644
index 0000000..fd1e969
--- /dev/null
+++ b/models/TaxProfile.js
@@ -0,0 +1,185 @@
+const mongoose = require('mongoose');
+
+const taxBracketSchema = new mongoose.Schema({
+ minIncome: {
+ type: Number,
+ required: true,
+ min: 0
+ },
+ maxIncome: {
+ type: Number,
+ default: null // null means no upper limit
+ },
+ rate: {
+ type: Number,
+ required: true,
+ min: 0,
+ max: 100
+ },
+ fixedAmount: {
+ type: Number,
+ default: 0
+ }
+}, { _id: false });
+
+const deductionSchema = new mongoose.Schema({
+ name: {
+ type: String,
+ required: true,
+ trim: true
+ },
+ code: {
+ type: String,
+ required: true,
+ trim: true
+ },
+ maxLimit: {
+ type: Number,
+ default: null // null means no limit
+ },
+ description: String
+}, { _id: false });
+
+const taxProfileSchema = new mongoose.Schema({
+ user: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'User',
+ required: true
+ },
+ taxYear: {
+ type: Number,
+ required: true
+ },
+ country: {
+ type: String,
+ required: true,
+ default: 'IN',
+ uppercase: true
+ },
+ region: {
+ type: String,
+ trim: true
+ },
+ regime: {
+ type: String,
+ enum: ['old', 'new', 'default'],
+ default: 'new'
+ },
+ filingStatus: {
+ type: String,
+ enum: ['individual', 'married_jointly', 'married_separately', 'head_of_household', 'business'],
+ default: 'individual'
+ },
+ employmentType: {
+ type: String,
+ enum: ['salaried', 'self_employed', 'business', 'freelancer', 'other'],
+ default: 'salaried'
+ },
+ taxBrackets: [taxBracketSchema],
+ standardDeduction: {
+ type: Number,
+ default: 50000 // Default for India
+ },
+ availableDeductions: [deductionSchema],
+ customDeductions: [{
+ name: String,
+ amount: Number,
+ section: String
+ }],
+ estimatedTaxCredits: {
+ type: Number,
+ default: 0
+ },
+ advanceTaxPaid: {
+ type: Number,
+ default: 0
+ },
+ tdsDeducted: {
+ type: Number,
+ default: 0
+ },
+ isActive: {
+ type: Boolean,
+ default: true
+ }
+}, {
+ timestamps: true
+});
+
+// Compound index for user and tax year
+taxProfileSchema.index({ user: 1, taxYear: 1 }, { unique: true });
+
+// Initialize default Indian tax brackets (New Regime FY 2024-25)
+taxProfileSchema.statics.getDefaultBrackets = function(country = 'IN', regime = 'new') {
+ if (country === 'IN') {
+ if (regime === 'new') {
+ return [
+ { minIncome: 0, maxIncome: 300000, rate: 0, fixedAmount: 0 },
+ { minIncome: 300001, maxIncome: 700000, rate: 5, fixedAmount: 0 },
+ { minIncome: 700001, maxIncome: 1000000, rate: 10, fixedAmount: 20000 },
+ { minIncome: 1000001, maxIncome: 1200000, rate: 15, fixedAmount: 50000 },
+ { minIncome: 1200001, maxIncome: 1500000, rate: 20, fixedAmount: 80000 },
+ { minIncome: 1500001, maxIncome: null, rate: 30, fixedAmount: 140000 }
+ ];
+ } else {
+ // Old Regime
+ return [
+ { minIncome: 0, maxIncome: 250000, rate: 0, fixedAmount: 0 },
+ { minIncome: 250001, maxIncome: 500000, rate: 5, fixedAmount: 0 },
+ { minIncome: 500001, maxIncome: 1000000, rate: 20, fixedAmount: 12500 },
+ { minIncome: 1000001, maxIncome: null, rate: 30, fixedAmount: 112500 }
+ ];
+ }
+ }
+
+ // US Tax Brackets (2024) - Simplified
+ if (country === 'US') {
+ return [
+ { minIncome: 0, maxIncome: 11600, rate: 10, fixedAmount: 0 },
+ { minIncome: 11601, maxIncome: 47150, rate: 12, fixedAmount: 1160 },
+ { minIncome: 47151, maxIncome: 100525, rate: 22, fixedAmount: 5426 },
+ { minIncome: 100526, maxIncome: 191950, rate: 24, fixedAmount: 17168 },
+ { minIncome: 191951, maxIncome: 243725, rate: 32, fixedAmount: 39110 },
+ { minIncome: 243726, maxIncome: 609350, rate: 35, fixedAmount: 55678 },
+ { minIncome: 609351, maxIncome: null, rate: 37, fixedAmount: 183647 }
+ ];
+ }
+
+ // Default progressive brackets
+ return [
+ { minIncome: 0, maxIncome: 50000, rate: 10, fixedAmount: 0 },
+ { minIncome: 50001, maxIncome: 100000, rate: 20, fixedAmount: 5000 },
+ { minIncome: 100001, maxIncome: null, rate: 30, fixedAmount: 15000 }
+ ];
+};
+
+// Get default deductions for a country
+taxProfileSchema.statics.getDefaultDeductions = function(country = 'IN') {
+ if (country === 'IN') {
+ return [
+ { name: 'Section 80C Investments', code: '80C', maxLimit: 150000, description: 'PPF, ELSS, Life Insurance, etc.' },
+ { name: 'Health Insurance Premium', code: '80D', maxLimit: 75000, description: 'Self and family health insurance' },
+ { name: 'Education Loan Interest', code: '80E', maxLimit: null, description: 'Interest on education loan' },
+ { name: 'Home Loan Interest', code: '24B', maxLimit: 200000, description: 'Interest on home loan' },
+ { name: 'Donations', code: '80G', maxLimit: null, description: 'Charitable donations' },
+ { name: 'NPS Contribution', code: '80CCD', maxLimit: 50000, description: 'National Pension Scheme' },
+ { name: 'Medical Expenses', code: '80DDB', maxLimit: 100000, description: 'Specified diseases treatment' },
+ { name: 'Rent Paid (HRA)', code: '10(13A)', maxLimit: null, description: 'House Rent Allowance' }
+ ];
+ }
+
+ if (country === 'US') {
+ return [
+ { name: 'Standard Deduction', code: 'STD', maxLimit: 14600, description: 'Standard deduction for single filers' },
+ { name: 'Mortgage Interest', code: 'MORT', maxLimit: 750000, description: 'Interest on home mortgage' },
+ { name: 'State and Local Taxes', code: 'SALT', maxLimit: 10000, description: 'State and local tax deduction' },
+ { name: 'Charitable Contributions', code: 'CHAR', maxLimit: null, description: 'Donations to qualified organizations' },
+ { name: 'Medical Expenses', code: 'MED', maxLimit: null, description: 'Exceeding 7.5% of AGI' },
+ { name: 'Student Loan Interest', code: 'STUD', maxLimit: 2500, description: 'Interest on student loans' }
+ ];
+ }
+
+ return [];
+};
+
+module.exports = mongoose.model('TaxProfile', taxProfileSchema);
diff --git a/public/tax-reports.html b/public/tax-reports.html
new file mode 100644
index 0000000..6fa978a
--- /dev/null
+++ b/public/tax-reports.html
@@ -0,0 +1,919 @@
+
+
+
+
+
+ Tax Calculator & Reports - ExpenseFlow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tax Summary
+
+
+
Loading tax calculation...
+
+
+
+
+
+
Compare Tax Regimes
+
Compare Old vs New tax regime to find the best option for you
+
+
+
+
+
+
Tax Deductions
+
Your tax-deductible expenses for the financial year
+
+
+
Track tax-deductible expenses to reduce your tax liability
+
+
+
+
+
+
+
+
+
Generate Report
+
+
+ Quick Reports:
+
+
+
+
+
+
+
+
+
+
Report Preview
+
+
+
Select or generate a report to view
+
+
+
+
+
+
Previous Reports
+
+
+
No reports generated yet
+
+
+
+
+
+
+
+
+
Tax Profile Settings
+
+
+
+
+
+
+
+
+ Old
+
+ New
+
+
+
+
+ ₹0.00
+
+
+
+
+
+
TDS & Advance Tax
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Custom Deductions
+
Add custom deductions like 80C investments, HRA, LTA, etc.
+
+
+
No custom deductions added
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/tax-reports.js b/public/tax-reports.js
new file mode 100644
index 0000000..8b69153
--- /dev/null
+++ b/public/tax-reports.js
@@ -0,0 +1,879 @@
+/**
+ * Tax Calculator & Reports Client-Side Manager
+ * Handles tax calculations, report generation, and PDF exports
+ */
+class TaxReportsManager {
+ constructor() {
+ this.baseUrl = '/api';
+ this.currentTaxYear = new Date().getFullYear();
+ this.profile = null;
+ this.reports = [];
+ this.init();
+ }
+
+ async init() {
+ await this.loadTaxProfile();
+ this.setupEventListeners();
+ }
+
+ /**
+ * Setup event listeners
+ */
+ setupEventListeners() {
+ // Tax year selector
+ const yearSelector = document.getElementById('tax-year-selector');
+ if (yearSelector) {
+ yearSelector.addEventListener('change', (e) => {
+ this.currentTaxYear = parseInt(e.target.value);
+ this.refreshAll();
+ });
+ }
+
+ // Regime toggle
+ const regimeToggle = document.getElementById('regime-toggle');
+ if (regimeToggle) {
+ regimeToggle.addEventListener('change', (e) => {
+ this.updateRegime(e.target.checked ? 'new' : 'old');
+ });
+ }
+
+ // Report generation form
+ const reportForm = document.getElementById('report-form');
+ if (reportForm) {
+ reportForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ this.handleReportGeneration(e.target);
+ });
+ }
+
+ // Quick report buttons
+ document.querySelectorAll('[data-quick-report]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ this.generateQuickReport(btn.dataset.quickReport, btn.dataset.reportType);
+ });
+ });
+ }
+
+ /**
+ * Get auth token
+ */
+ getToken() {
+ return localStorage.getItem('token');
+ }
+
+ /**
+ * Make authenticated request
+ */
+ async request(url, options = {}) {
+ const token = this.getToken();
+ if (!token) {
+ window.location.href = '/login.html';
+ return;
+ }
+
+ const config = {
+ ...options,
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+ };
+
+ const response = await fetch(`${this.baseUrl}${url}`, config);
+
+ if (response.status === 401) {
+ localStorage.removeItem('token');
+ window.location.href = '/login.html';
+ return;
+ }
+
+ return response;
+ }
+
+ // ==================== TAX PROFILE ====================
+
+ /**
+ * Load tax profile
+ */
+ async loadTaxProfile() {
+ try {
+ const response = await this.request(`/tax/profile?taxYear=${this.currentTaxYear}`);
+ const data = await response.json();
+
+ if (data.success) {
+ this.profile = data.data;
+ this.updateProfileUI();
+ }
+ } catch (error) {
+ console.error('Failed to load tax profile:', error);
+ this.showNotification('Failed to load tax profile', 'error');
+ }
+ }
+
+ /**
+ * Update tax profile
+ */
+ async updateProfile(updates) {
+ try {
+ const response = await this.request(`/tax/profile?taxYear=${this.currentTaxYear}`, {
+ method: 'PUT',
+ body: JSON.stringify(updates)
+ });
+ const data = await response.json();
+
+ if (data.success) {
+ this.profile = data.data;
+ this.updateProfileUI();
+ this.showNotification('Tax profile updated', 'success');
+ await this.calculateTax();
+ }
+ } catch (error) {
+ console.error('Failed to update profile:', error);
+ this.showNotification('Failed to update profile', 'error');
+ }
+ }
+
+ /**
+ * Update profile UI
+ */
+ updateProfileUI() {
+ if (!this.profile) return;
+
+ const elements = {
+ 'profile-country': this.profile.country,
+ 'profile-regime': this.profile.regime,
+ 'profile-standard-deduction': this.formatCurrency(this.profile.standardDeduction),
+ 'profile-tds': this.formatCurrency(this.profile.tdsDeducted),
+ 'profile-advance-tax': this.formatCurrency(this.profile.advanceTaxPaid)
+ };
+
+ Object.entries(elements).forEach(([id, value]) => {
+ const el = document.getElementById(id);
+ if (el) el.textContent = value;
+ });
+
+ // Update regime toggle
+ const regimeToggle = document.getElementById('regime-toggle');
+ if (regimeToggle) {
+ regimeToggle.checked = this.profile.regime === 'new';
+ }
+ }
+
+ /**
+ * Update tax regime
+ */
+ async updateRegime(regime) {
+ await this.updateProfile({ regime });
+ }
+
+ // ==================== TAX CALCULATION ====================
+
+ /**
+ * Calculate tax
+ */
+ async calculateTax(customDeductions = []) {
+ try {
+ this.showLoading('tax-calculation');
+
+ const response = await this.request('/tax/calculate', {
+ method: 'POST',
+ body: JSON.stringify({
+ taxYear: this.currentTaxYear,
+ customDeductions
+ })
+ });
+ const data = await response.json();
+
+ if (data.success) {
+ this.displayTaxCalculation(data.data);
+ }
+ } catch (error) {
+ console.error('Tax calculation error:', error);
+ this.showNotification('Failed to calculate tax', 'error');
+ } finally {
+ this.hideLoading('tax-calculation');
+ }
+ }
+
+ /**
+ * Display tax calculation
+ */
+ displayTaxCalculation(calc) {
+ const container = document.getElementById('tax-calculation-result');
+ if (!container) return;
+
+ container.innerHTML = `
+
+
+
Gross Income
+
${this.formatCurrency(calc.grossIncome)}
+
+
+
Total Deductions
+
${this.formatCurrency(calc.totalDeductions)}
+
+
+
Taxable Income
+
${this.formatCurrency(calc.taxableIncome)}
+
+
+
Total Tax
+
${this.formatCurrency(calc.totalTax)}
+
Effective Rate: ${calc.effectiveRate}%
+
+
+
+
+
Tax Breakdown
+
+
+
+ | Income Slab |
+ Rate |
+ Taxable Amount |
+ Tax |
+
+
+
+ ${calc.taxCalculation.map(t => `
+
+ | ${t.range} |
+ ${t.rate}% |
+ ${this.formatCurrency(t.taxableAmount)} |
+ ${this.formatCurrency(t.tax)} |
+
+ `).join('')}
+
+
+
+ | Base Tax |
+ ${this.formatCurrency(calc.baseTax)} |
+
+
+ | Surcharge |
+ ${this.formatCurrency(calc.surcharge)} |
+
+
+ | Health & Education Cess (4%) |
+ ${this.formatCurrency(calc.cess)} |
+
+
+ | Total Tax Liability |
+ ${this.formatCurrency(calc.totalTax)} |
+
+
+
+
+
+
+
+
${calc.taxPayable > 0 ? 'Tax Payable' : 'Tax Refund'}
+
${this.formatCurrency(calc.taxPayable > 0 ? calc.taxPayable : calc.taxRefund)}
+
After TDS (${this.formatCurrency(calc.tdsDeducted)}) and Advance Tax (${this.formatCurrency(calc.advanceTaxPaid)})
+
+
+
+ ${calc.deductions.length > 0 ? `
+
+
Deductions Summary
+
+
+
+ | Deduction |
+ Section |
+ Claimed |
+ Limit |
+ Allowed |
+
+
+
+ ${calc.deductions.map(d => `
+
+ | ${d.name} |
+ ${d.section || '-'} |
+ ${this.formatCurrency(d.claimed)} |
+ ${d.limit ? this.formatCurrency(d.limit) : 'No limit'} |
+ ${this.formatCurrency(d.allowed)} |
+
+ `).join('')}
+
+
+
+ ` : ''}
+ `;
+ }
+
+ /**
+ * Compare tax regimes
+ */
+ async compareRegimes() {
+ try {
+ this.showLoading('regime-comparison');
+
+ const response = await this.request(`/tax/compare-regimes?taxYear=${this.currentTaxYear}`);
+ const data = await response.json();
+
+ if (data.success) {
+ this.displayRegimeComparison(data.data);
+ }
+ } catch (error) {
+ console.error('Regime comparison error:', error);
+ this.showNotification('Failed to compare regimes', 'error');
+ } finally {
+ this.hideLoading('regime-comparison');
+ }
+ }
+
+ /**
+ * Display regime comparison
+ */
+ displayRegimeComparison(comparison) {
+ const container = document.getElementById('regime-comparison');
+ if (!container) return;
+
+ container.innerHTML = `
+
+
+
New Regime
+
+
Taxable Income: ${this.formatCurrency(comparison.newRegime.taxableIncome)}
+
Deductions: ${this.formatCurrency(comparison.newRegime.deductions)}
+
Tax: ${this.formatCurrency(comparison.newRegime.totalTax)}
+
Effective Rate: ${comparison.newRegime.effectiveRate}%
+
+ ${comparison.recommendation === 'new' ? '
Recommended' : ''}
+
+
+
Old Regime
+
+
Taxable Income: ${this.formatCurrency(comparison.oldRegime.taxableIncome)}
+
Deductions: ${this.formatCurrency(comparison.oldRegime.deductions)}
+
Tax: ${this.formatCurrency(comparison.oldRegime.totalTax)}
+
Effective Rate: ${comparison.oldRegime.effectiveRate}%
+
+ ${comparison.recommendation === 'old' ? '
Recommended' : ''}
+
+
+
+
${comparison.message}
+
+ `;
+ }
+
+ // ==================== REPORTS ====================
+
+ /**
+ * Generate report
+ */
+ async generateReport(reportType, startDate, endDate, currency = 'INR') {
+ try {
+ this.showLoading('report-generation');
+
+ const response = await this.request('/reports/generate', {
+ method: 'POST',
+ body: JSON.stringify({
+ reportType,
+ startDate,
+ endDate,
+ currency
+ })
+ });
+ const data = await response.json();
+
+ if (data.success) {
+ this.showNotification('Report generated successfully', 'success');
+ await this.loadReports();
+ this.displayReport(data.data);
+ return data.data;
+ }
+ } catch (error) {
+ console.error('Report generation error:', error);
+ this.showNotification('Failed to generate report', 'error');
+ } finally {
+ this.hideLoading('report-generation');
+ }
+ }
+
+ /**
+ * Handle report form submission
+ */
+ async handleReportGeneration(form) {
+ const formData = new FormData(form);
+ await this.generateReport(
+ formData.get('reportType'),
+ formData.get('startDate'),
+ formData.get('endDate'),
+ formData.get('currency') || 'INR'
+ );
+ }
+
+ /**
+ * Generate quick report
+ */
+ async generateQuickReport(period, reportType = 'expense_summary') {
+ try {
+ this.showLoading('report-generation');
+
+ const response = await this.request(`/reports/quick/${period}`, {
+ method: 'POST',
+ body: JSON.stringify({ reportType })
+ });
+ const data = await response.json();
+
+ if (data.success) {
+ this.showNotification(`${period} report generated`, 'success');
+ await this.loadReports();
+ this.displayReport(data.data);
+ }
+ } catch (error) {
+ console.error('Quick report error:', error);
+ this.showNotification('Failed to generate quick report', 'error');
+ } finally {
+ this.hideLoading('report-generation');
+ }
+ }
+
+ /**
+ * Load user's reports
+ */
+ async loadReports(page = 1, limit = 10) {
+ try {
+ const response = await this.request(`/reports?page=${page}&limit=${limit}`);
+ const data = await response.json();
+
+ if (data.success) {
+ this.reports = data.data;
+ this.displayReportsList(data.data, data.pagination);
+ }
+ } catch (error) {
+ console.error('Failed to load reports:', error);
+ }
+ }
+
+ /**
+ * Display reports list
+ */
+ displayReportsList(reports, pagination) {
+ const container = document.getElementById('reports-list');
+ if (!container) return;
+
+ if (reports.length === 0) {
+ container.innerHTML = `
+
+
No reports generated yet
+
Generate your first financial report above
+
+ `;
+ return;
+ }
+
+ container.innerHTML = `
+
+
+
+ | Report |
+ Type |
+ Period |
+ Generated |
+ Actions |
+
+
+
+ ${reports.map(report => `
+
+ | ${report.title} |
+ ${this.formatReportType(report.reportType)} |
+ ${this.formatDateRange(report.dateRange)} |
+ ${this.formatDate(report.generatedAt)} |
+
+
+
+
+ |
+
+ `).join('')}
+
+
+
+ ${pagination && pagination.pages > 1 ? `
+
+ ` : ''}
+ `;
+ }
+
+ /**
+ * View report
+ */
+ async viewReport(reportId) {
+ try {
+ const response = await this.request(`/reports/${reportId}`);
+ const data = await response.json();
+
+ if (data.success) {
+ this.displayReport(data.data);
+ }
+ } catch (error) {
+ console.error('Failed to load report:', error);
+ this.showNotification('Failed to load report', 'error');
+ }
+ }
+
+ /**
+ * Display report
+ */
+ displayReport(report) {
+ const container = document.getElementById('report-view');
+ if (!container) return;
+
+ let content = `
+
+ `;
+
+ // Summary section
+ content += `
+
+
+
Total Income
+
${this.formatCurrency(report.totalIncome)}
+
+
+
Total Expenses
+
${this.formatCurrency(report.totalExpenses)}
+
+
+
Net ${report.netIncome >= 0 ? 'Savings' : 'Loss'}
+
${this.formatCurrency(Math.abs(report.netIncome))}
+
+
+ `;
+
+ // Income breakdown
+ if (report.incomeBreakdown && report.incomeBreakdown.length > 0) {
+ content += this.renderBreakdownTable('Income Breakdown', report.incomeBreakdown, 'income');
+ }
+
+ // Expense breakdown
+ if (report.expenseBreakdown && report.expenseBreakdown.length > 0) {
+ content += this.renderBreakdownTable('Expense Breakdown', report.expenseBreakdown, 'expense');
+ }
+
+ // Monthly trends
+ if (report.monthlyTrends && report.monthlyTrends.length > 0) {
+ content += this.renderMonthlyTrends(report.monthlyTrends);
+ }
+
+ // Tax summary
+ if (report.taxSummary) {
+ content += this.renderTaxSummary(report.taxSummary);
+ }
+
+ container.innerHTML = content;
+ }
+
+ /**
+ * Render breakdown table
+ */
+ renderBreakdownTable(title, data, type) {
+ return `
+
+
${title}
+
+
+
+ | Category |
+ Amount |
+ % of Total |
+ Count |
+
+
+
+ ${data.map(item => `
+
+ | ${this.capitalize(item.category)} |
+ ${this.formatCurrency(item.amount)} |
+ ${item.percentage || '-'}% |
+ ${item.count} |
+
+ `).join('')}
+
+
+
+ `;
+ }
+
+ /**
+ * Render monthly trends
+ */
+ renderMonthlyTrends(trends) {
+ return `
+
+
Monthly Trends
+
+
+
+ | Month |
+ Income |
+ Expenses |
+ Net Savings |
+
+
+
+ ${trends.map(t => `
+
+ | ${t.month} |
+ ${this.formatCurrency(t.income)} |
+ ${this.formatCurrency(t.expenses)} |
+ ${this.formatCurrency(t.netSavings)} |
+
+ `).join('')}
+
+
+
+ `;
+ }
+
+ /**
+ * Render tax summary
+ */
+ renderTaxSummary(summary) {
+ return `
+
+
Tax Summary
+
+
+ Gross Income
+ ${this.formatCurrency(summary.grossIncome)}
+
+
+ Total Deductions
+ ${this.formatCurrency(summary.totalDeductions)}
+
+
+ Taxable Income
+ ${this.formatCurrency(summary.taxableIncome)}
+
+
+ Tax Liability
+ ${this.formatCurrency(summary.taxLiability)}
+
+
+ Effective Rate
+ ${summary.effectiveRate}%
+
+
+ Tax Regime
+ ${this.capitalize(summary.regime)}
+
+
+
+ `;
+ }
+
+ /**
+ * Download report as PDF
+ */
+ async downloadPDF(reportId) {
+ try {
+ this.showNotification('Generating PDF...', 'info');
+
+ const response = await this.request(`/reports/${reportId}/pdf`);
+
+ if (!response.ok) {
+ throw new Error('Failed to generate PDF');
+ }
+
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `report_${reportId}.pdf`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ this.showNotification('PDF downloaded', 'success');
+ } catch (error) {
+ console.error('PDF download error:', error);
+ this.showNotification('Failed to download PDF', 'error');
+ }
+ }
+
+ /**
+ * Delete report
+ */
+ async deleteReport(reportId) {
+ if (!confirm('Are you sure you want to delete this report?')) return;
+
+ try {
+ const response = await this.request(`/reports/${reportId}`, {
+ method: 'DELETE'
+ });
+ const data = await response.json();
+
+ if (data.success) {
+ this.showNotification('Report deleted', 'success');
+ await this.loadReports();
+
+ // Clear report view if viewing deleted report
+ const container = document.getElementById('report-view');
+ if (container) container.innerHTML = '';
+ }
+ } catch (error) {
+ console.error('Delete report error:', error);
+ this.showNotification('Failed to delete report', 'error');
+ }
+ }
+
+ // ==================== UTILITIES ====================
+
+ /**
+ * Refresh all data
+ */
+ async refreshAll() {
+ await Promise.all([
+ this.loadTaxProfile(),
+ this.calculateTax(),
+ this.loadReports()
+ ]);
+ }
+
+ /**
+ * Format currency
+ */
+ formatCurrency(amount, currency = 'INR') {
+ const symbols = { INR: '₹', USD: '$', EUR: '€', GBP: '£' };
+ const symbol = symbols[currency] || currency;
+ const formatted = Math.abs(amount || 0).toLocaleString('en-IN', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ });
+ return amount < 0 ? `-${symbol}${formatted}` : `${symbol}${formatted}`;
+ }
+
+ /**
+ * Format date
+ */
+ formatDate(dateString) {
+ if (!dateString) return '-';
+ return new Date(dateString).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ });
+ }
+
+ /**
+ * Format date range
+ */
+ formatDateRange(range) {
+ if (!range) return '-';
+ return `${this.formatDate(range.startDate)} - ${this.formatDate(range.endDate)}`;
+ }
+
+ /**
+ * Format report type
+ */
+ formatReportType(type) {
+ const types = {
+ income_statement: 'Income Statement',
+ expense_summary: 'Expense Summary',
+ profit_loss: 'P&L',
+ tax_report: 'Tax Report',
+ category_breakdown: 'Categories',
+ monthly_comparison: 'Monthly',
+ annual_summary: 'Annual'
+ };
+ return types[type] || type;
+ }
+
+ /**
+ * Capitalize string
+ */
+ capitalize(str) {
+ if (!str) return '';
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
+ }
+
+ /**
+ * Show loading state
+ */
+ showLoading(elementId) {
+ const el = document.getElementById(elementId);
+ if (el) {
+ el.classList.add('loading');
+ el.dataset.originalContent = el.innerHTML;
+ el.innerHTML = '';
+ }
+ }
+
+ /**
+ * Hide loading state
+ */
+ hideLoading(elementId) {
+ const el = document.getElementById(elementId);
+ if (el) {
+ el.classList.remove('loading');
+ }
+ }
+
+ /**
+ * Show notification
+ */
+ showNotification(message, type = 'info') {
+ const container = document.getElementById('notification-container') || this.createNotificationContainer();
+
+ const notification = document.createElement('div');
+ notification.className = `notification ${type}`;
+ notification.innerHTML = `
+ ${message}
+
+ `;
+
+ container.appendChild(notification);
+
+ setTimeout(() => notification.remove(), 5000);
+ }
+
+ /**
+ * Create notification container
+ */
+ createNotificationContainer() {
+ const container = document.createElement('div');
+ container.id = 'notification-container';
+ document.body.appendChild(container);
+ return container;
+ }
+}
+
+// Initialize on DOM ready
+let taxReports;
+document.addEventListener('DOMContentLoaded', () => {
+ taxReports = new TaxReportsManager();
+});
+
+// Export for module usage
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = TaxReportsManager;
+}
diff --git a/routes/reports.js b/routes/reports.js
new file mode 100644
index 0000000..17efae4
--- /dev/null
+++ b/routes/reports.js
@@ -0,0 +1,283 @@
+const express = require('express');
+const router = express.Router();
+const auth = require('../middleware/auth');
+const rateLimit = require('../middleware/rateLimit');
+const reportService = require('../services/reportService');
+const pdfService = require('../services/pdfService');
+const { validateReportGeneration, validateReportList } = require('../middleware/taxValidator');
+
+/**
+ * @route POST /api/reports/generate
+ * @desc Generate a financial report
+ * @access Private
+ */
+router.post('/generate', auth, validateReportGeneration, async (req, res) => {
+ try {
+ const {
+ reportType,
+ startDate,
+ endDate,
+ currency,
+ includeForecasts,
+ workspaceId
+ } = req.body;
+
+ const report = await reportService.generateReport(req.user.id, reportType, {
+ startDate,
+ endDate,
+ currency,
+ includeForecasts,
+ workspaceId
+ });
+
+ res.status(201).json({
+ success: true,
+ data: report,
+ message: 'Report generated successfully'
+ });
+ } catch (error) {
+ console.error('Report generation error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to generate report'
+ });
+ }
+});
+
+/**
+ * @route GET /api/reports
+ * @desc Get user's reports
+ * @access Private
+ */
+router.get('/', auth, validateReportList, async (req, res) => {
+ try {
+ const { page, limit, reportType, status } = req.query;
+
+ const result = await reportService.getUserReports(req.user.id, {
+ page: parseInt(page) || 1,
+ limit: parseInt(limit) || 10,
+ reportType,
+ status
+ });
+
+ res.json({
+ success: true,
+ data: result.reports,
+ pagination: result.pagination
+ });
+ } catch (error) {
+ console.error('Get reports error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch reports'
+ });
+ }
+});
+
+/**
+ * @route GET /api/reports/:id
+ * @desc Get report by ID
+ * @access Private
+ */
+router.get('/:id', auth, async (req, res) => {
+ try {
+ const report = await reportService.getReportById(req.params.id, req.user.id);
+
+ res.json({
+ success: true,
+ data: report
+ });
+ } catch (error) {
+ console.error('Get report error:', error);
+ if (error.message === 'Report not found') {
+ return res.status(404).json({
+ success: false,
+ error: 'Report not found'
+ });
+ }
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch report'
+ });
+ }
+});
+
+/**
+ * @route GET /api/reports/:id/pdf
+ * @desc Download report as PDF
+ * @access Private
+ */
+router.get('/:id/pdf', auth, async (req, res) => {
+ try {
+ const pdfBuffer = await pdfService.generatePDFForReport(req.params.id, req.user.id);
+
+ // Get report for filename
+ const report = await reportService.getReportById(req.params.id, req.user.id);
+ const filename = `${report.title.replace(/[^a-zA-Z0-9]/g, '_')}.pdf`;
+
+ res.setHeader('Content-Type', 'application/pdf');
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
+ res.setHeader('Content-Length', pdfBuffer.length);
+
+ res.send(pdfBuffer);
+ } catch (error) {
+ console.error('PDF generation error:', error);
+ if (error.message === 'Report not found or not ready') {
+ return res.status(404).json({
+ success: false,
+ error: 'Report not found or not ready'
+ });
+ }
+ res.status(500).json({
+ success: false,
+ error: 'Failed to generate PDF'
+ });
+ }
+});
+
+/**
+ * @route DELETE /api/reports/:id
+ * @desc Delete a report
+ * @access Private
+ */
+router.delete('/:id', auth, async (req, res) => {
+ try {
+ await reportService.deleteReport(req.params.id, req.user.id);
+
+ res.json({
+ success: true,
+ message: 'Report deleted successfully'
+ });
+ } catch (error) {
+ console.error('Delete report error:', error);
+ if (error.message === 'Report not found') {
+ return res.status(404).json({
+ success: false,
+ error: 'Report not found'
+ });
+ }
+ res.status(500).json({
+ success: false,
+ error: 'Failed to delete report'
+ });
+ }
+});
+
+/**
+ * @route POST /api/reports/quick/:type
+ * @desc Generate quick report (preset date ranges)
+ * @access Private
+ */
+router.post('/quick/:type', auth, async (req, res) => {
+ try {
+ const { type } = req.params;
+ const { reportType = 'expense_summary' } = req.body;
+
+ let startDate, endDate;
+ const now = new Date();
+
+ switch (type) {
+ case 'this-month':
+ startDate = new Date(now.getFullYear(), now.getMonth(), 1);
+ endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+ break;
+ case 'last-month':
+ startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+ endDate = new Date(now.getFullYear(), now.getMonth(), 0);
+ break;
+ case 'this-quarter':
+ const quarter = Math.floor(now.getMonth() / 3);
+ startDate = new Date(now.getFullYear(), quarter * 3, 1);
+ endDate = new Date(now.getFullYear(), (quarter + 1) * 3, 0);
+ break;
+ case 'this-year':
+ startDate = new Date(now.getFullYear(), 0, 1);
+ endDate = new Date(now.getFullYear(), 11, 31);
+ break;
+ case 'last-year':
+ startDate = new Date(now.getFullYear() - 1, 0, 1);
+ endDate = new Date(now.getFullYear() - 1, 11, 31);
+ break;
+ case 'financial-year':
+ // Indian FY (April to March)
+ const fyStart = now.getMonth() >= 3 ? now.getFullYear() : now.getFullYear() - 1;
+ startDate = new Date(fyStart, 3, 1);
+ endDate = new Date(fyStart + 1, 2, 31);
+ break;
+ default:
+ return res.status(400).json({
+ success: false,
+ error: 'Invalid quick report type. Use: this-month, last-month, this-quarter, this-year, last-year, financial-year'
+ });
+ }
+
+ const report = await reportService.generateReport(req.user.id, reportType, {
+ startDate,
+ endDate
+ });
+
+ res.status(201).json({
+ success: true,
+ data: report,
+ message: `${type} report generated successfully`
+ });
+ } catch (error) {
+ console.error('Quick report error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to generate quick report'
+ });
+ }
+});
+
+/**
+ * @route GET /api/reports/types/available
+ * @desc Get available report types
+ * @access Private
+ */
+router.get('/types/available', auth, (req, res) => {
+ const reportTypes = [
+ {
+ type: 'income_statement',
+ name: 'Income Statement',
+ description: 'Summary of income and expenses with savings rate'
+ },
+ {
+ type: 'expense_summary',
+ name: 'Expense Summary',
+ description: 'Detailed breakdown of expenses by category'
+ },
+ {
+ type: 'profit_loss',
+ name: 'Profit & Loss Statement',
+ description: 'Traditional P&L format with operating and discretionary expenses'
+ },
+ {
+ type: 'tax_report',
+ name: 'Tax Report',
+ description: 'Tax liability calculation with deductions breakdown'
+ },
+ {
+ type: 'category_breakdown',
+ name: 'Category Breakdown',
+ description: 'Detailed analysis of income and expenses by category'
+ },
+ {
+ type: 'monthly_comparison',
+ name: 'Monthly Comparison',
+ description: 'Month-over-month financial trends and growth rates'
+ },
+ {
+ type: 'annual_summary',
+ name: 'Annual Summary',
+ description: 'Comprehensive yearly financial overview'
+ }
+ ];
+
+ res.json({
+ success: true,
+ data: reportTypes
+ });
+});
+
+module.exports = router;
diff --git a/routes/tax.js b/routes/tax.js
new file mode 100644
index 0000000..3df90a1
--- /dev/null
+++ b/routes/tax.js
@@ -0,0 +1,265 @@
+const express = require('express');
+const router = express.Router();
+const auth = require('../middleware/auth');
+const rateLimit = require('../middleware/rateLimit');
+const taxService = require('../services/taxService');
+const {
+ validateTaxProfile,
+ validateTaxCalculation,
+ validateDeductionCategory,
+ validateExpenseTaxTag
+} = require('../middleware/taxValidator');
+
+/**
+ * @route GET /api/tax/profile
+ * @desc Get user's tax profile for current year
+ * @access Private
+ */
+router.get('/profile', auth, async (req, res) => {
+ try {
+ const taxYear = parseInt(req.query.taxYear) || new Date().getFullYear();
+ const profile = await taxService.getOrCreateProfile(req.user.id, taxYear);
+
+ res.json({
+ success: true,
+ data: profile
+ });
+ } catch (error) {
+ console.error('Get tax profile error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch tax profile'
+ });
+ }
+});
+
+/**
+ * @route PUT /api/tax/profile
+ * @desc Update tax profile
+ * @access Private
+ */
+router.put('/profile', auth, validateTaxProfile, async (req, res) => {
+ try {
+ const taxYear = parseInt(req.query.taxYear) || new Date().getFullYear();
+ const profile = await taxService.updateProfile(req.user.id, taxYear, req.body);
+
+ res.json({
+ success: true,
+ data: profile,
+ message: 'Tax profile updated successfully'
+ });
+ } catch (error) {
+ console.error('Update tax profile error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to update tax profile'
+ });
+ }
+});
+
+/**
+ * @route GET /api/tax/calculate
+ * @desc Calculate tax liability
+ * @access Private
+ */
+router.get('/calculate', auth, async (req, res) => {
+ try {
+ const taxYear = parseInt(req.query.taxYear) || new Date().getFullYear();
+ const options = {};
+
+ if (req.query.customDeductions) {
+ options.customDeductions = JSON.parse(req.query.customDeductions);
+ }
+
+ const calculation = await taxService.calculateTax(req.user.id, taxYear, options);
+
+ res.json({
+ success: true,
+ data: calculation
+ });
+ } catch (error) {
+ console.error('Tax calculation error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to calculate tax'
+ });
+ }
+});
+
+/**
+ * @route POST /api/tax/calculate
+ * @desc Calculate tax with custom deductions
+ * @access Private
+ */
+router.post('/calculate', auth, validateTaxCalculation, async (req, res) => {
+ try {
+ const { taxYear = new Date().getFullYear(), customDeductions = [] } = req.body;
+
+ const calculation = await taxService.calculateTax(req.user.id, taxYear, {
+ customDeductions
+ });
+
+ res.json({
+ success: true,
+ data: calculation
+ });
+ } catch (error) {
+ console.error('Tax calculation error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to calculate tax'
+ });
+ }
+});
+
+/**
+ * @route GET /api/tax/compare-regimes
+ * @desc Compare old vs new tax regime
+ * @access Private
+ */
+router.get('/compare-regimes', auth, async (req, res) => {
+ try {
+ const taxYear = parseInt(req.query.taxYear) || new Date().getFullYear();
+ const comparison = await taxService.compareRegimes(req.user.id, taxYear);
+
+ res.json({
+ success: true,
+ data: comparison
+ });
+ } catch (error) {
+ console.error('Regime comparison error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to compare tax regimes'
+ });
+ }
+});
+
+/**
+ * @route GET /api/tax/summary
+ * @desc Get tax summary for dashboard
+ * @access Private
+ */
+router.get('/summary', auth, async (req, res) => {
+ try {
+ const taxYear = parseInt(req.query.taxYear) || new Date().getFullYear();
+ const summary = await taxService.getTaxSummary(req.user.id, taxYear);
+
+ res.json({
+ success: true,
+ data: summary
+ });
+ } catch (error) {
+ console.error('Tax summary error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch tax summary'
+ });
+ }
+});
+
+/**
+ * @route GET /api/tax/deductible-categories
+ * @desc Get tax-deductible expense categories
+ * @access Private
+ */
+router.get('/deductible-categories', auth, async (req, res) => {
+ try {
+ const country = req.query.country || 'IN';
+ const categories = await taxService.getDeductibleCategories(country);
+
+ res.json({
+ success: true,
+ data: categories
+ });
+ } catch (error) {
+ console.error('Get deductible categories error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch deductible categories'
+ });
+ }
+});
+
+/**
+ * @route POST /api/tax/auto-tag
+ * @desc Auto-tag expense as tax-deductible
+ * @access Private
+ */
+router.post('/auto-tag', auth, async (req, res) => {
+ try {
+ const { description, category, amount } = req.body;
+
+ const expense = { description, category, amount };
+ const taxInfo = await taxService.autoTagExpense(expense);
+
+ res.json({
+ success: true,
+ data: taxInfo
+ });
+ } catch (error) {
+ console.error('Auto-tag expense error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to auto-tag expense'
+ });
+ }
+});
+
+/**
+ * @route GET /api/tax/deductible-expenses
+ * @desc Get all tax-deductible expenses for a period
+ * @access Private
+ */
+router.get('/deductible-expenses', auth, async (req, res) => {
+ try {
+ const taxYear = parseInt(req.query.taxYear) || new Date().getFullYear();
+
+ // Get date range for tax year (Indian FY: April to March)
+ const startDate = new Date(taxYear, 3, 1); // April 1
+ const endDate = new Date(taxYear + 1, 2, 31); // March 31
+
+ const expenses = await taxService.getDeductibleExpenses(req.user.id, startDate, endDate);
+
+ res.json({
+ success: true,
+ data: {
+ taxYear,
+ period: { startDate, endDate },
+ expenses,
+ totalDeductible: expenses.reduce((sum, e) => sum + e.deductibleAmount, 0)
+ }
+ });
+ } catch (error) {
+ console.error('Get deductible expenses error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch deductible expenses'
+ });
+ }
+});
+
+/**
+ * @route POST /api/tax/initialize-categories
+ * @desc Initialize default tax categories (admin only)
+ * @access Private
+ */
+router.post('/initialize-categories', auth, async (req, res) => {
+ try {
+ const country = req.body.country || 'IN';
+ await taxService.initializeDefaultCategories(country);
+
+ res.json({
+ success: true,
+ message: `Default tax categories initialized for ${country}`
+ });
+ } catch (error) {
+ console.error('Initialize categories error:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to initialize tax categories'
+ });
+ }
+});
+
+module.exports = router;
diff --git a/server.js b/server.js
index e7e9231..835eff2 100644
--- a/server.js
+++ b/server.js
@@ -243,15 +243,8 @@ app.use('/api/currency', require('./routes/currency'));
app.use('/api/groups', require('./routes/groups'));
app.use('/api/splits', require('./routes/splits'));
app.use('/api/workspaces', require('./routes/workspaces'));
-app.use('/api/approvals', require('./routes/approvals'));
-app.use('/api/investments', require('./routes/investments'));
-app.use('/api/ai', require('./routes/ai'));
-app.use('/api/multicurrency', require('./routes/multicurrency'));
-app.use('/api/collaboration', require('./routes/collaboration'));
-app.use('/api/audit-compliance', require('./routes/auditCompliance'));
-app.use('/api/accounting', require('./routes/accounting'));
-app.use('/api/analytics', require('./routes/analytics'));
-app.use('/api/fraud-detection', require('./routes/fraudDetection'));
+app.use('/api/tax', require('./routes/tax'));
+app.use('/api/reports', require('./routes/reports'));
// Root route to serve the UI
app.get('/', (req, res) => {
diff --git a/services/pdfService.js b/services/pdfService.js
new file mode 100644
index 0000000..09ff33b
--- /dev/null
+++ b/services/pdfService.js
@@ -0,0 +1,603 @@
+const PDFDocument = require('pdfkit');
+const FinancialReport = require('../models/FinancialReport');
+
+class PDFService {
+ /**
+ * Generate PDF from report
+ */
+ async generatePDF(report) {
+ return new Promise((resolve, reject) => {
+ try {
+ const doc = new PDFDocument({
+ size: 'A4',
+ margins: { top: 50, bottom: 50, left: 50, right: 50 },
+ info: {
+ Title: report.title,
+ Author: 'ExpenseFlow',
+ Subject: 'Financial Report',
+ Creator: 'ExpenseFlow Report Generator'
+ }
+ });
+
+ const chunks = [];
+ doc.on('data', chunk => chunks.push(chunk));
+ doc.on('end', () => resolve(Buffer.concat(chunks)));
+ doc.on('error', reject);
+
+ // Generate content based on report type
+ this.addHeader(doc, report);
+
+ switch (report.reportType) {
+ case 'income_statement':
+ this.addIncomeStatement(doc, report);
+ break;
+ case 'expense_summary':
+ this.addExpenseSummary(doc, report);
+ break;
+ case 'profit_loss':
+ this.addProfitLoss(doc, report);
+ break;
+ case 'tax_report':
+ this.addTaxReport(doc, report);
+ break;
+ case 'category_breakdown':
+ this.addCategoryBreakdown(doc, report);
+ break;
+ case 'monthly_comparison':
+ this.addMonthlyComparison(doc, report);
+ break;
+ case 'annual_summary':
+ this.addAnnualSummary(doc, report);
+ break;
+ default:
+ this.addGenericReport(doc, report);
+ }
+
+ this.addFooter(doc);
+ doc.end();
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ /**
+ * Add header to PDF
+ */
+ addHeader(doc, report) {
+ // Logo/Brand area
+ doc.fontSize(24)
+ .fillColor('#2563eb')
+ .text('ExpenseFlow', { align: 'center' });
+
+ doc.moveDown(0.5);
+
+ // Report title
+ doc.fontSize(18)
+ .fillColor('#1f2937')
+ .text(report.title, { align: 'center' });
+
+ // Date range
+ const startDate = new Date(report.dateRange.startDate).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ });
+ const endDate = new Date(report.dateRange.endDate).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ });
+
+ doc.fontSize(10)
+ .fillColor('#6b7280')
+ .text(`Period: ${startDate} - ${endDate}`, { align: 'center' });
+
+ doc.moveDown();
+ this.addDivider(doc);
+ doc.moveDown();
+ }
+
+ /**
+ * Add footer to PDF
+ */
+ addFooter(doc) {
+ const pages = doc.bufferedPageRange();
+ for (let i = 0; i < pages.count; i++) {
+ doc.switchToPage(i);
+
+ // Footer line
+ doc.strokeColor('#e5e7eb')
+ .lineWidth(1)
+ .moveTo(50, doc.page.height - 50)
+ .lineTo(doc.page.width - 50, doc.page.height - 50)
+ .stroke();
+
+ // Page number
+ doc.fontSize(8)
+ .fillColor('#9ca3af')
+ .text(
+ `Page ${i + 1} of ${pages.count}`,
+ 50,
+ doc.page.height - 35,
+ { align: 'center' }
+ );
+
+ // Generated timestamp
+ doc.text(
+ `Generated on ${new Date().toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ })} by ExpenseFlow`,
+ 50,
+ doc.page.height - 35,
+ { align: 'right' }
+ );
+ }
+ }
+
+ /**
+ * Add income statement content
+ */
+ addIncomeStatement(doc, report) {
+ // Summary section
+ this.addSectionTitle(doc, 'Financial Summary');
+
+ const summaryData = [
+ ['Gross Income', this.formatCurrency(report.totalIncome, report.currency)],
+ ['Total Expenses', this.formatCurrency(report.totalExpenses, report.currency)],
+ ['Net Income', this.formatCurrency(report.netIncome, report.currency)],
+ ['Savings Rate', `${report.savingsRate}%`]
+ ];
+
+ this.addTable(doc, summaryData);
+ doc.moveDown();
+
+ // Income breakdown
+ if (report.incomeBreakdown && report.incomeBreakdown.length > 0) {
+ this.addSectionTitle(doc, 'Income Breakdown');
+ const incomeData = report.incomeBreakdown.map(i => [
+ this.capitalize(i.category),
+ this.formatCurrency(i.amount, report.currency),
+ `${i.count} transactions`
+ ]);
+ this.addTable(doc, incomeData, ['Category', 'Amount', 'Transactions']);
+ doc.moveDown();
+ }
+
+ // Expense breakdown
+ if (report.expenseBreakdown && report.expenseBreakdown.length > 0) {
+ this.addSectionTitle(doc, 'Expense Breakdown');
+ const expenseData = report.expenseBreakdown.map(e => [
+ this.capitalize(e.category),
+ this.formatCurrency(e.amount, report.currency),
+ `${e.count} transactions`
+ ]);
+ this.addTable(doc, expenseData, ['Category', 'Amount', 'Transactions']);
+ }
+ }
+
+ /**
+ * Add expense summary content
+ */
+ addExpenseSummary(doc, report) {
+ // Summary stats
+ this.addSectionTitle(doc, 'Expense Overview');
+
+ const summaryData = [
+ ['Total Expenses', this.formatCurrency(report.totalExpenses, report.currency)],
+ ['Average Monthly', this.formatCurrency(report.averageMonthlyExpense || 0, report.currency)]
+ ];
+
+ this.addTable(doc, summaryData);
+ doc.moveDown();
+
+ // Category breakdown
+ if (report.expenseBreakdown && report.expenseBreakdown.length > 0) {
+ this.addSectionTitle(doc, 'Expense by Category');
+ const categoryData = report.expenseBreakdown.map(e => [
+ this.capitalize(e.category),
+ this.formatCurrency(e.amount, report.currency),
+ `${e.percentage || 0}%`,
+ `${e.count} txns`
+ ]);
+ this.addTable(doc, categoryData, ['Category', 'Amount', '% of Total', 'Count']);
+ doc.moveDown();
+ }
+
+ // Top expenses
+ if (report.topExpenses && report.topExpenses.length > 0) {
+ this.addSectionTitle(doc, 'Top 10 Expenses');
+ const topData = report.topExpenses.map(e => [
+ e.description.substring(0, 30),
+ this.capitalize(e.category),
+ this.formatCurrency(e.amount, report.currency)
+ ]);
+ this.addTable(doc, topData, ['Description', 'Category', 'Amount']);
+ }
+ }
+
+ /**
+ * Add profit/loss content
+ */
+ addProfitLoss(doc, report) {
+ // P&L Summary
+ this.addSectionTitle(doc, 'Profit & Loss Statement');
+
+ doc.fontSize(12)
+ .fillColor('#1f2937');
+
+ // Revenue section
+ doc.font('Helvetica-Bold').text('REVENUE');
+ doc.font('Helvetica');
+
+ if (report.incomeBreakdown) {
+ for (const income of report.incomeBreakdown) {
+ doc.text(` ${this.capitalize(income.category)}`, { continued: true })
+ .text(this.formatCurrency(income.amount, report.currency), { align: 'right' });
+ }
+ }
+
+ doc.moveDown(0.5);
+ doc.font('Helvetica-Bold')
+ .text('Total Revenue', { continued: true })
+ .text(this.formatCurrency(report.totalIncome, report.currency), { align: 'right' });
+
+ doc.moveDown();
+ this.addDivider(doc);
+ doc.moveDown();
+
+ // Expenses section
+ doc.font('Helvetica-Bold').text('EXPENSES');
+ doc.font('Helvetica');
+
+ if (report.summary) {
+ if (report.summary.operatingExpenses > 0) {
+ doc.text(' Operating Expenses', { continued: true })
+ .text(this.formatCurrency(report.summary.operatingExpenses, report.currency), { align: 'right' });
+ }
+ if (report.summary.discretionaryExpenses > 0) {
+ doc.text(' Discretionary Expenses', { continued: true })
+ .text(this.formatCurrency(report.summary.discretionaryExpenses, report.currency), { align: 'right' });
+ }
+ if (report.summary.essentialExpenses > 0) {
+ doc.text(' Essential Expenses', { continued: true })
+ .text(this.formatCurrency(report.summary.essentialExpenses, report.currency), { align: 'right' });
+ }
+ if (report.summary.otherExpenses > 0) {
+ doc.text(' Other Expenses', { continued: true })
+ .text(this.formatCurrency(report.summary.otherExpenses, report.currency), { align: 'right' });
+ }
+ }
+
+ doc.moveDown(0.5);
+ doc.font('Helvetica-Bold')
+ .text('Total Expenses', { continued: true })
+ .text(this.formatCurrency(report.totalExpenses, report.currency), { align: 'right' });
+
+ doc.moveDown();
+ this.addDivider(doc, '#2563eb', 2);
+ doc.moveDown();
+
+ // Net Income
+ const netColor = report.netIncome >= 0 ? '#059669' : '#dc2626';
+ doc.font('Helvetica-Bold')
+ .fontSize(14)
+ .fillColor(netColor)
+ .text('NET INCOME', { continued: true })
+ .text(this.formatCurrency(report.netIncome, report.currency), { align: 'right' });
+
+ doc.fillColor('#1f2937');
+
+ if (report.summary?.profitMargin) {
+ doc.moveDown()
+ .fontSize(10)
+ .fillColor('#6b7280')
+ .text(`Profit Margin: ${report.summary.profitMargin}%`, { align: 'right' });
+ }
+ }
+
+ /**
+ * Add tax report content
+ */
+ addTaxReport(doc, report) {
+ this.addSectionTitle(doc, 'Tax Summary');
+
+ if (report.taxSummary) {
+ const taxData = [
+ ['Gross Income', this.formatCurrency(report.taxSummary.grossIncome, report.currency)],
+ ['Total Deductions', this.formatCurrency(report.taxSummary.totalDeductions, report.currency)],
+ ['Taxable Income', this.formatCurrency(report.taxSummary.taxableIncome, report.currency)],
+ ['Tax Liability', this.formatCurrency(report.taxSummary.taxLiability, report.currency)],
+ ['Effective Tax Rate', `${report.taxSummary.effectiveRate}%`],
+ ['Tax Regime', this.capitalize(report.taxSummary.regime)]
+ ];
+
+ this.addTable(doc, taxData);
+ doc.moveDown();
+ }
+
+ // Tax deductions
+ if (report.taxDeductions && report.taxDeductions.length > 0) {
+ this.addSectionTitle(doc, 'Tax Deductions');
+ const deductionData = report.taxDeductions.map(d => [
+ this.capitalize(d.category),
+ d.section || 'N/A',
+ this.formatCurrency(d.amount, report.currency),
+ this.formatCurrency(d.deductible, report.currency)
+ ]);
+ this.addTable(doc, deductionData, ['Category', 'Section', 'Total', 'Deductible']);
+ }
+
+ // Disclaimer
+ doc.moveDown(2);
+ doc.fontSize(8)
+ .fillColor('#6b7280')
+ .text('Disclaimer: This report is for informational purposes only and should not be considered as professional tax advice. Please consult a qualified tax professional for accurate tax planning and filing.', {
+ align: 'justify'
+ });
+ }
+
+ /**
+ * Add category breakdown content
+ */
+ addCategoryBreakdown(doc, report) {
+ // Income categories
+ if (report.incomeBreakdown && report.incomeBreakdown.length > 0) {
+ this.addSectionTitle(doc, 'Income by Category');
+ const incomeData = report.incomeBreakdown.map(i => [
+ this.capitalize(i.category),
+ this.formatCurrency(i.amount, report.currency),
+ `${i.percentage || 0}%`
+ ]);
+ this.addTable(doc, incomeData, ['Category', 'Amount', '% of Total']);
+ doc.moveDown();
+ }
+
+ // Expense categories
+ if (report.expenseBreakdown && report.expenseBreakdown.length > 0) {
+ this.addSectionTitle(doc, 'Expenses by Category');
+ const expenseData = report.expenseBreakdown.map(e => [
+ this.capitalize(e.category),
+ this.formatCurrency(e.amount, report.currency),
+ `${e.percentage || 0}%`,
+ `Avg: ${this.formatCurrency(e.avgAmount || 0, report.currency)}`
+ ]);
+ this.addTable(doc, expenseData, ['Category', 'Amount', '% of Total', 'Average']);
+ }
+
+ // Summary
+ doc.moveDown();
+ this.addDivider(doc);
+ doc.moveDown();
+
+ doc.font('Helvetica-Bold')
+ .text('Total Income: ', { continued: true })
+ .font('Helvetica')
+ .text(this.formatCurrency(report.totalIncome, report.currency));
+
+ doc.font('Helvetica-Bold')
+ .text('Total Expenses: ', { continued: true })
+ .font('Helvetica')
+ .text(this.formatCurrency(report.totalExpenses, report.currency));
+
+ const netColor = report.netIncome >= 0 ? '#059669' : '#dc2626';
+ doc.font('Helvetica-Bold')
+ .fillColor(netColor)
+ .text('Net Income: ', { continued: true })
+ .font('Helvetica')
+ .text(this.formatCurrency(report.netIncome, report.currency));
+ }
+
+ /**
+ * Add monthly comparison content
+ */
+ addMonthlyComparison(doc, report) {
+ this.addSectionTitle(doc, 'Monthly Financial Overview');
+
+ if (report.monthlyTrends && report.monthlyTrends.length > 0) {
+ const monthlyData = report.monthlyTrends.map(m => [
+ m.month,
+ this.formatCurrency(m.income, report.currency),
+ this.formatCurrency(m.expenses, report.currency),
+ this.formatCurrency(m.netSavings, report.currency)
+ ]);
+ this.addTable(doc, monthlyData, ['Month', 'Income', 'Expenses', 'Net Savings']);
+ }
+
+ doc.moveDown();
+
+ // Summary stats
+ this.addSectionTitle(doc, 'Period Summary');
+ const summaryData = [
+ ['Total Income', this.formatCurrency(report.totalIncome, report.currency)],
+ ['Total Expenses', this.formatCurrency(report.totalExpenses, report.currency)],
+ ['Net Income', this.formatCurrency(report.netIncome, report.currency)],
+ ['Avg Monthly Income', this.formatCurrency(report.averageMonthlyIncome || 0, report.currency)],
+ ['Avg Monthly Expense', this.formatCurrency(report.averageMonthlyExpense || 0, report.currency)]
+ ];
+ this.addTable(doc, summaryData);
+ }
+
+ /**
+ * Add annual summary content
+ */
+ addAnnualSummary(doc, report) {
+ // Key metrics
+ this.addSectionTitle(doc, 'Annual Financial Summary');
+
+ const summaryData = [
+ ['Total Income', this.formatCurrency(report.totalIncome, report.currency)],
+ ['Total Expenses', this.formatCurrency(report.totalExpenses, report.currency)],
+ ['Net Savings', this.formatCurrency(report.netIncome, report.currency)],
+ ['Savings Rate', `${report.savingsRate}%`]
+ ];
+ this.addTable(doc, summaryData);
+ doc.moveDown();
+
+ // Monthly averages
+ if (report.summary) {
+ this.addSectionTitle(doc, 'Monthly Averages');
+ const avgData = [
+ ['Average Monthly Income', this.formatCurrency(report.summary.averageMonthlyIncome || 0, report.currency)],
+ ['Average Monthly Expense', this.formatCurrency(report.summary.averageMonthlyExpense || 0, report.currency)],
+ ['Highest Expense Month', `${report.summary.highestExpenseMonth || 'N/A'} (${this.formatCurrency(report.summary.highestExpenseAmount || 0, report.currency)})`],
+ ['Lowest Expense Month', `${report.summary.lowestExpenseMonth || 'N/A'} (${this.formatCurrency(report.summary.lowestExpenseAmount || 0, report.currency)})`]
+ ];
+ this.addTable(doc, avgData);
+ doc.moveDown();
+ }
+
+ // Category breakdown
+ if (report.expenseBreakdown && report.expenseBreakdown.length > 0) {
+ this.addSectionTitle(doc, 'Top Expense Categories');
+ const top5 = report.expenseBreakdown.slice(0, 5);
+ const categoryData = top5.map(e => [
+ this.capitalize(e.category),
+ this.formatCurrency(e.amount, report.currency),
+ `${e.percentage || 0}%`
+ ]);
+ this.addTable(doc, categoryData, ['Category', 'Amount', '% of Total']);
+ }
+ }
+
+ /**
+ * Add generic report content
+ */
+ addGenericReport(doc, report) {
+ doc.fontSize(12)
+ .text(`Report Type: ${report.reportType}`);
+ doc.moveDown();
+
+ if (report.totalIncome !== undefined) {
+ doc.text(`Total Income: ${this.formatCurrency(report.totalIncome, report.currency)}`);
+ }
+ if (report.totalExpenses !== undefined) {
+ doc.text(`Total Expenses: ${this.formatCurrency(report.totalExpenses, report.currency)}`);
+ }
+ if (report.netIncome !== undefined) {
+ doc.text(`Net Income: ${this.formatCurrency(report.netIncome, report.currency)}`);
+ }
+ }
+
+ /**
+ * Helper: Add section title
+ */
+ addSectionTitle(doc, title) {
+ doc.fontSize(14)
+ .font('Helvetica-Bold')
+ .fillColor('#1f2937')
+ .text(title);
+ doc.moveDown(0.5);
+ doc.font('Helvetica');
+ }
+
+ /**
+ * Helper: Add divider line
+ */
+ addDivider(doc, color = '#e5e7eb', width = 1) {
+ doc.strokeColor(color)
+ .lineWidth(width)
+ .moveTo(50, doc.y)
+ .lineTo(doc.page.width - 50, doc.y)
+ .stroke();
+ }
+
+ /**
+ * Helper: Add table
+ */
+ addTable(doc, rows, headers = null) {
+ const startX = 50;
+ const colWidth = (doc.page.width - 100) / (headers ? headers.length : rows[0]?.length || 2);
+ let y = doc.y;
+
+ // Headers
+ if (headers) {
+ doc.font('Helvetica-Bold')
+ .fontSize(10)
+ .fillColor('#4b5563');
+
+ headers.forEach((header, i) => {
+ doc.text(header, startX + (i * colWidth), y, {
+ width: colWidth,
+ align: i === 0 ? 'left' : 'right'
+ });
+ });
+
+ y = doc.y + 5;
+ doc.strokeColor('#e5e7eb')
+ .lineWidth(0.5)
+ .moveTo(startX, y)
+ .lineTo(doc.page.width - 50, y)
+ .stroke();
+ y += 10;
+ }
+
+ // Rows
+ doc.font('Helvetica')
+ .fontSize(10)
+ .fillColor('#1f2937');
+
+ for (const row of rows) {
+ const rowY = y;
+ row.forEach((cell, i) => {
+ doc.text(String(cell), startX + (i * colWidth), rowY, {
+ width: colWidth,
+ align: i === 0 ? 'left' : 'right'
+ });
+ });
+ y = doc.y + 5;
+
+ // Check for page break
+ if (y > doc.page.height - 100) {
+ doc.addPage();
+ y = 50;
+ }
+ }
+
+ doc.y = y;
+ }
+
+ /**
+ * Helper: Format currency
+ */
+ formatCurrency(amount, currency = 'INR') {
+ const symbols = { INR: '₹', USD: '$', EUR: '€', GBP: '£' };
+ const symbol = symbols[currency] || currency;
+ const formatted = Math.abs(amount).toLocaleString('en-IN', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ });
+ return amount < 0 ? `-${symbol}${formatted}` : `${symbol}${formatted}`;
+ }
+
+ /**
+ * Helper: Capitalize string
+ */
+ capitalize(str) {
+ if (!str) return '';
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
+ }
+
+ /**
+ * Generate PDF for report ID
+ */
+ async generatePDFForReport(reportId, userId) {
+ const report = await FinancialReport.findOne({
+ _id: reportId,
+ user: userId,
+ status: 'ready'
+ });
+
+ if (!report) {
+ throw new Error('Report not found or not ready');
+ }
+
+ return this.generatePDF(report);
+ }
+}
+
+module.exports = new PDFService();
diff --git a/services/reportService.js b/services/reportService.js
new file mode 100644
index 0000000..d08541d
--- /dev/null
+++ b/services/reportService.js
@@ -0,0 +1,602 @@
+const FinancialReport = require('../models/FinancialReport');
+const Expense = require('../models/Expense');
+const TaxProfile = require('../models/TaxProfile');
+const taxService = require('./taxService');
+const mongoose = require('mongoose');
+
+class ReportService {
+ /**
+ * Generate financial report
+ */
+ async generateReport(userId, reportType, options = {}) {
+ const {
+ startDate = new Date(new Date().getFullYear(), 0, 1),
+ endDate = new Date(),
+ currency = 'INR',
+ includeForecasts = false,
+ workspaceId = null
+ } = options;
+
+ const start = new Date(startDate);
+ const end = new Date(endDate);
+
+ // Check for existing report
+ const existingReport = await FinancialReport.findOne({
+ user: userId,
+ reportType,
+ 'dateRange.startDate': start,
+ 'dateRange.endDate': end,
+ status: { $in: ['ready', 'processing'] }
+ });
+
+ if (existingReport && existingReport.status === 'ready') {
+ return existingReport;
+ }
+
+ // Create new report
+ const report = new FinancialReport({
+ user: userId,
+ reportType,
+ title: this.generateTitle(reportType, start, end),
+ dateRange: { startDate: start, endDate: end },
+ currency,
+ workspace: workspaceId,
+ status: 'processing'
+ });
+
+ await report.save();
+
+ try {
+ // Generate report data based on type
+ let reportData;
+ switch (reportType) {
+ case 'income_statement':
+ reportData = await this.generateIncomeStatement(userId, start, end);
+ break;
+ case 'expense_summary':
+ reportData = await this.generateExpenseSummary(userId, start, end);
+ break;
+ case 'profit_loss':
+ reportData = await this.generateProfitLoss(userId, start, end);
+ break;
+ case 'tax_report':
+ reportData = await this.generateTaxReport(userId, start, end);
+ break;
+ case 'category_breakdown':
+ reportData = await this.generateCategoryBreakdown(userId, start, end);
+ break;
+ case 'monthly_comparison':
+ reportData = await this.generateMonthlyComparison(userId, start, end);
+ break;
+ case 'annual_summary':
+ reportData = await this.generateAnnualSummary(userId, start, end);
+ break;
+ default:
+ throw new Error(`Unknown report type: ${reportType}`);
+ }
+
+ // Update report with data
+ Object.assign(report, reportData);
+ report.status = 'ready';
+ report.generatedAt = new Date();
+
+ await report.save();
+ return report;
+ } catch (error) {
+ report.status = 'failed';
+ report.error = error.message;
+ await report.save();
+ throw error;
+ }
+ }
+
+ /**
+ * Generate income statement
+ */
+ async generateIncomeStatement(userId, startDate, endDate) {
+ const [income, expenses] = await Promise.all([
+ Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ type: 'income',
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: '$category',
+ total: { $sum: '$amount' },
+ count: { $sum: 1 }
+ }
+ }
+ ]),
+ Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ type: 'expense',
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: '$category',
+ total: { $sum: '$amount' },
+ count: { $sum: 1 }
+ }
+ }
+ ])
+ ]);
+
+ const totalIncome = income.reduce((sum, i) => sum + i.total, 0);
+ const totalExpenses = expenses.reduce((sum, e) => sum + e.total, 0);
+
+ return {
+ incomeBreakdown: income.map(i => ({
+ category: i._id || 'Other',
+ amount: i.total,
+ count: i.count
+ })),
+ expenseBreakdown: expenses.map(e => ({
+ category: e._id || 'Other',
+ amount: e.total,
+ count: e.count
+ })),
+ totalIncome,
+ totalExpenses,
+ netIncome: totalIncome - totalExpenses,
+ savingsRate: totalIncome > 0 ? ((totalIncome - totalExpenses) / totalIncome * 100).toFixed(2) : 0,
+ summary: {
+ grossIncome: totalIncome,
+ operatingExpenses: totalExpenses,
+ netProfit: totalIncome - totalExpenses,
+ profitMargin: totalIncome > 0 ? ((totalIncome - totalExpenses) / totalIncome * 100).toFixed(2) : 0
+ }
+ };
+ }
+
+ /**
+ * Generate expense summary
+ */
+ async generateExpenseSummary(userId, startDate, endDate) {
+ const [categoryBreakdown, monthlyTrends, topExpenses] = await Promise.all([
+ Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ type: 'expense',
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: '$category',
+ total: { $sum: '$amount' },
+ count: { $sum: 1 },
+ avgAmount: { $avg: '$amount' }
+ }
+ },
+ { $sort: { total: -1 } }
+ ]),
+ Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ type: 'expense',
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: {
+ year: { $year: '$date' },
+ month: { $month: '$date' }
+ },
+ total: { $sum: '$amount' },
+ count: { $sum: 1 }
+ }
+ },
+ { $sort: { '_id.year': 1, '_id.month': 1 } }
+ ]),
+ Expense.find({
+ user: userId,
+ type: 'expense',
+ date: { $gte: startDate, $lte: endDate }
+ })
+ .sort({ amount: -1 })
+ .limit(10)
+ .select('description amount category date')
+ ]);
+
+ const totalExpenses = categoryBreakdown.reduce((sum, c) => sum + c.total, 0);
+
+ return {
+ expenseBreakdown: categoryBreakdown.map(c => ({
+ category: c._id || 'Other',
+ amount: c.total,
+ count: c.count,
+ avgAmount: Math.round(c.avgAmount),
+ percentage: totalExpenses > 0 ? ((c.total / totalExpenses) * 100).toFixed(2) : 0
+ })),
+ totalExpenses,
+ monthlyTrends: monthlyTrends.map(m => ({
+ month: `${m._id.year}-${String(m._id.month).padStart(2, '0')}`,
+ income: 0,
+ expenses: m.total,
+ netSavings: -m.total,
+ transactionCount: m.count
+ })),
+ topExpenses: topExpenses.map(e => ({
+ description: e.description,
+ amount: e.amount,
+ category: e.category,
+ date: e.date
+ })),
+ averageMonthlyExpense: monthlyTrends.length > 0
+ ? totalExpenses / monthlyTrends.length
+ : 0
+ };
+ }
+
+ /**
+ * Generate profit/loss statement
+ */
+ async generateProfitLoss(userId, startDate, endDate) {
+ const data = await Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: {
+ type: '$type',
+ category: '$category'
+ },
+ total: { $sum: '$amount' },
+ count: { $sum: 1 }
+ }
+ }
+ ]);
+
+ const income = data.filter(d => d._id.type === 'income');
+ const expenses = data.filter(d => d._id.type === 'expense');
+
+ const totalIncome = income.reduce((sum, i) => sum + i.total, 0);
+ const totalExpenses = expenses.reduce((sum, e) => sum + e.total, 0);
+ const netIncome = totalIncome - totalExpenses;
+
+ // Categorize expenses
+ const operatingExpenses = expenses
+ .filter(e => ['utilities', 'transport', 'food'].includes(e._id.category))
+ .reduce((sum, e) => sum + e.total, 0);
+
+ const discretionaryExpenses = expenses
+ .filter(e => ['shopping', 'entertainment'].includes(e._id.category))
+ .reduce((sum, e) => sum + e.total, 0);
+
+ const essentialExpenses = expenses
+ .filter(e => ['healthcare'].includes(e._id.category))
+ .reduce((sum, e) => sum + e.total, 0);
+
+ return {
+ incomeBreakdown: income.map(i => ({
+ category: i._id.category || 'Other',
+ amount: i.total,
+ count: i.count
+ })),
+ expenseBreakdown: expenses.map(e => ({
+ category: e._id.category || 'Other',
+ amount: e.total,
+ count: e.count
+ })),
+ totalIncome,
+ totalExpenses,
+ netIncome,
+ summary: {
+ grossIncome: totalIncome,
+ operatingExpenses,
+ discretionaryExpenses,
+ essentialExpenses,
+ otherExpenses: totalExpenses - operatingExpenses - discretionaryExpenses - essentialExpenses,
+ netProfit: netIncome,
+ profitMargin: totalIncome > 0 ? ((netIncome / totalIncome) * 100).toFixed(2) : 0
+ }
+ };
+ }
+
+ /**
+ * Generate tax report
+ */
+ async generateTaxReport(userId, startDate, endDate) {
+ const taxYear = startDate.getFullYear();
+
+ // Get tax calculation
+ const taxCalc = await taxService.calculateTax(userId, taxYear);
+
+ // Get deductible expenses
+ const deductibleExpenses = await taxService.getDeductibleExpenses(userId, startDate, endDate);
+
+ return {
+ totalIncome: taxCalc.grossIncome,
+ totalExpenses: 0, // Will be filled from expense data
+ netIncome: taxCalc.taxableIncome,
+ taxDeductions: deductibleExpenses.map(d => ({
+ category: d.category,
+ section: d.section,
+ amount: d.totalAmount,
+ deductible: d.deductibleAmount
+ })),
+ taxSummary: {
+ grossIncome: taxCalc.grossIncome,
+ totalDeductions: taxCalc.totalDeductions,
+ taxableIncome: taxCalc.taxableIncome,
+ taxLiability: taxCalc.totalTax,
+ effectiveRate: taxCalc.effectiveRate,
+ regime: taxCalc.regime
+ }
+ };
+ }
+
+ /**
+ * Generate category breakdown
+ */
+ async generateCategoryBreakdown(userId, startDate, endDate) {
+ const breakdown = await Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: {
+ type: '$type',
+ category: '$category'
+ },
+ total: { $sum: '$amount' },
+ count: { $sum: 1 },
+ avgAmount: { $avg: '$amount' },
+ minAmount: { $min: '$amount' },
+ maxAmount: { $max: '$amount' }
+ }
+ },
+ { $sort: { total: -1 } }
+ ]);
+
+ const incomeData = breakdown.filter(b => b._id.type === 'income');
+ const expenseData = breakdown.filter(b => b._id.type === 'expense');
+
+ const totalIncome = incomeData.reduce((sum, i) => sum + i.total, 0);
+ const totalExpenses = expenseData.reduce((sum, e) => sum + e.total, 0);
+
+ return {
+ incomeBreakdown: incomeData.map(i => ({
+ category: i._id.category || 'Other',
+ amount: i.total,
+ count: i.count,
+ percentage: totalIncome > 0 ? ((i.total / totalIncome) * 100).toFixed(2) : 0
+ })),
+ expenseBreakdown: expenseData.map(e => ({
+ category: e._id.category || 'Other',
+ amount: e.total,
+ count: e.count,
+ avgAmount: Math.round(e.avgAmount),
+ minAmount: e.minAmount,
+ maxAmount: e.maxAmount,
+ percentage: totalExpenses > 0 ? ((e.total / totalExpenses) * 100).toFixed(2) : 0
+ })),
+ totalIncome,
+ totalExpenses,
+ netIncome: totalIncome - totalExpenses
+ };
+ }
+
+ /**
+ * Generate monthly comparison
+ */
+ async generateMonthlyComparison(userId, startDate, endDate) {
+ const monthlyData = await Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: {
+ year: { $year: '$date' },
+ month: { $month: '$date' },
+ type: '$type'
+ },
+ total: { $sum: '$amount' },
+ count: { $sum: 1 }
+ }
+ },
+ { $sort: { '_id.year': 1, '_id.month': 1 } }
+ ]);
+
+ // Transform into monthly trends
+ const monthlyMap = new Map();
+
+ for (const data of monthlyData) {
+ const key = `${data._id.year}-${String(data._id.month).padStart(2, '0')}`;
+ if (!monthlyMap.has(key)) {
+ monthlyMap.set(key, {
+ month: key,
+ income: 0,
+ expenses: 0,
+ netSavings: 0,
+ transactionCount: 0
+ });
+ }
+
+ const entry = monthlyMap.get(key);
+ if (data._id.type === 'income') {
+ entry.income = data.total;
+ } else {
+ entry.expenses = data.total;
+ }
+ entry.transactionCount += data.count;
+ entry.netSavings = entry.income - entry.expenses;
+ }
+
+ const monthlyTrends = Array.from(monthlyMap.values());
+
+ const totalIncome = monthlyTrends.reduce((sum, m) => sum + m.income, 0);
+ const totalExpenses = monthlyTrends.reduce((sum, m) => sum + m.expenses, 0);
+
+ // Calculate growth rates
+ for (let i = 1; i < monthlyTrends.length; i++) {
+ const prev = monthlyTrends[i - 1];
+ const curr = monthlyTrends[i];
+
+ curr.incomeGrowth = prev.income > 0
+ ? ((curr.income - prev.income) / prev.income * 100).toFixed(2)
+ : 0;
+ curr.expenseGrowth = prev.expenses > 0
+ ? ((curr.expenses - prev.expenses) / prev.expenses * 100).toFixed(2)
+ : 0;
+ }
+
+ return {
+ monthlyTrends,
+ totalIncome,
+ totalExpenses,
+ netIncome: totalIncome - totalExpenses,
+ averageMonthlyIncome: monthlyTrends.length > 0 ? totalIncome / monthlyTrends.length : 0,
+ averageMonthlyExpense: monthlyTrends.length > 0 ? totalExpenses / monthlyTrends.length : 0
+ };
+ }
+
+ /**
+ * Generate annual summary
+ */
+ async generateAnnualSummary(userId, startDate, endDate) {
+ const [incomeStatement, categoryBreakdown, monthlyComp] = await Promise.all([
+ this.generateIncomeStatement(userId, startDate, endDate),
+ this.generateCategoryBreakdown(userId, startDate, endDate),
+ this.generateMonthlyComparison(userId, startDate, endDate)
+ ]);
+
+ // Find highest and lowest months
+ const months = monthlyComp.monthlyTrends;
+ const highestExpenseMonth = months.reduce((max, m) => m.expenses > max.expenses ? m : max, months[0] || { expenses: 0 });
+ const lowestExpenseMonth = months.reduce((min, m) => m.expenses < min.expenses ? m : min, months[0] || { expenses: 0 });
+
+ return {
+ totalIncome: incomeStatement.totalIncome,
+ totalExpenses: incomeStatement.totalExpenses,
+ netIncome: incomeStatement.netIncome,
+ savingsRate: incomeStatement.savingsRate,
+ incomeBreakdown: incomeStatement.incomeBreakdown,
+ expenseBreakdown: categoryBreakdown.expenseBreakdown,
+ monthlyTrends: monthlyComp.monthlyTrends,
+ summary: {
+ ...incomeStatement.summary,
+ averageMonthlyIncome: monthlyComp.averageMonthlyIncome,
+ averageMonthlyExpense: monthlyComp.averageMonthlyExpense,
+ highestExpenseMonth: highestExpenseMonth?.month,
+ highestExpenseAmount: highestExpenseMonth?.expenses || 0,
+ lowestExpenseMonth: lowestExpenseMonth?.month,
+ lowestExpenseAmount: lowestExpenseMonth?.expenses || 0
+ }
+ };
+ }
+
+ /**
+ * Get user's reports
+ */
+ async getUserReports(userId, options = {}) {
+ const {
+ page = 1,
+ limit = 10,
+ reportType,
+ status = 'ready'
+ } = options;
+
+ const query = { user: userId };
+ if (reportType) query.reportType = reportType;
+ if (status) query.status = status;
+
+ const [reports, total] = await Promise.all([
+ FinancialReport.find(query)
+ .sort({ generatedAt: -1 })
+ .skip((page - 1) * limit)
+ .limit(limit)
+ .lean(),
+ FinancialReport.countDocuments(query)
+ ]);
+
+ return {
+ reports,
+ pagination: {
+ page,
+ limit,
+ total,
+ pages: Math.ceil(total / limit)
+ }
+ };
+ }
+
+ /**
+ * Get report by ID
+ */
+ async getReportById(reportId, userId) {
+ const report = await FinancialReport.findOne({
+ _id: reportId,
+ user: userId
+ });
+
+ if (!report) {
+ throw new Error('Report not found');
+ }
+
+ return report;
+ }
+
+ /**
+ * Delete report
+ */
+ async deleteReport(reportId, userId) {
+ const result = await FinancialReport.deleteOne({
+ _id: reportId,
+ user: userId
+ });
+
+ if (result.deletedCount === 0) {
+ throw new Error('Report not found');
+ }
+
+ return { success: true };
+ }
+
+ /**
+ * Generate report title
+ */
+ generateTitle(reportType, startDate, endDate) {
+ const typeNames = {
+ income_statement: 'Income Statement',
+ expense_summary: 'Expense Summary',
+ profit_loss: 'Profit & Loss Statement',
+ tax_report: 'Tax Report',
+ category_breakdown: 'Category Breakdown',
+ monthly_comparison: 'Monthly Comparison',
+ annual_summary: 'Annual Summary'
+ };
+
+ const formatDate = (date) => date.toLocaleDateString('en-US', {
+ month: 'short',
+ year: 'numeric'
+ });
+
+ return `${typeNames[reportType] || reportType} - ${formatDate(startDate)} to ${formatDate(endDate)}`;
+ }
+}
+
+module.exports = new ReportService();
diff --git a/services/taxService.js b/services/taxService.js
index 04d82c0..e371022 100644
--- a/services/taxService.js
+++ b/services/taxService.js
@@ -1,7 +1,426 @@
+const TaxProfile = require('../models/TaxProfile');
+const TaxCategory = require('../models/TaxCategory');
+const Expense = require('../models/Expense');
+const mongoose = require('mongoose');
+
class TaxService {
- init() {
- console.log('Tax service initialized');
+ /**
+ * Get or create tax profile for user
+ */
+ async getOrCreateProfile(userId, taxYear = new Date().getFullYear()) {
+ let profile = await TaxProfile.findOne({ user: userId, taxYear });
+
+ if (!profile) {
+ const defaultBrackets = TaxProfile.getDefaultBrackets('IN', 'new');
+ const defaultDeductions = TaxProfile.getDefaultDeductions('IN');
+
+ profile = new TaxProfile({
+ user: userId,
+ taxYear,
+ country: 'IN',
+ regime: 'new',
+ taxBrackets: defaultBrackets,
+ availableDeductions: defaultDeductions
+ });
+
+ await profile.save();
+ }
+
+ return profile;
+ }
+
+ /**
+ * Update tax profile
+ */
+ async updateProfile(userId, taxYear, updates) {
+ const profile = await this.getOrCreateProfile(userId, taxYear);
+
+ // If country or regime changes, update brackets and deductions
+ if (updates.country && updates.country !== profile.country) {
+ updates.taxBrackets = TaxProfile.getDefaultBrackets(updates.country, updates.regime || 'new');
+ updates.availableDeductions = TaxProfile.getDefaultDeductions(updates.country);
+ } else if (updates.regime && updates.regime !== profile.regime) {
+ updates.taxBrackets = TaxProfile.getDefaultBrackets(profile.country, updates.regime);
+ }
+
+ Object.assign(profile, updates);
+ await profile.save();
+
+ return profile;
+ }
+
+ /**
+ * Calculate tax liability
+ */
+ async calculateTax(userId, taxYear = new Date().getFullYear(), options = {}) {
+ const profile = await this.getOrCreateProfile(userId, taxYear);
+
+ // Get date range for tax year
+ const startDate = new Date(taxYear, 3, 1); // April 1 (Indian FY)
+ const endDate = new Date(taxYear + 1, 2, 31); // March 31
+
+ // Get all income and expenses for the year
+ const [incomeData, expenseData] = await Promise.all([
+ Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ type: 'income',
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: null,
+ totalIncome: { $sum: '$amount' },
+ count: { $sum: 1 }
+ }
+ }
+ ]),
+ this.getDeductibleExpenses(userId, startDate, endDate)
+ ]);
+
+ const grossIncome = incomeData[0]?.totalIncome || 0;
+
+ // Calculate deductions
+ const deductions = this.calculateDeductions(profile, expenseData, options.customDeductions || []);
+
+ // Calculate taxable income
+ let taxableIncome = grossIncome - profile.standardDeduction - deductions.total;
+ taxableIncome = Math.max(0, taxableIncome);
+
+ // Calculate tax using brackets
+ const taxCalculation = this.calculateTaxFromBrackets(taxableIncome, profile.taxBrackets);
+
+ // Add surcharge and cess
+ const surcharge = this.calculateSurcharge(taxCalculation.tax, taxableIncome, profile.country);
+ const cess = (taxCalculation.tax + surcharge) * 0.04; // 4% Health and Education Cess
+
+ const totalTax = taxCalculation.tax + surcharge + cess;
+
+ // Calculate effective tax rate
+ const effectiveRate = grossIncome > 0 ? (totalTax / grossIncome) * 100 : 0;
+
+ // Calculate tax payable after TDS and advance tax
+ const taxPayable = Math.max(0, totalTax - profile.tdsDeducted - profile.advanceTaxPaid);
+
+ return {
+ taxYear,
+ country: profile.country,
+ regime: profile.regime,
+ grossIncome,
+ standardDeduction: profile.standardDeduction,
+ deductions: deductions.breakdown,
+ totalDeductions: deductions.total + profile.standardDeduction,
+ taxableIncome,
+ taxCalculation: taxCalculation.breakdown,
+ baseTax: taxCalculation.tax,
+ surcharge,
+ cess,
+ totalTax,
+ effectiveRate: Math.round(effectiveRate * 100) / 100,
+ tdsDeducted: profile.tdsDeducted,
+ advanceTaxPaid: profile.advanceTaxPaid,
+ taxPayable,
+ taxRefund: taxPayable < 0 ? Math.abs(taxPayable) : 0
+ };
+ }
+
+ /**
+ * Calculate tax from brackets
+ */
+ calculateTaxFromBrackets(taxableIncome, brackets) {
+ let remainingIncome = taxableIncome;
+ let totalTax = 0;
+ const breakdown = [];
+
+ for (const bracket of brackets) {
+ if (remainingIncome <= 0) break;
+
+ const bracketMin = bracket.minIncome;
+ const bracketMax = bracket.maxIncome || Infinity;
+
+ if (taxableIncome > bracketMin) {
+ const taxableInBracket = Math.min(
+ remainingIncome,
+ bracketMax - bracketMin + 1
+ );
+
+ if (taxableInBracket > 0) {
+ const taxInBracket = (taxableInBracket * bracket.rate) / 100;
+ totalTax += taxInBracket;
+
+ breakdown.push({
+ range: bracket.maxIncome
+ ? `₹${bracketMin.toLocaleString()} - ₹${bracket.maxIncome.toLocaleString()}`
+ : `Above ₹${bracketMin.toLocaleString()}`,
+ rate: bracket.rate,
+ taxableAmount: taxableInBracket,
+ tax: taxInBracket
+ });
+
+ remainingIncome -= taxableInBracket;
+ }
+ }
+ }
+
+ return { tax: totalTax, breakdown };
+ }
+
+ /**
+ * Calculate surcharge based on income
+ */
+ calculateSurcharge(baseTax, taxableIncome, country) {
+ if (country !== 'IN') return 0;
+
+ if (taxableIncome > 50000000) return baseTax * 0.37; // 37% for > 5 Cr
+ if (taxableIncome > 20000000) return baseTax * 0.25; // 25% for > 2 Cr
+ if (taxableIncome > 10000000) return baseTax * 0.15; // 15% for > 1 Cr
+ if (taxableIncome > 5000000) return baseTax * 0.10; // 10% for > 50L
+
+ return 0;
+ }
+
+ /**
+ * Get deductible expenses
+ */
+ async getDeductibleExpenses(userId, startDate, endDate) {
+ const taxCategories = await TaxCategory.find({
+ type: { $in: ['deductible', 'partially_deductible'] },
+ isActive: true
+ });
+
+ const expenses = await Expense.aggregate([
+ {
+ $match: {
+ user: new mongoose.Types.ObjectId(userId),
+ type: 'expense',
+ date: { $gte: startDate, $lte: endDate }
+ }
+ },
+ {
+ $group: {
+ _id: '$category',
+ total: { $sum: '$amount' },
+ count: { $sum: 1 },
+ expenses: { $push: { description: '$description', amount: '$amount', date: '$date' } }
+ }
+ }
+ ]);
+
+ const deductibleExpenses = [];
+
+ for (const expense of expenses) {
+ // Find matching tax category
+ const taxCategory = taxCategories.find(tc =>
+ tc.categoryMappings.some(cm => cm.expenseCategory === expense._id)
+ );
+
+ if (taxCategory) {
+ const mapping = taxCategory.categoryMappings.find(cm => cm.expenseCategory === expense._id);
+ const deductibleAmount = (expense.total * (mapping?.deductiblePercentage || 0)) / 100;
+
+ deductibleExpenses.push({
+ category: expense._id,
+ totalAmount: expense.total,
+ deductibleAmount,
+ deductiblePercentage: mapping?.deductiblePercentage || 0,
+ taxCategory: taxCategory.name,
+ section: taxCategory.section,
+ count: expense.count
+ });
+ }
+ }
+
+ return deductibleExpenses;
+ }
+
+ /**
+ * Calculate all deductions
+ */
+ calculateDeductions(profile, expenseData, customDeductions = []) {
+ const breakdown = [];
+ let total = 0;
+
+ // Calculate deductions from expense categories
+ for (const expense of expenseData) {
+ if (expense.deductibleAmount > 0) {
+ // Check if there's a limit for this deduction
+ const deduction = profile.availableDeductions.find(d => d.code === expense.section);
+ const limit = deduction?.maxLimit || Infinity;
+ const actualDeduction = Math.min(expense.deductibleAmount, limit);
+
+ breakdown.push({
+ name: expense.taxCategory,
+ section: expense.section,
+ claimed: expense.deductibleAmount,
+ limit: limit === Infinity ? null : limit,
+ allowed: actualDeduction
+ });
+
+ total += actualDeduction;
+ }
+ }
+
+ // Add custom deductions from profile
+ for (const custom of profile.customDeductions || []) {
+ const deduction = profile.availableDeductions.find(d => d.code === custom.section);
+ const limit = deduction?.maxLimit || Infinity;
+ const actualDeduction = Math.min(custom.amount, limit);
+
+ breakdown.push({
+ name: custom.name,
+ section: custom.section,
+ claimed: custom.amount,
+ limit: limit === Infinity ? null : limit,
+ allowed: actualDeduction
+ });
+
+ total += actualDeduction;
+ }
+
+ // Add additional custom deductions passed in options
+ for (const custom of customDeductions) {
+ breakdown.push({
+ name: custom.name,
+ section: custom.section || 'Custom',
+ claimed: custom.amount,
+ limit: null,
+ allowed: custom.amount
+ });
+
+ total += custom.amount;
+ }
+
+ return { breakdown, total };
+ }
+
+ /**
+ * Get tax-deductible expense categories for tagging
+ */
+ async getDeductibleCategories(country = 'IN') {
+ const categories = await TaxCategory.find({
+ country,
+ type: { $in: ['deductible', 'partially_deductible'] },
+ isActive: true
+ }).select('code name description section maxDeductionLimit keywords categoryMappings');
+
+ return categories;
+ }
+
+ /**
+ * Auto-tag expense as tax-deductible
+ */
+ async autoTagExpense(expense) {
+ const categories = await TaxCategory.find({
+ isActive: true,
+ type: { $in: ['deductible', 'partially_deductible'] }
+ });
+
+ const description = expense.description.toLowerCase();
+ const category = expense.category;
+
+ for (const taxCat of categories) {
+ // Check keywords
+ const keywordMatch = taxCat.keywords.some(kw => description.includes(kw));
+
+ // Check category mapping
+ const categoryMatch = taxCat.categoryMappings.some(cm => cm.expenseCategory === category);
+
+ if (keywordMatch || categoryMatch) {
+ return {
+ isTaxDeductible: true,
+ taxCategory: taxCat.code,
+ taxCategoryName: taxCat.name,
+ section: taxCat.section,
+ deductiblePercentage: taxCat.categoryMappings.find(cm => cm.expenseCategory === category)?.deductiblePercentage || 100
+ };
+ }
+ }
+
+ return { isTaxDeductible: false };
+ }
+
+ /**
+ * Get tax summary for dashboard
+ */
+ async getTaxSummary(userId, taxYear = new Date().getFullYear()) {
+ const taxCalc = await this.calculateTax(userId, taxYear);
+
+ return {
+ taxYear,
+ grossIncome: taxCalc.grossIncome,
+ totalDeductions: taxCalc.totalDeductions,
+ taxableIncome: taxCalc.taxableIncome,
+ estimatedTax: taxCalc.totalTax,
+ effectiveRate: taxCalc.effectiveRate,
+ taxPaid: taxCalc.tdsDeducted + taxCalc.advanceTaxPaid,
+ taxDue: taxCalc.taxPayable,
+ topDeductions: taxCalc.deductions.slice(0, 5)
+ };
+ }
+
+ /**
+ * Compare tax between old and new regime
+ */
+ async compareRegimes(userId, taxYear = new Date().getFullYear()) {
+ const profile = await this.getOrCreateProfile(userId, taxYear);
+ const originalRegime = profile.regime;
+
+ // Calculate for new regime
+ profile.regime = 'new';
+ profile.taxBrackets = TaxProfile.getDefaultBrackets('IN', 'new');
+ const newRegimeTax = await this.calculateTax(userId, taxYear);
+
+ // Calculate for old regime
+ profile.regime = 'old';
+ profile.taxBrackets = TaxProfile.getDefaultBrackets('IN', 'old');
+ const oldRegimeTax = await this.calculateTax(userId, taxYear);
+
+ // Restore original regime
+ profile.regime = originalRegime;
+ profile.taxBrackets = TaxProfile.getDefaultBrackets('IN', originalRegime);
+ await profile.save();
+
+ const savings = oldRegimeTax.totalTax - newRegimeTax.totalTax;
+
+ return {
+ newRegime: {
+ taxableIncome: newRegimeTax.taxableIncome,
+ totalTax: newRegimeTax.totalTax,
+ effectiveRate: newRegimeTax.effectiveRate,
+ deductions: newRegimeTax.totalDeductions
+ },
+ oldRegime: {
+ taxableIncome: oldRegimeTax.taxableIncome,
+ totalTax: oldRegimeTax.totalTax,
+ effectiveRate: oldRegimeTax.effectiveRate,
+ deductions: oldRegimeTax.totalDeductions
+ },
+ recommendation: savings > 0 ? 'new' : 'old',
+ savings: Math.abs(savings),
+ message: savings > 0
+ ? `New regime saves you ₹${Math.abs(savings).toLocaleString()}`
+ : `Old regime saves you ₹${Math.abs(savings).toLocaleString()}`
+ };
+ }
+
+ /**
+ * Initialize default tax categories
+ */
+ async initializeDefaultCategories(country = 'IN') {
+ const categories = TaxCategory.getDefaultCategories(country);
+
+ for (const category of categories) {
+ await TaxCategory.findOneAndUpdate(
+ { code: category.code },
+ category,
+ { upsert: true, new: true }
+ );
+ }
+
+ console.log(`Default tax categories initialized for ${country}`);
}
}
-module.exports = new TaxService();
\ No newline at end of file
+module.exports = new TaxService();