# -*- coding: utf-8 -*- from odoo import models, fields, api, Command import logging _logger = logging.getLogger(__name__) class PosSession(models.Model): _inherit = 'pos.session' 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() return res def _create_bank_payment_moves(self, data): """Override to skip account.payment creation for intercompany payment methods.""" intercompany_pms = self.payment_method_ids.filtered( lambda pm: pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id ) if not intercompany_pms: return super()._create_bank_payment_moves(data) combine_receivables_bank = data.get('combine_receivables_bank', {}) MoveLine = data.get('MoveLine') # Split the data into intercompany and standard standard_combine = {} intercompany_combine = {} for pm, amounts in combine_receivables_bank.items(): if pm in intercompany_pms: intercompany_combine[pm] = amounts else: standard_combine[pm] = amounts # Call super with only standard payments data['combine_receivables_bank'] = standard_combine res_data = super()._create_bank_payment_moves(data) # Restore original data data['combine_receivables_bank'] = combine_receivables_bank # Manually handle intercompany ones: create the line in main move but skip account.payment for pm, amounts in intercompany_combine.items(): 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 return res_data def _create_intercompany_clearing_moves(self): for session in self: if session.state != 'closed' or not session.move_id: continue # Dictionary to accumulate amounts per payment method clearing_amounts = {} # Find all orders and payments for this session orders = session.order_ids for order in orders: for payment in order.payment_ids: pm = payment.payment_method_id if pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id: if pm not in clearing_amounts: clearing_amounts[pm] = 0.0 clearing_amounts[pm] += payment.amount # Group PMs by their clearing journal journal_to_pms = {} 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 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 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): """Create a single mirror journal entry in the parent company, with separate lines per PM. For each PM: Debit: Outstanding receipt account of the parent bank journal Credit: Hubungan RK liability (229101) in the parent """ parent_groups = {} for data in pm_level_data: 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 parent_rk_account = pm.parent_intercompany_account_id 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 for pml in parent_bank_journal.inbound_payment_method_line_ids: if pml.payment_account_id: outstanding_receipt_account = pml.payment_account_id 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) if group_key not in parent_groups: parent_groups[group_key] = { 'parent_company': parent_company, 'parent_clearing_journal': parent_clearing_journal, 'lines_data': [] } parent_groups[group_key]['lines_data'].append({ 'pm': pm, 'amount': data['amount'], 'amount_company_curr': data['amount_company_curr'], 'outstanding_receipt_account': outstanding_receipt_account, 'parent_rk_account': parent_rk_account, }) entry_date = session.stop_at or fields.Date.context_today(session) for group_key, group_data in parent_groups.items(): parent_company = group_data['parent_company'] parent_clearing_journal = group_data['parent_clearing_journal'] line_ids = [] for line_data in group_data['lines_data']: pm = line_data['pm'] amount = line_data['amount'] parent_currency = parent_company.currency_id if session.currency_id != parent_currency: amount_parent_curr = session.currency_id._convert( amount, parent_currency, parent_company, entry_date ) else: amount_parent_curr = amount line_ids.append(Command.create({ 'name': f"POS Receipt: {pm.name} ({session.company_id.name})", 'account_id': line_data['outstanding_receipt_account'].id, 'debit': amount_parent_curr, 'credit': 0.0, 'currency_id': session.currency_id.id, 'amount_currency': amount, })) line_ids.append(Command.create({ 'name': f"Due to Branch: {pm.name} ({session.company_id.name})", 'account_id': line_data['parent_rk_account'].id, 'debit': 0.0, 'credit': amount_parent_curr, 'currency_id': session.currency_id.id, 'amount_currency': -amount, })) if not line_ids: continue parent_move_vals = { 'journal_id': parent_clearing_journal.id, 'date': entry_date, 'ref': f"POS Mirror: {session.name} - {session.company_id.name}", 'move_type': 'entry', 'company_id': parent_company.id, 'line_ids': line_ids, } 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", session.name, parent_company.name, e )