Compare commits
2 Commits
1e42b50a9c
...
6330fd914c
| Author | SHA1 | Date | |
|---|---|---|---|
| 6330fd914c | |||
| fdafdefccd |
@ -11,14 +11,13 @@ class PosSession(models.Model):
|
|||||||
def _validate_session(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
|
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)
|
res = super(PosSession, self)._validate_session(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
||||||
|
|
||||||
# After the standard validation, we create the parent mirror entries
|
# After the standard validation and account move creation, we create the inter-company clearing moves
|
||||||
self._create_intercompany_parent_moves()
|
self._create_intercompany_clearing_moves()
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _create_bank_payment_moves(self, data):
|
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(
|
intercompany_pms = self.payment_method_ids.filtered(
|
||||||
lambda pm: pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id
|
lambda pm: pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id
|
||||||
)
|
)
|
||||||
@ -48,49 +47,23 @@ class PosSession(models.Model):
|
|||||||
|
|
||||||
# Manually handle intercompany ones: create the line in main move but skip account.payment
|
# Manually handle intercompany ones: create the line in main move but skip account.payment
|
||||||
for pm, amounts in intercompany_combine.items():
|
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']))
|
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
|
return res_data
|
||||||
|
|
||||||
def _create_intercompany_parent_moves(self):
|
def _create_intercompany_clearing_moves(self):
|
||||||
for session in self:
|
for session in self:
|
||||||
if session.state != 'closed' or not session.move_id:
|
if session.state != 'closed' or not session.move_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Accumulate amounts per payment method
|
# Dictionary to accumulate amounts per payment method
|
||||||
clearing_amounts = {}
|
clearing_amounts = {}
|
||||||
for order in session.order_ids:
|
|
||||||
|
# Find all orders and payments for this session
|
||||||
|
orders = session.order_ids
|
||||||
|
|
||||||
|
for order in orders:
|
||||||
for payment in order.payment_ids:
|
for payment in order.payment_ids:
|
||||||
pm = payment.payment_method_id
|
pm = payment.payment_method_id
|
||||||
if pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id:
|
if pm.intercompany_clearing_account_id and pm.intercompany_clearing_journal_id:
|
||||||
@ -98,28 +71,136 @@ class PosSession(models.Model):
|
|||||||
clearing_amounts[pm] = 0.0
|
clearing_amounts[pm] = 0.0
|
||||||
clearing_amounts[pm] += payment.amount
|
clearing_amounts[pm] += payment.amount
|
||||||
|
|
||||||
if not clearing_amounts:
|
# Group PMs by their clearing journal
|
||||||
continue
|
journal_to_pms = {}
|
||||||
|
|
||||||
pm_level_data = []
|
|
||||||
for pm, amount in clearing_amounts.items():
|
for pm, amount in clearing_amounts.items():
|
||||||
if session.currency_id.is_zero(amount):
|
if session.currency_id.is_zero(amount):
|
||||||
continue
|
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
|
amount_company_curr = amount
|
||||||
if session.currency_id != session.company_id.currency_id:
|
if session.currency_id != session.company_id.currency_id:
|
||||||
amount_company_curr = session.currency_id._convert(
|
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)
|
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_level_data.append({
|
||||||
'pm': pm,
|
'pm': pm,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'amount_company_curr': amount_company_curr,
|
'amount_company_curr': amount_company_curr,
|
||||||
})
|
})
|
||||||
|
|
||||||
if pm_level_data:
|
# Aggregate for branch move
|
||||||
self._create_aggregated_parent_mirror_move(session, pm_level_data)
|
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)
|
||||||
|
|
||||||
def _create_aggregated_parent_mirror_move(self, 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
|
||||||
|
# Odoo 19 names these lines as "SessionName - PMName"
|
||||||
|
pos_debit_lines = session.move_id.line_ids.filtered(
|
||||||
|
lambda l: l.account_id == receivable_account and l.debit > 0 and \
|
||||||
|
any(l.name.endswith(f" - {pm.name}") for pm in pms)
|
||||||
|
)
|
||||||
|
|
||||||
|
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.
|
"""Create a single mirror journal entry in the parent company, with separate lines per PM.
|
||||||
|
|
||||||
For each PM:
|
For each PM:
|
||||||
@ -131,6 +212,7 @@ class PosSession(models.Model):
|
|||||||
pm = data['pm']
|
pm = data['pm']
|
||||||
parent_company = pm.parent_company_id or session.company_id.parent_id
|
parent_company = pm.parent_company_id or session.company_id.parent_id
|
||||||
if not parent_company:
|
if not parent_company:
|
||||||
|
_logger.info("No parent company configured for PM %s, skipping parent mirror entry.", pm.name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parent_bank_journal = pm.parent_bank_journal_id
|
parent_bank_journal = pm.parent_bank_journal_id
|
||||||
@ -138,6 +220,7 @@ class PosSession(models.Model):
|
|||||||
parent_clearing_journal = pm.parent_clearing_journal_id
|
parent_clearing_journal = pm.parent_clearing_journal_id
|
||||||
|
|
||||||
if not parent_bank_journal or not parent_rk_account or not parent_clearing_journal:
|
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
|
continue
|
||||||
|
|
||||||
outstanding_receipt_account = None
|
outstanding_receipt_account = None
|
||||||
@ -147,6 +230,7 @@ class PosSession(models.Model):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not outstanding_receipt_account:
|
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
|
continue
|
||||||
|
|
||||||
group_key = (parent_company.id, parent_clearing_journal.id)
|
group_key = (parent_company.id, parent_clearing_journal.id)
|
||||||
@ -218,6 +302,10 @@ class PosSession(models.Model):
|
|||||||
try:
|
try:
|
||||||
parent_move = self.env['account.move'].sudo().with_company(parent_company).create(parent_move_vals)
|
parent_move = self.env['account.move'].sudo().with_company(parent_company).create(parent_move_vals)
|
||||||
parent_move._post()
|
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:
|
except Exception as e:
|
||||||
_logger.error(
|
_logger.error(
|
||||||
"Failed to create aggregated parent mirror entry for session %s in company %s: %s",
|
"Failed to create aggregated parent mirror entry for session %s in company %s: %s",
|
||||||
|
|||||||
@ -40,6 +40,16 @@ class AccountPaymentRegister(models.TransientModel):
|
|||||||
# Debit: Payable Account (clears the vendor bill)
|
# Debit: Payable Account (clears the vendor bill)
|
||||||
# Credit: Inter-company (RK) Account (Liability to Parent)
|
# Credit: Inter-company (RK) Account (Liability to Parent)
|
||||||
|
|
||||||
|
# Currency logic: Odoo 19 requires debit/credit in company currency
|
||||||
|
# and amount_currency in foreign currency.
|
||||||
|
amount_company_curr = abs(sum(lines.mapped('amount_residual')))
|
||||||
|
amount_currency = 0.0
|
||||||
|
currency_id = False
|
||||||
|
|
||||||
|
if self.currency_id != branch_company.currency_id:
|
||||||
|
amount_currency = sum(lines.mapped('amount_residual_currency'))
|
||||||
|
currency_id = self.currency_id.id
|
||||||
|
|
||||||
clearing_move_vals = {
|
clearing_move_vals = {
|
||||||
'move_type': 'entry',
|
'move_type': 'entry',
|
||||||
'company_id': branch_company.id,
|
'company_id': branch_company.id,
|
||||||
@ -51,19 +61,19 @@ class AccountPaymentRegister(models.TransientModel):
|
|||||||
'name': _("Clearing: %s") % (", ".join(lines.move_id.mapped('name'))),
|
'name': _("Clearing: %s") % (", ".join(lines.move_id.mapped('name'))),
|
||||||
'partner_id': self.partner_id.id,
|
'partner_id': self.partner_id.id,
|
||||||
'account_id': lines[0].account_id.id, # The payable account
|
'account_id': lines[0].account_id.id, # The payable account
|
||||||
'debit': amount if self.payment_type == 'outbound' else 0.0,
|
'debit': amount_company_curr if self.payment_type == 'outbound' else 0.0,
|
||||||
'credit': amount if self.payment_type == 'inbound' else 0.0,
|
'credit': amount_company_curr if self.payment_type == 'inbound' else 0.0,
|
||||||
'currency_id': self.currency_id.id,
|
'currency_id': currency_id,
|
||||||
'amount_currency': amount if self.payment_type == 'outbound' else -amount,
|
'amount_currency': amount_currency if self.payment_type == 'outbound' else -amount_currency,
|
||||||
}),
|
}),
|
||||||
(0, 0, {
|
(0, 0, {
|
||||||
'name': _("Due to Parent (%s)") % parent_company.name,
|
'name': _("Due to Parent (%s)") % parent_company.name,
|
||||||
'partner_id': False,
|
'partner_id': False,
|
||||||
'account_id': journal.branch_intercompany_account_id.id,
|
'account_id': journal.branch_intercompany_account_id.id,
|
||||||
'debit': amount if self.payment_type == 'inbound' else 0.0,
|
'debit': amount_company_curr if self.payment_type == 'inbound' else 0.0,
|
||||||
'credit': amount if self.payment_type == 'outbound' else 0.0,
|
'credit': amount_company_curr if self.payment_type == 'outbound' else 0.0,
|
||||||
'currency_id': self.currency_id.id,
|
'currency_id': currency_id,
|
||||||
'amount_currency': -amount if self.payment_type == 'outbound' else amount,
|
'amount_currency': -amount_currency if self.payment_type == 'outbound' else amount_currency,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@ -83,7 +93,7 @@ class AccountPaymentRegister(models.TransientModel):
|
|||||||
'payment_type': self.payment_type,
|
'payment_type': self.payment_type,
|
||||||
'partner_type': self.partner_type,
|
'partner_type': self.partner_type,
|
||||||
'partner_id': self.partner_id.id,
|
'partner_id': self.partner_id.id,
|
||||||
'amount': amount,
|
'amount': abs(amount_currency) if currency_id else amount_company_curr,
|
||||||
'currency_id': self.currency_id.id,
|
'currency_id': self.currency_id.id,
|
||||||
'date': self.payment_date,
|
'date': self.payment_date,
|
||||||
'memo': _("Centralized Pay for %s (%s)") % (branch_company.name, ", ".join(lines.move_id.mapped('name'))),
|
'memo': _("Centralized Pay for %s (%s)") % (branch_company.name, ", ".join(lines.move_id.mapped('name'))),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user