151 lines
5.3 KiB
Python
151 lines
5.3 KiB
Python
import logging
|
|
from odoo import fields
|
|
from datetime import timedelta
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_ap_aging(env, params):
|
|
today = fields.Date.today()
|
|
domain = [
|
|
('account_id.account_type', '=', 'liability_payable'),
|
|
('parent_state', '=', 'posted'),
|
|
('reconciled', '=', False),
|
|
('company_id', '=', env.company.id),
|
|
]
|
|
amls = env['account.move.line'].search(domain)
|
|
|
|
buckets = {'current': 0, '1_30': 0, '31_60': 0, '61_90': 0, '90_plus': 0}
|
|
for aml in amls:
|
|
amt = abs(aml.amount_residual)
|
|
if not aml.date_maturity or aml.date_maturity >= today:
|
|
buckets['current'] += amt
|
|
else:
|
|
days = (today - aml.date_maturity).days
|
|
if days <= 30:
|
|
buckets['1_30'] += amt
|
|
elif days <= 60:
|
|
buckets['31_60'] += amt
|
|
elif days <= 90:
|
|
buckets['61_90'] += amt
|
|
else:
|
|
buckets['90_plus'] += amt
|
|
|
|
return {'total': sum(buckets.values()), 'buckets': buckets, 'line_count': len(amls)}
|
|
|
|
|
|
def find_duplicate_bills(env, params):
|
|
window_days = int(params.get('window_days', 7))
|
|
bills = env['account.move'].search([
|
|
('move_type', 'in', ('in_invoice', 'in_refund')),
|
|
('state', '=', 'posted'),
|
|
('company_id', '=', env.company.id),
|
|
], order='partner_id, amount_total, date')
|
|
|
|
duplicates = []
|
|
prev = None
|
|
for bill in bills:
|
|
if prev and (
|
|
prev.partner_id == bill.partner_id
|
|
and abs(prev.amount_total - bill.amount_total) < 0.01
|
|
and abs((prev.date - bill.date).days) <= window_days
|
|
):
|
|
duplicates.append({
|
|
'bill_1': {'id': prev.id, 'name': prev.name, 'date': str(prev.date)},
|
|
'bill_2': {'id': bill.id, 'name': bill.name, 'date': str(bill.date)},
|
|
'partner': bill.partner_id.name,
|
|
'amount': bill.amount_total,
|
|
})
|
|
prev = bill
|
|
|
|
return {'count': len(duplicates), 'duplicates': duplicates[:20]}
|
|
|
|
|
|
def match_bill_to_po(env, params):
|
|
bill_id = int(params['bill_id'])
|
|
bill = env['account.move'].browse(bill_id)
|
|
if not bill.exists():
|
|
return {'error': 'Bill not found'}
|
|
matches = []
|
|
for line in bill.invoice_line_ids:
|
|
if line.purchase_line_id:
|
|
matches.append({
|
|
'bill_line': line.name or '',
|
|
'po': line.purchase_line_id.order_id.name,
|
|
'po_line': line.purchase_line_id.name,
|
|
'po_qty': line.purchase_line_id.product_qty,
|
|
'bill_qty': line.quantity,
|
|
'match': abs(line.quantity - line.purchase_line_id.product_qty) < 0.01,
|
|
})
|
|
return {'bill': bill.name, 'matches': matches, 'unmatched_lines': len(bill.invoice_line_ids) - len(matches)}
|
|
|
|
|
|
def get_unpaid_bills(env, params):
|
|
domain = [
|
|
('move_type', 'in', ('in_invoice', 'in_refund')),
|
|
('state', '=', 'posted'),
|
|
('payment_state', 'in', ('not_paid', 'partial')),
|
|
('company_id', '=', env.company.id),
|
|
]
|
|
if params.get('partner_id'):
|
|
domain.append(('partner_id', '=', int(params['partner_id'])))
|
|
bills = env['account.move'].search(domain, order='invoice_date_due asc', limit=int(params.get('limit', 50)))
|
|
return {
|
|
'count': len(bills),
|
|
'total': sum(b.amount_residual for b in bills),
|
|
'bills': [{
|
|
'id': b.id, 'name': b.name,
|
|
'partner': b.partner_id.name if b.partner_id else '',
|
|
'amount_total': b.amount_total,
|
|
'amount_residual': b.amount_residual,
|
|
'date_due': str(b.invoice_date_due) if b.invoice_date_due else '',
|
|
} for b in bills],
|
|
}
|
|
|
|
|
|
def verify_bill_taxes(env, params):
|
|
bill_id = int(params['bill_id'])
|
|
bill = env['account.move'].browse(bill_id)
|
|
if not bill.exists():
|
|
return {'error': 'Bill not found'}
|
|
issues = []
|
|
for line in bill.invoice_line_ids:
|
|
if line.product_id and not line.tax_ids:
|
|
issues.append({
|
|
'line': line.name or line.product_id.name,
|
|
'issue': 'No tax applied to product line',
|
|
})
|
|
return {'bill': bill.name, 'issues': issues, 'clean': len(issues) == 0}
|
|
|
|
|
|
def get_payment_schedule(env, params):
|
|
days_ahead = int(params.get('days_ahead', 30))
|
|
cutoff = fields.Date.today() + timedelta(days=days_ahead)
|
|
bills = env['account.move'].search([
|
|
('move_type', '=', 'in_invoice'),
|
|
('state', '=', 'posted'),
|
|
('payment_state', 'in', ('not_paid', 'partial')),
|
|
('invoice_date_due', '<=', cutoff),
|
|
('company_id', '=', env.company.id),
|
|
], order='invoice_date_due asc')
|
|
return {
|
|
'period': f'Next {days_ahead} days',
|
|
'total': sum(b.amount_residual for b in bills),
|
|
'bills': [{
|
|
'id': b.id, 'name': b.name,
|
|
'partner': b.partner_id.name if b.partner_id else '',
|
|
'amount_residual': b.amount_residual,
|
|
'date_due': str(b.invoice_date_due) if b.invoice_date_due else '',
|
|
} for b in bills[:50]],
|
|
}
|
|
|
|
|
|
TOOLS = {
|
|
'get_ap_aging': get_ap_aging,
|
|
'find_duplicate_bills': find_duplicate_bills,
|
|
'match_bill_to_po': match_bill_to_po,
|
|
'get_unpaid_bills': get_unpaid_bills,
|
|
'verify_bill_taxes': verify_bill_taxes,
|
|
'get_payment_schedule': get_payment_schedule,
|
|
}
|