import logging from typing import Any, Dict, Optional from models import Address, Asset, Receipt, Transaction logger = logging.getLogger(__name__) class TaxRulesEngine: """Engine to handle tax calculations based on the four tax rules""" # Provincial tax rates (simplified - in production, use a tax rate API) PROVINCIAL_TAX_RATES = { "ON": 0.13, # Ontario HST "QC": 0.14975, # Quebec QST "BC": 0.12, # British Columbia "AB": 0.05, # Alberta "SK": 0.11, # Saskatchewan "MB": 0.12, # Manitoba "NS": 0.15, # Nova Scotia "NB": 0.15, # New Brunswick "NL": 0.15, # Newfoundland and Labrador "PE": 0.15, # Prince Edward Island "NT": 0.05, # Northwest Territories "NU": 0.05, # Nunavut "YT": 0.05, # Yukon } def __init__(self): self.logger = logging.getLogger(__name__) def apply_sales_tax_rule(self, receipt: Receipt) -> Dict[str, Any]: """ Sales Tax Rule: Apply correct sales tax based on billing vs shipping addresses """ try: # Determine which address to use for tax calculation tax_address = self._get_tax_address(receipt) if not tax_address: return { "success": False, "error": "No valid address found for tax calculation", "calculated_tax": 0.0, "tax_rate": 0.0, } # Get tax rate for the province tax_rate = self.PROVINCIAL_TAX_RATES.get(tax_address.province, 0.0) # Calculate tax amount calculated_tax = receipt.amount * tax_rate return { "success": True, "calculated_tax": calculated_tax, "tax_rate": tax_rate, "tax_address": tax_address.province, "rule_applied": "Sales Tax Rule", } except Exception as e: self.logger.error(f"Error applying sales tax rule: {str(e)}") return { "success": False, "error": str(e), "calculated_tax": 0.0, "tax_rate": 0.0, } def _get_tax_address(self, receipt: Receipt) -> Optional[Address]: """Determine which address to use for tax calculation""" # Rule: Use shipping address if different from billing, otherwise use billing if receipt.shipping_address and receipt.billing_address: if self._addresses_different( receipt.billing_address, receipt.shipping_address ): return receipt.shipping_address else: return receipt.billing_address elif receipt.shipping_address: return receipt.shipping_address elif receipt.billing_address: return receipt.billing_address else: return None def _addresses_different(self, billing: Address, shipping: Address) -> bool: """Check if billing and shipping addresses are different""" return ( billing.province != shipping.province or billing.city != shipping.city or billing.postal_code != shipping.postal_code ) def apply_fx_rule( self, receipt: Receipt, transaction: Transaction ) -> Dict[str, Any]: """ Foreign Exchange Rule: Handle currency mismatches """ try: # Check for currency mismatch if receipt.currency != transaction.currency: fx_discrepancy = abs(receipt.amount - abs(transaction.amount)) return { "success": True, "fx_discrepancy": fx_discrepancy, "receipt_currency": receipt.currency, "transaction_currency": transaction.currency, "receipt_amount": receipt.amount, "transaction_amount": abs(transaction.amount), "requires_manual_review": True, "rule_applied": "Foreign Exchange Rule", } else: return { "success": True, "fx_discrepancy": 0.0, "requires_manual_review": False, "rule_applied": "No FX Rule (same currency)", } except Exception as e: self.logger.error(f"Error applying FX rule: {str(e)}") return { "success": False, "error": str(e), "fx_discrepancy": 0.0, "requires_manual_review": False, } def calculate_straight_line_depreciation( self, asset: Asset, year: int ) -> Dict[str, Any]: """ Straight-Line Depreciation for accounting purposes """ try: if year > asset.useful_life_years: return { "success": False, "error": f"Year {year} exceeds useful life of {asset.useful_life_years} years", "depreciation": 0.0, } # Straight-line formula: (Cost - Residual Value) / Useful Life annual_depreciation = ( asset.purchase_amount - asset.residual_value ) / asset.useful_life_years return { "success": True, "depreciation": annual_depreciation, "book_value": asset.purchase_amount - (annual_depreciation * year), "method": "Straight-Line", "rule_applied": "Depreciation Rule (Accounting)", } except Exception as e: self.logger.error(f"Error calculating straight-line depreciation: {str(e)}") return {"success": False, "error": str(e), "depreciation": 0.0} def calculate_cca_depreciation(self, asset: Asset, year: int) -> Dict[str, Any]: """ CCA (Capital Cost Allowance) Depreciation for tax purposes """ try: if year < 1: return { "success": False, "error": "Year must be at least 1", "depreciation": 0.0, } # CCA uses declining balance method book_value = asset.purchase_amount total_depreciation = 0.0 for current_year in range(1, year + 1): # CCA is calculated on the declining balance cca_amount = book_value * asset.cca_rate book_value -= cca_amount total_depreciation += cca_amount # Stop if book value reaches residual value if book_value <= asset.residual_value: break return { "success": True, "depreciation": cca_amount, # Current year depreciation "total_depreciation": total_depreciation, "book_value": max(book_value, asset.residual_value), "method": "CCA Declining Balance", "rule_applied": "Depreciation Rule (Tax)", } except Exception as e: self.logger.error(f"Error calculating CCA depreciation: {str(e)}") return {"success": False, "error": str(e), "depreciation": 0.0} def apply_meals_entertainment_rule(self, receipt: Receipt) -> Dict[str, Any]: """ Meals & Entertainment Tax Deduction Rule """ try: if not receipt.is_meals_entertainment: return { "success": True, "tax_deduction": receipt.amount, "accounting_deduction": receipt.amount, "rule_applied": "No M&E Rule (not meals/entertainment)", } # For tax purposes: 50% deductible tax_deduction = receipt.amount * 0.5 # For accounting purposes: 100% deductible accounting_deduction = receipt.amount # Sales tax is fully deductible for accounting tax_on_meal = receipt.tax return { "success": True, "tax_deduction": tax_deduction, "accounting_deduction": accounting_deduction, "tax_on_meal": tax_on_meal, "rule_applied": "Meals & Entertainment Rule", } except Exception as e: self.logger.error(f"Error applying meals & entertainment rule: {str(e)}") return { "success": False, "error": str(e), "tax_deduction": 0.0, "accounting_deduction": 0.0, } def apply_all_tax_rules( self, receipt: Receipt, transaction: Transaction = None ) -> Dict[str, Any]: """ Apply all tax rules to a receipt """ results = { "receipt_id": receipt.id, "rules_applied": [], "sales_tax": {}, "fx_analysis": {}, "meals_entertainment": {}, } # Apply Sales Tax Rule sales_tax_result = self.apply_sales_tax_rule(receipt) results["sales_tax"] = sales_tax_result if sales_tax_result["success"]: results["rules_applied"].append("Sales Tax Rule") # Apply FX Rule (if transaction provided) if transaction: fx_result = self.apply_fx_rule(receipt, transaction) results["fx_analysis"] = fx_result if fx_result["success"]: results["rules_applied"].append("Foreign Exchange Rule") # Apply Meals & Entertainment Rule me_result = self.apply_meals_entertainment_rule(receipt) results["meals_entertainment"] = me_result if me_result["success"]: results["rules_applied"].append("Meals & Entertainment Rule") return results