From 1e42b50a9c9723c480539fb33c39e1063bf18c2e Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Tue, 5 May 2026 18:59:14 +0700 Subject: [PATCH] refactor: integrate intercompany clearing lines directly into POS session move and remove external clearing moves --- models/pos_session.py | 198 ++++++++++++------------------------------ 1 file changed, 55 insertions(+), 143 deletions(-) diff --git a/models/pos_session.py b/models/pos_session.py index afc95c3..4763ee8 100644 --- a/models/pos_session.py +++ b/models/pos_session.py @@ -11,13 +11,14 @@ class PosSession(models.Model): def _validate_session(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None): res = super(PosSession, self)._validate_session(balancing_account, amount_to_balance, bank_payment_method_diffs) - # After the standard validation and account move creation, we create the inter-company clearing moves - self._create_intercompany_clearing_moves() + # After the standard validation, we create the parent mirror entries + self._create_intercompany_parent_moves() return res def _create_bank_payment_moves(self, data): - """Override to skip account.payment creation for intercompany payment methods.""" + """Override to skip account.payment creation for intercompany payment methods, + and append the clearing lines directly to the POS session move.""" intercompany_pms = self.payment_method_ids.filtered( lambda pm: pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id ) @@ -47,23 +48,49 @@ class PosSession(models.Model): # Manually handle intercompany ones: create the line in main move but skip account.payment for pm, amounts in intercompany_combine.items(): + # 1. Create the standard Debit AR In Transit line combine_receivable_line = MoveLine.create(self._get_combine_receivable_vals(pm, amounts['amount'], amounts['amount_converted'])) - res_data['payment_method_to_receivable_lines'][pm] = combine_receivable_line + + # 2. Create the Intercompany Clearing Lines (Credit AR In Transit, Debit Hubungan RK) + receivable_account = self._get_receivable_account(pm) + intercompany_account = pm.intercompany_clearing_account_id + + amount = amounts['amount'] + amount_company_curr = amounts['amount_converted'] + + credit_transit_line = MoveLine.create({ + 'name': f"Clearing - {pm.name}", + 'account_id': receivable_account.id, + 'move_id': self.move_id.id, + 'credit': amount_company_curr, + 'debit': 0.0, + 'currency_id': self.currency_id.id, + 'amount_currency': -amount, + }) + + debit_rk_line = MoveLine.create({ + 'name': f"Due from Parent - {pm.name}", + 'account_id': intercompany_account.id, + 'move_id': self.move_id.id, + 'credit': 0.0, + 'debit': amount_company_curr, + 'currency_id': self.currency_id.id, + 'amount_currency': amount, + }) + + # Add both AR In Transit lines to the data dict so standard Odoo reconciles them! + res_data['payment_method_to_receivable_lines'][pm] = combine_receivable_line | credit_transit_line return res_data - def _create_intercompany_clearing_moves(self): + def _create_intercompany_parent_moves(self): for session in self: if session.state != 'closed' or not session.move_id: continue - # Dictionary to accumulate amounts per payment method + # Accumulate amounts per payment method clearing_amounts = {} - - # Find all orders and payments for this session - orders = session.order_ids - - for order in orders: + for order in session.order_ids: for payment in order.payment_ids: pm = payment.payment_method_id if pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id: @@ -71,136 +98,28 @@ class PosSession(models.Model): clearing_amounts[pm] = 0.0 clearing_amounts[pm] += payment.amount - # Group PMs by their clearing journal - journal_to_pms = {} + if not clearing_amounts: + continue + + pm_level_data = [] for pm, amount in clearing_amounts.items(): if session.currency_id.is_zero(amount): continue - journal = pm.intercompany_clearing_journal_id - if journal not in journal_to_pms: - journal_to_pms[journal] = [] - journal_to_pms[journal].append(pm) - - for clearing_journal, pms in journal_to_pms.items(): - aggregated_data = {} # Key: (receivable_account, intercompany_account) - pm_level_data = [] # For parent mirror entries + amount_company_curr = amount + if session.currency_id != session.company_id.currency_id: + amount_company_curr = session.currency_id._convert( + amount, session.company_id.currency_id, session.company_id, session.stop_at or fields.Date.context_today(session) + ) + pm_level_data.append({ + 'pm': pm, + 'amount': amount, + 'amount_company_curr': amount_company_curr, + }) - for pm in pms: - amount = clearing_amounts[pm] - receivable_account = self._get_receivable_account(pm) - if not receivable_account: - continue - - intercompany_account = pm.intercompany_clearing_account_id - - # Convert amount to company currency if needed - amount_company_curr = amount - if session.currency_id != session.company_id.currency_id: - amount_company_curr = session.currency_id._convert( - amount, session.company_id.currency_id, session.company_id, session.stop_at or fields.Date.context_today(session) - ) - - # Store PM level data for parent mirror - pm_level_data.append({ - 'pm': pm, - 'amount': amount, - 'amount_company_curr': amount_company_curr, - }) - - # Aggregate for branch move - key = (receivable_account, intercompany_account) - if key not in aggregated_data: - aggregated_data[key] = { - 'total_amount': 0.0, - 'total_company_curr': 0.0, - 'pms': [], - 'receivable_account': receivable_account, - 'intercompany_account': intercompany_account, - } - aggregated_data[key]['total_amount'] += amount - aggregated_data[key]['total_company_curr'] += amount_company_curr - aggregated_data[key]['pms'].append(pm) + if pm_level_data: + self._create_aggregated_parent_mirror_move(session, pm_level_data) - if not aggregated_data: - continue - - line_ids = [] - for key, data in aggregated_data.items(): - # CREDIT: Total AR in Transit - line_ids.append(Command.create({ - 'name': f"Total Clearing - {session.name}", - 'account_id': data['receivable_account'].id, - 'credit': data['total_company_curr'], - 'debit': 0.0, - 'currency_id': session.currency_id.id, - 'amount_currency': -data['total_amount'], - })) - - # DEBIT: Total Hubungan RK - line_ids.append(Command.create({ - 'name': f"Total Due from Parent - {session.name}", - 'account_id': data['intercompany_account'].id, - 'credit': 0.0, - 'debit': data['total_company_curr'], - 'currency_id': session.currency_id.id, - 'amount_currency': data['total_amount'], - })) - - # --- BRANCH SIDE: Aggregated Clearing Move (Target: 2 items) --- - move_vals = { - 'journal_id': clearing_journal.id, - 'date': session.stop_at or fields.Date.context_today(session), - 'ref': f"Inter-company clearing for {session.name}", - 'move_type': 'entry', - 'company_id': session.company_id.id, - 'line_ids': line_ids, - } - - try: - clearing_move = self.env['account.move'].sudo().with_company(session.company_id).create(move_vals) - clearing_move._post() - - # 1. Reconcile aggregated lines with session move - for key, data in aggregated_data.items(): - receivable_account = data['receivable_account'] - pms = data['pms'] - - try: - # Find the aggregated credit line - clearing_credit_line = clearing_move.line_ids.filtered( - lambda l: l.account_id == receivable_account and l.credit > 0 - ) - # Find all matching debit lines in the session move - pm_names = [pm.name for pm in pms] - pos_debit_lines = session.move_id.line_ids.filtered( - lambda l: l.account_id == receivable_account and l.debit > 0 and \ - any(name in l.name for name in pm_names) - ) - - if clearing_credit_line and pos_debit_lines: - (clearing_credit_line + pos_debit_lines).reconcile() - except Exception as re_e: - _logger.warning("Could not auto-reconcile aggregated clearing lines for session %s: %s", session.name, re_e) - - # 2. Create parent mirror move (aggregated into one entry but separate lines per PM) - self._create_aggregated_parent_mirror_move(session, pm_level_data, clearing_move) - - except Exception as e: - _logger.error("Failed to create/post aggregated inter-company clearing move for session %s: %s", session.name, e) - - - - def _get_related_account_moves(self): - res = super()._get_related_account_moves() - for session in self: - clearing_moves = self.env['account.move'].sudo().search([ - ('company_id', '=', session.company_id.id), - ('ref', '=', f"Inter-company clearing for {session.name}") - ]) - res |= clearing_moves - return res - - def _create_aggregated_parent_mirror_move(self, session, pm_level_data, branch_clearing_move): + def _create_aggregated_parent_mirror_move(self, session, pm_level_data): """Create a single mirror journal entry in the parent company, with separate lines per PM. For each PM: @@ -212,7 +131,6 @@ class PosSession(models.Model): pm = data['pm'] parent_company = pm.parent_company_id or session.company_id.parent_id if not parent_company: - _logger.info("No parent company configured for PM %s, skipping parent mirror entry.", pm.name) continue parent_bank_journal = pm.parent_bank_journal_id @@ -220,7 +138,6 @@ class PosSession(models.Model): parent_clearing_journal = pm.parent_clearing_journal_id if not parent_bank_journal or not parent_rk_account or not parent_clearing_journal: - _logger.warning("Parent clearing not fully configured for PM %s. Skipping.", pm.name) continue outstanding_receipt_account = None @@ -230,7 +147,6 @@ class PosSession(models.Model): break if not outstanding_receipt_account: - _logger.warning("No outstanding receipt account found on parent bank journal %s. Skipping PM %s.", parent_bank_journal.name, pm.name) continue group_key = (parent_company.id, parent_clearing_journal.id) @@ -302,10 +218,6 @@ class PosSession(models.Model): try: parent_move = self.env['account.move'].sudo().with_company(parent_company).create(parent_move_vals) parent_move._post() - _logger.info( - "Created aggregated parent mirror entry %s in company %s for session %s (Lines: %s)", - parent_move.name, parent_company.name, session.name, len(line_ids) - ) except Exception as e: _logger.error( "Failed to create aggregated parent mirror entry for session %s in company %s: %s",