diff --git a/fusion_accounting_bank_rec/__manifest__.py b/fusion_accounting_bank_rec/__manifest__.py index 82ac868c..bceaa881 100644 --- a/fusion_accounting_bank_rec/__manifest__.py +++ b/fusion_accounting_bank_rec/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting — Bank Reconciliation', - 'version': '19.0.1.0.0', + 'version': '19.0.1.0.1', 'category': 'Accounting/Accounting', 'sequence': 28, 'summary': 'Native V19 bank reconciliation widget with AI confidence scoring + behavioural learning.', diff --git a/fusion_accounting_bank_rec/models/__init__.py b/fusion_accounting_bank_rec/models/__init__.py index e69de29b..e8bf5bcf 100644 --- a/fusion_accounting_bank_rec/models/__init__.py +++ b/fusion_accounting_bank_rec/models/__init__.py @@ -0,0 +1,2 @@ +from . import fusion_reconcile_pattern +from . import fusion_reconcile_precedent diff --git a/fusion_accounting_bank_rec/models/fusion_reconcile_pattern.py b/fusion_accounting_bank_rec/models/fusion_reconcile_pattern.py new file mode 100644 index 00000000..65dcb7e8 --- /dev/null +++ b/fusion_accounting_bank_rec/models/fusion_reconcile_pattern.py @@ -0,0 +1,55 @@ +"""Per-partner bank reconciliation pattern aggregate. + +One row per (company_id, partner_id). Continuously summarises HOW this +partner gets reconciled. Recomputed nightly via cron from the precedent +table. Used as a feature input to confidence_scoring. +""" + +from odoo import fields, models + + +class FusionReconcilePattern(models.Model): + _name = "fusion.reconcile.pattern" + _description = "Per-partner bank reconciliation pattern aggregate" + _rec_name = "partner_id" + + company_id = fields.Many2one('res.company', required=True, index=True, + default=lambda self: self.env.company) + partner_id = fields.Many2one('res.partner', required=True, index=True) + + # Volume + cadence + reconcile_count = fields.Integer(default=0, + help="Total past reconciles for this partner") + typical_amount_range = fields.Char( + help="e.g. '$1,200 – $2,400 (median $1,847.50)'") + typical_cadence_days = fields.Float( + help="Mean inter-reconcile days") + typical_day_of_month = fields.Char( + help="e.g. '1st, 15th'") + + # Matching strategy used historically + pref_strategy = fields.Selection([ + ('exact_amount', 'Exact-amount-first'), + ('fifo', 'FIFO oldest-due-first'), + ('multi_invoice', 'Multi-invoice consolidation'), + ('cherry_pick', 'Cherry-pick specific invoices'), + ]) + pref_account_id = fields.Many2one('account.account', + help="Most-used target account") + + # Memo signature + common_memo_tokens = fields.Char( + help="Comma-separated tokens that appear in ≥30% of past reconciles") + + # Tax + write-off habits + common_writeoff_account_id = fields.Many2one('account.account') + common_writeoff_tax_id = fields.Many2one('account.tax') + typical_writeoff_amount = fields.Float( + help="e.g. 0.05 for rounding diffs") + + last_refreshed_at = fields.Datetime() + + _uniq_company_partner = models.Constraint( + 'unique(company_id, partner_id)', + 'One pattern row per (company, partner) — already exists.', + ) diff --git a/fusion_accounting_bank_rec/models/fusion_reconcile_precedent.py b/fusion_accounting_bank_rec/models/fusion_reconcile_precedent.py new file mode 100644 index 00000000..b7f25671 --- /dev/null +++ b/fusion_accounting_bank_rec/models/fusion_reconcile_precedent.py @@ -0,0 +1,49 @@ +"""Per-historical-decision reconciliation memory. + +One row per past reconciliation. Holds the full feature vector + outcome, +used by precedent_lookup for K-nearest-neighbour search when scoring a +new bank line. +""" + +from odoo import fields, models + + +class FusionReconcilePrecedent(models.Model): + _name = "fusion.reconcile.precedent" + _description = "Historical bank reconciliation decision (memory)" + _order = "reconciled_at desc, id desc" + + company_id = fields.Many2one('res.company', required=True, index=True, + default=lambda self: self.env.company) + partner_id = fields.Many2one('res.partner', index=True) + + # Bank line features (the "input") + amount = fields.Monetary(currency_field='currency_id') + currency_id = fields.Many2one('res.currency') + date = fields.Date() + memo_tokens = fields.Char( + help="Comma-separated normalized memo tokens (output of memo_tokenizer)") + journal_id = fields.Many2one('account.journal') + + # Outcome (the "decision made") + matched_move_line_count = fields.Integer( + help="1 = exact, 2-3 = consolidation, etc.") + matched_account_ids = fields.Char( + help="Comma-separated account.account IDs that were matched against") + matched_invoice_ages_days = fields.Char( + help="Comma-separated days-old at reconcile time, e.g. '12, 45, 78'") + write_off_amount = fields.Float() + write_off_account_id = fields.Many2one('account.account') + exchange_diff = fields.Boolean() + + # Provenance + reconciler_user_id = fields.Many2one('res.users') + reconciled_at = fields.Datetime() + source = fields.Selection([ + ('historical_bootstrap', 'Imported from history'), + ('manual', 'Manual reconcile via fusion'), + ('ai_accepted', 'AI suggestion accepted'), + ('auto_rule', 'account.reconcile.model auto-fired'), + ], required=True) + + # No uniqueness constraint — multiple reconciles can share features diff --git a/fusion_accounting_bank_rec/security/ir.model.access.csv b/fusion_accounting_bank_rec/security/ir.model.access.csv index 97dd8b91..62975dd9 100644 --- a/fusion_accounting_bank_rec/security/ir.model.access.csv +++ b/fusion_accounting_bank_rec/security/ir.model.access.csv @@ -1 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_fusion_reconcile_pattern_user,pattern user,model_fusion_reconcile_pattern,fusion_accounting_core.group_fusion_accounting_user,1,0,0,0 +access_fusion_reconcile_pattern_admin,pattern admin,model_fusion_reconcile_pattern,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1 +access_fusion_reconcile_precedent_user,precedent user,model_fusion_reconcile_precedent,fusion_accounting_core.group_fusion_accounting_user,1,0,0,0 +access_fusion_reconcile_precedent_admin,precedent admin,model_fusion_reconcile_precedent,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1