account_shared_bank_cash/models/pos_session.py

297 lines
14 KiB
Python

# -*- 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 moves (separate per PM as requested)
for data in pm_level_data:
self._create_parent_mirror_move(session, data['pm'], data['amount'], data['amount_company_curr'], 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_parent_mirror_move(self, session, pm, amount, amount_company_curr, branch_clearing_move):
"""Create the mirror journal entry in the parent company.
Debit: Outstanding receipt account of the parent bank journal
(for bank statement reconciliation in the parent)
Credit: Hubungan RK liability (229101) in the parent
(records the parent's liability to the branch)
"""
# Determine parent company
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)
return
# Validate required parent configuration
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. "
"Need: parent_bank_journal_id, parent_intercompany_account_id, parent_clearing_journal_id. "
"Skipping parent mirror entry.",
pm.name
)
return
# Get the outstanding receipt account from the parent bank journal
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 parent mirror entry for PM %s.",
parent_bank_journal.name, pm.name
)
return
# Convert amount to parent company currency if needed
parent_currency = parent_company.currency_id
if session.currency_id != parent_currency:
amount_parent_curr = session.currency_id._convert(
amount, parent_currency, parent_company,
session.stop_at or fields.Date.context_today(session)
)
else:
amount_parent_curr = amount
entry_date = session.stop_at or fields.Date.context_today(session)
parent_move_vals = {
'journal_id': parent_clearing_journal.id,
'date': entry_date,
'ref': f"POS Mirror: {session.name} ({pm.name}) - {session.company_id.name}",
'move_type': 'entry',
'company_id': parent_company.id,
'line_ids': [
Command.create({
'name': f"POS Receipt: {pm.name} ({session.company_id.name})",
'account_id': outstanding_receipt_account.id,
'debit': amount_parent_curr,
'credit': 0.0,
'currency_id': session.currency_id.id,
'amount_currency': amount,
}),
Command.create({
'name': f"Due to Branch: {pm.name} ({session.company_id.name})",
'account_id': parent_rk_account.id,
'debit': 0.0,
'credit': amount_parent_curr,
'currency_id': session.currency_id.id,
'amount_currency': -amount,
}),
]
}
try:
parent_move = self.env['account.move'].sudo().with_company(parent_company).create(parent_move_vals)
parent_move._post()
_logger.info(
"Created parent mirror entry %s in company %s for session %s PM %s (amount: %s)",
parent_move.name, parent_company.name, session.name, pm.name, amount_parent_curr
)
except Exception as e:
_logger.error(
"Failed to create parent mirror entry for session %s, PM %s in company %s: %s",
session.name, pm.name, parent_company.name, e
)