Files
Odoo-Modules/fusion_accounting/services/tools/hst_management.py
gsinghpal 3cc93b8783 changes
2026-04-04 15:37:16 -04:00

300 lines
11 KiB
Python

import logging
_logger = logging.getLogger(__name__)
def calculate_hst_balance(env, params):
date_from = params.get('date_from')
date_to = params.get('date_to')
base_domain = [
('parent_state', '=', 'posted'),
('company_id', '=', env.company.id),
]
if date_from:
base_domain.append(('date', '>=', date_from))
if date_to:
base_domain.append(('date', '<=', date_to))
# Odoo 19 Enterprise: account.account may not have company_id field
# (shared chart of accounts). Use try/except to handle both cases.
try:
collected_accounts = env['account.account'].search([
('code', '=like', '2005%'), ('company_ids', 'in', env.company.id),
])
itc_accounts = env['account.account'].search([
('code', '=like', '2006%'), ('company_ids', 'in', env.company.id),
])
except Exception:
collected_accounts = env['account.account'].search([
('code', '=like', '2005%'),
])
itc_accounts = env['account.account'].search([
('code', '=like', '2006%'),
])
collected_lines = env['account.move.line'].search(
base_domain + [('account_id', 'in', collected_accounts.ids)]
)
itc_lines = env['account.move.line'].search(
base_domain + [('account_id', 'in', itc_accounts.ids)]
)
hst_collected = abs(sum(l.balance for l in collected_lines))
itcs = abs(sum(l.balance for l in itc_lines))
return {
'hst_collected': hst_collected,
'input_tax_credits': itcs,
'net_hst': hst_collected - itcs,
'status': 'owing' if (hst_collected - itcs) > 0 else 'refund',
'period': f'{date_from or "all"} to {date_to or "now"}',
}
def get_tax_report(env, params):
report_ref = params.get('report_ref', 'account.generic_tax_report')
try:
report = env.ref(report_ref)
except Exception:
return {'error': f'Report not found: {report_ref}'}
options = report.get_options({
'date': {
'date_from': params.get('date_from', ''),
'date_to': params.get('date_to', ''),
}
})
lines = report._get_lines(options)
return {
'report_name': report.name,
'lines': [{
'name': l.get('name', ''),
'columns': [c.get('no_format', c.get('name', '')) for c in l.get('columns', [])],
} for l in lines[:50]],
}
def find_missing_tax_invoices(env, params):
date_from = params.get('date_from')
date_to = params.get('date_to')
domain = [
('move_type', 'in', ('out_invoice', 'out_refund')),
('state', '=', 'posted'),
('company_id', '=', env.company.id),
]
if date_from:
domain.append(('date', '>=', date_from))
if date_to:
domain.append(('date', '<=', date_to))
invoices = env['account.move'].search(domain)
missing = invoices.filtered(
lambda inv: not any(line.tax_ids for line in inv.invoice_line_ids)
)
return {
'total_invoices': len(invoices),
'missing_tax_count': len(missing),
'invoices': [{
'id': inv.id,
'name': inv.name,
'partner': inv.partner_id.name if inv.partner_id else '',
'amount_total': inv.amount_total,
'date': str(inv.date),
} for inv in missing[:30]],
}
def find_missing_itc_bills(env, params):
date_from = params.get('date_from')
date_to = params.get('date_to')
domain = [
('move_type', 'in', ('in_invoice', 'in_refund')),
('state', '=', 'posted'),
('company_id', '=', env.company.id),
]
if date_from:
domain.append(('date', '>=', date_from))
if date_to:
domain.append(('date', '<=', date_to))
bills = env['account.move'].search(domain)
missing = bills.filtered(
lambda b: not any(line.tax_ids for line in b.invoice_line_ids)
)
return {
'total_bills': len(bills),
'missing_itc_count': len(missing),
'bills': [{
'id': b.id,
'name': b.name,
'partner': b.partner_id.name if b.partner_id else '',
'amount_total': b.amount_total,
'date': str(b.date),
} for b in missing[:30]],
}
def get_tax_return_status(env, params):
try:
AccountReturn = env['account.return']
except KeyError:
return {'error': 'Tax return model (account.return) is not available. The account_tax_report or related Enterprise module may not be installed.'}
returns = AccountReturn.search([
('company_id', '=', env.company.id),
], order='date_start desc', limit=10)
return {
'returns': [{
'id': r.id,
'name': r.display_name,
'date_start': str(r.date_start) if hasattr(r, 'date_start') else '',
'date_end': str(r.date_end) if hasattr(r, 'date_end') else '',
'state': r.state if hasattr(r, 'state') else '',
} for r in returns],
}
def generate_tax_return(env, params):
try:
AccountReturn = env['account.return']
except KeyError:
return {'error': 'Tax return model (account.return) is not available.'}
try:
AccountReturn._generate_or_refresh_all_returns(
company=env.company
)
return {'status': 'generated', 'message': 'Tax returns refreshed successfully.'}
except Exception as e:
return {'error': str(e)}
def validate_tax_return(env, params):
try:
AccountReturn = env['account.return']
except KeyError:
return {'error': 'Tax return model (account.return) is not available.'}
return_id = int(params['return_id'])
tax_return = AccountReturn.browse(return_id)
if not tax_return.exists():
return {'error': 'Tax return not found'}
try:
tax_return.action_validate()
return {'status': 'validated', 'return_id': return_id}
except Exception as e:
return {'error': str(e)}
def create_expense_entry(env, params):
"""[Tier 3] Create a direct GL expense entry in the Misc journal with optional HST split.
This is the 'old school' way of recording expenses without a formal vendor bill.
Requires user approval before execution."""
date = params.get('date', str(env['account.move']._fields['date'].default(env['account.move'])))
description = params.get('description', 'Expense')
expense_account_id = int(params['expense_account_id'])
amount = abs(float(params['amount']))
has_hst = params.get('has_hst', False)
bank_journal_id = int(params.get('bank_journal_id', 0))
# Find the MISC journal
misc_journal = env['account.journal'].search([
('code', '=', 'MISC'), ('company_id', '=', env.company.id),
], limit=1)
if not misc_journal:
return {'error': 'Miscellaneous Operations journal (MISC) not found'}
expense_account = env['account.account'].browse(expense_account_id)
if not expense_account.exists():
return {'error': f'Expense account not found: {expense_account_id}'}
# Determine credit account (bank outstanding or AP)
credit_account = None
if bank_journal_id:
bank_journal = env['account.journal'].browse(bank_journal_id)
if bank_journal.exists():
# Use the bank journal's default debit/credit account
credit_account = (bank_journal.default_account_id
or bank_journal.company_id.account_journal_payment_credit_account_id)
if not credit_account:
# Fallback to AP account
credit_account = env['account.account'].search([
('account_type', '=', 'liability_payable'),
('company_ids', 'in', env.company.id),
], limit=1)
if not credit_account:
return {'error': 'Could not determine credit account for the expense entry'}
line_ids = []
if has_hst:
# Split: net expense + 13% HST ITC
hst_rate = 0.13
net_amount = round(amount / (1 + hst_rate), 2)
hst_amount = round(amount - net_amount, 2)
# Find HST ITC account (2006%)
itc_account = env['account.account'].search([
('code', '=like', '2006%'),
], limit=1)
if not itc_account:
# Fallback: use the HST purchase tax account
hst_tax = env['account.tax'].search([
('type_tax_use', '=', 'purchase'), ('amount', '=', 13.0),
('company_id', '=', env.company.id),
], limit=1)
if hst_tax and hst_tax.invoice_repartition_line_ids:
for rep in hst_tax.invoice_repartition_line_ids:
if rep.repartition_type == 'tax' and rep.account_id:
itc_account = rep.account_id
break
if not itc_account:
return {'error': 'HST ITC account (2006) not found'}
line_ids = [
(0, 0, {'name': description, 'account_id': expense_account_id,
'debit': net_amount, 'credit': 0.0}),
(0, 0, {'name': f'HST ITC - {description}', 'account_id': itc_account.id,
'debit': hst_amount, 'credit': 0.0}),
(0, 0, {'name': description, 'account_id': credit_account.id,
'debit': 0.0, 'credit': amount}),
]
else:
# Simple: debit expense / credit bank
line_ids = [
(0, 0, {'name': description, 'account_id': expense_account_id,
'debit': amount, 'credit': 0.0}),
(0, 0, {'name': description, 'account_id': credit_account.id,
'debit': 0.0, 'credit': amount}),
]
try:
move = env['account.move'].create({
'move_type': 'entry',
'journal_id': misc_journal.id,
'date': date,
'ref': description,
'line_ids': line_ids,
'company_id': env.company.id,
})
move.action_post()
return {
'status': 'posted',
'move_id': move.id,
'move_name': move.name,
'amount': amount,
'has_hst': has_hst,
'hst_amount': round(amount - amount / 1.13, 2) if has_hst else 0.0,
}
except Exception as e:
_logger.error("Failed to create expense entry: %s", e)
return {'error': str(e)}
TOOLS = {
'calculate_hst_balance': calculate_hst_balance,
'get_tax_report': get_tax_report,
'find_missing_tax_invoices': find_missing_tax_invoices,
'find_missing_itc_bills': find_missing_itc_bills,
'get_tax_return_status': get_tax_return_status,
'generate_tax_return': generate_tax_return,
'validate_tax_return': validate_tax_return,
'create_expense_entry': create_expense_entry,
}