- Amount: $
| '; + h += ' | Entry | '; + h += 'Type | '; + h += 'Partner | '; + h += 'Date | '; + h += 'Residual | '; + h += 'Apply | '; + h += 'Score | '; + h += '
|---|---|---|---|---|---|---|---|
| `; + h += ` | ${this._esc(c.name)}`;
+ if (c.ref) h += ` ${this._esc(c.ref)}`; + h += ` | `;
+ const typeClass = c.type === 'payment' ? 'bg-success-subtle text-success' : c.type === 'bill' ? 'bg-info-subtle text-info' : 'bg-primary-subtle text-primary';
+ h += `${this._esc(c.type || 'entry')} | `; + h += `${this._esc(c.partner)} | `; + h += `${this._esc(c.date)} | `; + h += `$${residual.toFixed(2)} | `; + h += ``; + h += ``; + h += ` | `; + h += `${score}`; + if (c.reasons) h += ` — ${this._esc(c.reasons)}`; + h += ` |
Ask me about your accounting data.
- I can help with bank reconciliation, tax analysis, AR/AP, auditing, and more.
What would you like to work on?
+
+
+ | Type | +Details | +Amount | ++ |
|---|
Loading dashboard...
+Loading dashboard...
AI-prioritised items will appear here after the first audit scan.
+No match history yet
AI tool calls and their outcomes will appear here.
diff --git a/fusion_poynt/__manifest__.py b/fusion_poynt/__manifest__.py index d3794874..c8c04213 100644 --- a/fusion_poynt/__manifest__.py +++ b/fusion_poynt/__manifest__.py @@ -22,6 +22,7 @@ 'views/payment_poynt_templates.xml', 'views/poynt_terminal_views.xml', 'views/account_move_views.xml', + 'views/account_payment_views.xml', 'views/sale_order_views.xml', 'views/res_config_settings_views.xml', 'views/poynt_settlement_views.xml', diff --git a/fusion_poynt/models/__init__.py b/fusion_poynt/models/__init__.py index c0c1f5d7..9189ff07 100644 --- a/fusion_poynt/models/__init__.py +++ b/fusion_poynt/models/__init__.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import account_move +from . import account_payment from . import payment_provider from . import payment_token from . import payment_transaction diff --git a/fusion_poynt/models/account_payment.py b/fusion_poynt/models/account_payment.py new file mode 100644 index 00000000..a53fab35 --- /dev/null +++ b/fusion_poynt/models/account_payment.py @@ -0,0 +1,42 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import _, api, fields, models + + +class AccountPayment(models.Model): + _inherit = 'account.payment' + + poynt_settlement_line_ids = fields.One2many( + 'poynt.settlement.line', + 'existing_payment_id', + string="Settlement Lines", + ) + poynt_settlement_count = fields.Integer( + string="Settlements", + compute='_compute_poynt_settlement_count', + ) + + @api.depends('poynt_settlement_line_ids') + def _compute_poynt_settlement_count(self): + for payment in self: + payment.poynt_settlement_count = len(payment.poynt_settlement_line_ids) + + def action_view_poynt_settlement(self): + """Open the settlement batch linked to this payment.""" + self.ensure_one() + batch_ids = self.poynt_settlement_line_ids.mapped('batch_id').ids + if len(batch_ids) == 1: + return { + 'type': 'ir.actions.act_window', + 'name': _("Settlement Batch"), + 'res_model': 'poynt.settlement.batch', + 'view_mode': 'form', + 'res_id': batch_ids[0], + } + return { + 'type': 'ir.actions.act_window', + 'name': _("Settlement Batches"), + 'res_model': 'poynt.settlement.batch', + 'view_mode': 'list,form', + 'domain': [('id', 'in', batch_ids)], + } diff --git a/fusion_poynt/models/poynt_settlement.py b/fusion_poynt/models/poynt_settlement.py index 3e9025fc..0f1dbbd4 100644 --- a/fusion_poynt/models/poynt_settlement.py +++ b/fusion_poynt/models/poynt_settlement.py @@ -4,7 +4,7 @@ import logging from datetime import timedelta from odoo import _, api, fields, models -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import UserError _logger = logging.getLogger(__name__) @@ -52,10 +52,10 @@ class PoyntSettlementBatch(models.Model): ) state = fields.Selection([ ('draft', "Draft"), - ('matched', "Matched"), + ('matched', "Matched to Deposit"), ('reconciled', "Reconciled"), ('error', "Error"), - ], string="Status", required=True, default='draft', tracking=True) + ], string="Status", required=True, default='draft') currency_id = fields.Many2one( 'res.currency', @@ -93,10 +93,18 @@ class PoyntSettlementBatch(models.Model): store=True, ) matched_count = fields.Integer( - string="Matched to Customers", + string="Matched to Existing Payments", compute='_compute_totals', store=True, ) + payment_count = fields.Integer( + string="Payments", + compute='_compute_smart_buttons', + ) + invoice_count = fields.Integer( + string="Invoices", + compute='_compute_smart_buttons', + ) notes = fields.Text(string="Notes") _sql_constraints = [ @@ -113,7 +121,7 @@ class PoyntSettlementBatch(models.Model): ) or '/' return super().create(vals_list) - @api.depends('line_ids.amount', 'line_ids.action', 'line_ids.partner_id', 'elavon_deposit') + @api.depends('line_ids.amount', 'line_ids.action', 'line_ids.existing_payment_id', 'elavon_deposit') def _compute_totals(self): for batch in self: sales = sum( @@ -127,7 +135,38 @@ class PoyntSettlementBatch(models.Model): batch.fee_amount = net - batch.elavon_deposit if batch.elavon_deposit else 0.0 batch.sale_count = len(batch.line_ids.filtered(lambda l: l.action == 'SALE')) batch.refund_count = len(batch.line_ids.filtered(lambda l: l.action == 'REFUND')) - batch.matched_count = len(batch.line_ids.filtered(lambda l: l.partner_id)) + batch.matched_count = len(batch.line_ids.filtered(lambda l: l.existing_payment_id)) + + def _compute_smart_buttons(self): + for batch in self: + payments = batch.line_ids.mapped('existing_payment_id') + invoices = batch.line_ids.mapped('existing_invoice_id') + batch.payment_count = len(payments) + batch.invoice_count = len(invoices) + + def action_view_payments(self): + """Open linked payments in a list view.""" + self.ensure_one() + payment_ids = self.line_ids.mapped('existing_payment_id').ids + return { + 'type': 'ir.actions.act_window', + 'name': _("Payments - %s", self.name), + 'res_model': 'account.payment', + 'view_mode': 'list,form', + 'domain': [('id', 'in', payment_ids)], + } + + def action_view_invoices(self): + """Open linked invoices in a list view.""" + self.ensure_one() + invoice_ids = self.line_ids.mapped('existing_invoice_id').ids + return { + 'type': 'ir.actions.act_window', + 'name': _("Invoices - %s", self.name), + 'res_model': 'account.move', + 'view_mode': 'list,form', + 'domain': [('id', 'in', invoice_ids)], + } # === BUSINESS METHODS === # @@ -165,7 +204,7 @@ class PoyntSettlementBatch(models.Model): card = txn.get('fundingSource', {}).get('card', {}) - # Convert ISO 8601 timestamp (2025-03-05T19:19:10Z) to Odoo format + # Convert ISO 8601 timestamp to Odoo format created_at = txn.get('createdAt', '') if created_at: created_at = created_at.replace('T', ' ').replace('Z', '') @@ -198,90 +237,117 @@ class PoyntSettlementBatch(models.Model): if not self.line_ids: raise UserError(_("No transaction lines to match. Fetch transactions first.")) - # Look for Elavon deposit on the settlement date (or ±1 day for timing) - StmtLine = self.env['account.bank.statement.line'] - domain = [ - ('journal_id.name', 'ilike', 'Scotia'), - ('date', '>=', self.settlement_date - timedelta(days=1)), - ('date', '<=', self.settlement_date + timedelta(days=1)), - ('amount', '>', 0), - ('payment_ref', 'ilike', 'ELAVON'), - ('is_reconciled', '=', False), - ] - candidates = StmtLine.search(domain, order='date asc') + # Search for Elavon deposits near the settlement date + # Use journal_id = 50 (Scotia Current) and SQL for the date + # since date is a related field from account.move + self.env.cr.execute(""" + SELECT absl.id, am.date, absl.amount + FROM account_bank_statement_line absl + JOIN account_move am ON am.id = absl.move_id + WHERE absl.journal_id = 50 + AND am.date >= %s + AND am.date <= %s + AND absl.amount > 0 + AND absl.payment_ref ILIKE '%%ELAVON%%' + ORDER BY am.date + """, [ + self.settlement_date - timedelta(days=1), + self.settlement_date + timedelta(days=1), + ]) + rows = self.env.cr.fetchall() - if not candidates: - self.notes = f"No unreconciled Elavon deposit found near {self.settlement_date}" - return False + if not rows: + self.write({ + 'notes': f"No Elavon deposit found near {self.settlement_date}", + }) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': _("No Elavon deposit found near %s", self.settlement_date), + 'type': 'warning', + 'sticky': False, + }, + } - # Try to find the closest match by amount net_amount = self.poynt_total best_match = None best_diff = float('inf') - for line in candidates: - diff = abs(line.amount - net_amount) + for row_id, row_date, row_amount in rows: + diff = abs(float(row_amount) - net_amount) # Allow up to 5% tolerance for processing fees - if diff < best_diff and diff <= net_amount * 0.05: + if diff < best_diff and (net_amount == 0 or diff <= abs(net_amount) * 0.05): best_diff = diff - best_match = line + best_match = (row_id, row_date, float(row_amount)) if best_match: self.write({ - 'bank_statement_line_id': best_match.id, - 'elavon_deposit': best_match.amount, - 'settlement_date': best_match.date, + 'bank_statement_line_id': best_match[0], + 'elavon_deposit': best_match[2], + 'settlement_date': best_match[1], 'state': 'matched', }) _logger.info( "Poynt batch %s matched to bank line %s (deposit $%.2f, fees $%.2f)", - self.name, best_match.id, best_match.amount, self.fee_amount, + self.name, best_match[0], best_match[2], self.fee_amount, ) - return True + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': _("Matched to Elavon deposit of $%(amount).2f (fees: $%(fees).2f)", + amount=best_match[2], fees=self.fee_amount), + 'type': 'success', + 'sticky': False, + }, + } else: - self.notes = ( - f"No matching Elavon deposit found. " - f"Poynt net: ${net_amount:.2f}, " - f"closest candidate: ${candidates[0].amount:.2f}" - ) - return False + closest = min(rows, key=lambda r: abs(float(r[2]) - net_amount)) + self.write({ + 'notes': ( + f"No matching deposit. " + f"Poynt net: ${net_amount:.2f}, " + f"closest: ${float(closest[2]):.2f} on {closest[1]}" + ), + }) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': _( + "No matching deposit found. Poynt net: $%(net).2f, " + "closest deposit: $%(closest).2f", + net=net_amount, closest=float(closest[2]), + ), + 'type': 'warning', + 'sticky': False, + }, + } - def action_match_customers(self): - """Attempt to match settlement lines to Odoo customers and invoices.""" + def action_match_existing_payments(self): + """Match settlement lines to EXISTING payments already recorded by staff. + + This does NOT create new payments. Staff already record payments when + customers pay at the terminal. This method links the Poynt transaction + to that existing payment for audit/reconciliation purposes. + """ self.ensure_one() matched = 0 - for line in self.line_ids.filtered(lambda l: not l.partner_id and l.action == 'SALE'): - if line._match_to_customer(): + for line in self.line_ids.filtered(lambda l: not l.existing_payment_id and l.action == 'SALE'): + if line._match_to_existing_payment(): matched += 1 _logger.info( - "Poynt batch %s: matched %d/%d lines to customers", - self.name, matched, len(self.line_ids), - ) - return True - - def action_create_payments(self): - """Create account.payment records for matched settlement lines.""" - self.ensure_one() - if self.state == 'reconciled': - raise UserError(_("This batch is already reconciled.")) - - payable_lines = self.line_ids.filtered( - lambda l: l.partner_id and l.action == 'SALE' and l.state in ('fetched', 'matched') and not l.payment_id + "Poynt batch %s: matched %d/%d lines to existing payments", + self.name, matched, len(self.line_ids.filtered(lambda l: l.action == 'SALE')), ) - if not payable_lines: - raise UserError(_("No matched lines available for payment creation.")) - - for line in payable_lines: - line._create_customer_payment() - - # Check if all lines are processed - all_paid = all( - l.state in ('paid', 'error') or l.action == 'REFUND' - for l in self.line_ids + # Check if all SALE lines are matched + unmatched = self.line_ids.filtered( + lambda l: l.action == 'SALE' and not l.existing_payment_id and l.state != 'no_match' ) - if all_paid: + if not unmatched and self.state == 'matched': self.state = 'reconciled' return True @@ -318,10 +384,10 @@ class PoyntSettlementBatch(models.Model): return # Handle weekend: if today is Monday, fetch Fri+Sat+Sun - weekday = yesterday.weekday() # 0=Monday, 6=Sunday - if weekday == 6: # Sunday → fetch Fri-Sun, deposit Monday - txn_date_from = yesterday - timedelta(days=2) # Friday - elif weekday == 5: # Saturday → skip, will be batched with Sunday + weekday = yesterday.weekday() + if weekday == 6: # Sunday → fetch Fri-Sun + txn_date_from = yesterday - timedelta(days=2) + elif weekday == 5: # Saturday → skip _logger.info("Poynt settlement cron: Saturday — will batch with Sunday/Monday.") return else: @@ -334,7 +400,6 @@ class PoyntSettlementBatch(models.Model): }) try: - # Fetch all transactions for the date range transactions = provider._poynt_fetch_settlement_transactions( txn_date_from, yesterday, ) @@ -360,7 +425,6 @@ class PoyntSettlementBatch(models.Model): amount = amounts.get('transactionAmount', 0) / 100.0 card = txn.get('fundingSource', {}).get('card', {}) - # Convert ISO 8601 timestamp to Odoo format created_at = txn.get('createdAt', '') if created_at: created_at = created_at.replace('T', ' ').replace('Z', '') @@ -384,8 +448,8 @@ class PoyntSettlementBatch(models.Model): # Try to match to bank deposit batch.action_match_deposit() - # Try to match customers - batch.action_match_customers() + # Try to match to existing payments (NOT create new ones) + batch.action_match_existing_payments() _logger.info( "Poynt settlement cron: created batch %s with %d lines for %s→%s", @@ -427,23 +491,28 @@ class PoyntSettlementLine(models.Model): card_brand = fields.Char(string="Card Brand") card_last4 = fields.Char(string="Card Last 4", size=4) card_holder_name = fields.Char(string="Cardholder Name") + + # Links to EXISTING records (staff-created, not settlement-created) + existing_payment_id = fields.Many2one( + 'account.payment', + string="Existing Payment", + readonly=True, + ondelete='set null', + help="The payment already recorded by staff for this transaction.", + ) + existing_invoice_id = fields.Many2one( + 'account.move', + string="Linked Invoice", + domain="[('move_type', '=', 'out_invoice')]", + ondelete='set null', + help="The invoice this payment was applied to.", + ) partner_id = fields.Many2one( 'res.partner', string="Customer", ondelete='set null', ) - invoice_id = fields.Many2one( - 'account.move', - string="Matched Invoice", - domain="[('move_type', '=', 'out_invoice')]", - ondelete='set null', - ) - payment_id = fields.Many2one( - 'account.payment', - string="Payment", - readonly=True, - ondelete='set null', - ) + action = fields.Selection([ ('SALE', "Sale"), ('REFUND', "Refund"), @@ -451,13 +520,13 @@ class PoyntSettlementLine(models.Model): ], string="Action", required=True) state = fields.Selection([ ('fetched', "Fetched"), - ('matched', "Matched"), - ('paid', "Payment Created"), + ('matched', "Matched to Payment"), + ('no_match', "No Existing Payment"), ('error', "Error"), ], string="Status", required=True, default='fetched') match_method = fields.Char( string="Match Method", - help="How this line was matched to a customer (e.g., 'odoo_txn', 'card_token', 'invoice_amount', 'name').", + help="How this line was matched to an existing payment.", ) notes = fields.Text(string="Notes") @@ -466,167 +535,131 @@ class PoyntSettlementLine(models.Model): 'This Poynt transaction has already been recorded.'), ] - # === CUSTOMER MATCHING === # + # === MATCH TO EXISTING PAYMENTS === # - def _match_to_customer(self): - """Attempt to match this settlement line to an Odoo customer/invoice. + def _match_to_existing_payment(self): + """Match this Poynt transaction to an existing payment already in Odoo. + + Staff record payments when customers pay at the terminal. This method + finds that existing payment — it does NOT create a new one. Matching strategy (in priority order): - 1. Check poynt_transaction_id in payment.transaction (direct Odoo payment) - 2. Match by card_last4 against payment.token records - 3. Match by amount against open invoices within ±2 days - 4. Match by card_holder_name fuzzy search against res.partner + 1. Poynt transaction ID in payment.transaction (direct Odoo integration) + 2. Poynt transaction UUID found in payment memo field + 3. Exact amount + cardholder name match on same date (±2 days) + 4. Exact amount match on same date (±2 days) :return: True if matched, False otherwise. """ self.ensure_one() - if self.partner_id: + if self.existing_payment_id: return True - # Strategy 1: Direct Odoo payment transaction + # Strategy 1: Direct Odoo payment transaction (Poynt-integrated payments) PaymentTxn = self.env['payment.transaction'] odoo_txn = PaymentTxn.search([ ('poynt_transaction_id', '=', self.poynt_transaction_id), ], limit=1) - if odoo_txn and odoo_txn.partner_id: + if odoo_txn and odoo_txn.payment_id: self.write({ + 'existing_payment_id': odoo_txn.payment_id.id, 'partner_id': odoo_txn.partner_id.id, - 'invoice_id': odoo_txn.invoice_ids[:1].id if odoo_txn.invoice_ids else False, - 'match_method': 'odoo_txn', + 'existing_invoice_id': odoo_txn.invoice_ids[:1].id if odoo_txn.invoice_ids else False, + 'match_method': 'poynt_txn', 'state': 'matched', }) return True - # Strategy 2: Card token match - if self.card_last4: - token = self.env['payment.token'].search([ - ('payment_details', 'ilike', self.card_last4), - ('provider_id.code', '=', 'poynt'), + # Strategy 2: Poynt transaction UUID in payment memo field + # Staff sometimes record the UUID when entering payments manually + if self.poynt_transaction_id: + memo_match = self.env['account.payment'].search([ + ('memo', 'ilike', self.poynt_transaction_id), + ('payment_type', '=', 'inbound'), + ('state', 'in', ('posted', 'in_process')), ], limit=1) - if token and token.partner_id: + if memo_match: self.write({ - 'partner_id': token.partner_id.id, - 'match_method': 'card_token', - 'state': 'matched', - }) - # Try to find matching invoice - self._match_invoice() - return True - - # Strategy 3: Amount match against open invoices - if self.amount and self.transaction_date: - date = self.transaction_date.date() if self.transaction_date else fields.Date.today() - invoices = self.env['account.move'].search([ - ('move_type', '=', 'out_invoice'), - ('state', '=', 'posted'), - ('payment_state', 'in', ('not_paid', 'partial')), - ('amount_residual', '=', self.amount), - ('invoice_date', '>=', date - timedelta(days=7)), - ('invoice_date', '<=', date + timedelta(days=2)), - ], limit=1) - if invoices: - self.write({ - 'partner_id': invoices.partner_id.id, - 'invoice_id': invoices.id, - 'match_method': 'invoice_amount', + 'existing_payment_id': memo_match.id, + 'partner_id': memo_match.partner_id.id if memo_match.partner_id else False, + 'existing_invoice_id': self._find_invoice_for_payment(memo_match), + 'match_method': 'memo_uuid', 'state': 'matched', }) return True - # Strategy 4: Cardholder name fuzzy match - if self.card_holder_name: - name = self.card_holder_name.strip() - if len(name) >= 3: - partners = self.env['res.partner'].search([ - '|', - ('name', 'ilike', name), - ('name', 'ilike', name.split()[-1] if ' ' in name else name), - ], limit=5) - if len(partners) == 1: - self.write({ - 'partner_id': partners.id, - 'match_method': 'name', - 'state': 'matched', - }) - self._match_invoice() - return True + # Determine the date range for searching + if self.transaction_date: + txn_date = self.transaction_date.date() + else: + txn_date = self.batch_id.transaction_date + date_from = txn_date - timedelta(days=2) + date_to = txn_date + timedelta(days=2) + # Strategy 3: Exact amount + same date range on account.payment + # These are payments staff manually recorded + payments = self.env['account.payment'].search([ + ('amount', '=', self.amount), + ('payment_type', '=', 'inbound'), + ('date', '>=', date_from), + ('date', '<=', date_to), + ('state', 'in', ('posted', 'in_process')), + # Exclude payments already matched to other settlement lines + ('id', 'not in', self._get_already_matched_payment_ids()), + ], order='date asc') + + if payments: + # Prefer one with a partner that matches cardholder name + if self.card_holder_name: + name = self.card_holder_name.strip() + for pay in payments: + if pay.partner_id and name.lower() in (pay.partner_id.name or '').lower(): + self.write({ + 'existing_payment_id': pay.id, + 'partner_id': pay.partner_id.id, + 'existing_invoice_id': self._find_invoice_for_payment(pay), + 'match_method': 'amount_name', + 'state': 'matched', + }) + return True + + # Fall back to first matching payment + pay = payments[0] + self.write({ + 'existing_payment_id': pay.id, + 'partner_id': pay.partner_id.id if pay.partner_id else False, + 'existing_invoice_id': self._find_invoice_for_payment(pay), + 'match_method': 'amount_date', + 'state': 'matched', + }) + return True + + # No existing payment found — mark for review + self.write({ + 'state': 'no_match', + 'notes': f"No existing payment found for ${self.amount:.2f} near {txn_date}", + }) return False - def _match_invoice(self): - """Try to find a matching open invoice for this line's partner and amount.""" - self.ensure_one() - if self.invoice_id or not self.partner_id: - return + def _get_already_matched_payment_ids(self): + """Get payment IDs already matched to other lines in this batch.""" + return self.batch_id.line_ids.filtered( + lambda l: l.existing_payment_id and l.id != self.id + ).mapped('existing_payment_id').ids - invoices = self.env['account.move'].search([ - ('partner_id', '=', self.partner_id.id), - ('move_type', '=', 'out_invoice'), - ('state', '=', 'posted'), - ('payment_state', 'in', ('not_paid', 'partial')), - ('amount_residual', '=', self.amount), - ], limit=1, order='invoice_date desc') - if invoices: - self.invoice_id = invoices.id - - # === PAYMENT CREATION === # - - def _create_customer_payment(self): - """Create an account.payment for this matched settlement line.""" - self.ensure_one() - if not self.partner_id: - self.write({'state': 'error', 'notes': 'No customer matched'}) + def _find_invoice_for_payment(self, payment): + """Find the invoice that a payment was applied to.""" + if not payment.partner_id: return False - if self.payment_id: - return True - try: - # Use the provider's journal (Poynt payment journal) - journal = self.batch_id.provider_id.journal_id - if not journal: - # Fall back to first bank journal - journal = self.env['account.journal'].search([ - ('type', '=', 'bank'), - ('company_id', '=', self.env.company.id), - ], limit=1) + # Check reconciled invoices via the payment's move lines + receivable_lines = payment.move_id.line_ids.filtered( + lambda l: l.account_id.account_type == 'asset_receivable' and l.reconciled + ) + for line in receivable_lines: + for partial in (line.matched_debit_ids | line.matched_credit_ids): + counterpart = partial.debit_move_id if partial.credit_move_id == line else partial.credit_move_id + if counterpart.move_id.move_type == 'out_invoice': + return counterpart.move_id.id - payment_vals = { - 'partner_id': self.partner_id.id, - 'amount': self.amount, - 'currency_id': self.currency_id.id, - 'journal_id': journal.id, - 'payment_type': 'inbound', - 'partner_type': 'customer', - 'payment_method_line_id': journal.inbound_payment_method_line_ids[:1].id, - 'memo': f"Poynt {self.card_brand or 'Card'} ****{self.card_last4 or '????'} - {self.batch_id.name}", - } - - payment = self.env['account.payment'].create(payment_vals) - payment.action_post() - - self.write({ - 'payment_id': payment.id, - 'state': 'paid', - }) - - # Reconcile with invoice if matched - if self.invoice_id and self.invoice_id.payment_state in ('not_paid', 'partial'): - try: - (payment.move_id.line_ids + self.invoice_id.line_ids).filtered( - lambda l: l.account_id.account_type == 'asset_receivable' and not l.reconciled - ).reconcile() - except Exception as e: - _logger.warning( - "Could not auto-reconcile payment %s with invoice %s: %s", - payment.name, self.invoice_id.name, e, - ) - - return True - - except Exception as e: - self.write({'state': 'error', 'notes': str(e)}) - _logger.error( - "Failed to create payment for settlement line %s: %s", - self.poynt_transaction_id, e, - ) - return False + return False diff --git a/fusion_poynt/views/account_payment_views.xml b/fusion_poynt/views/account_payment_views.xml new file mode 100644 index 00000000..c965a104 --- /dev/null +++ b/fusion_poynt/views/account_payment_views.xml @@ -0,0 +1,19 @@ + +