# -*- coding: utf-8 -*- import logging import pytz from datetime import datetime, timedelta from odoo import models, fields, api, _ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class AccountJournal(models.Model): _inherit = 'account.journal' @api.model def _cron_create_cash_intercompany_entries(self, target_date=None, journal_ids=None): """Cron action to automatically generate cash centralization entries. Runs daily at 10:00 AM WIB (03:00 AM UTC) to process the previous day's net cash movements. """ # Determine the target date (previous day in WIB by default) if not target_date: jakarta_tz = pytz.timezone('Asia/Jakarta') now_jakarta = datetime.now(jakarta_tz) prev_day = (now_jakarta - timedelta(days=1)).date() elif isinstance(target_date, str): prev_day = fields.Date.from_string(target_date) else: prev_day = target_date _logger.info("Starting cash intercompany centralization for date %s", prev_day) # Search for all cash journals that have centralization configured domain = [ ('type', '=', 'cash'), ('is_centralized', '=', True) ] if journal_ids: domain.append(('id', 'in', journal_ids)) journals = self.sudo().search(domain) results = [] for journal in journals: # Check basic configuration requirements parent_company = journal.parent_company_id parent_journal = journal.parent_journal_id branch_rk_account = journal.branch_intercompany_account_id parent_rk_account = journal.parent_intercompany_account_id if not parent_company or not parent_journal or not branch_rk_account or not parent_rk_account: msg = _("Centralization settings are incomplete (Parent Company, Parent Journal, or RK accounts not configured).") _logger.warning("Skipping cash journal %s (%s): %s", journal.name, journal.company_id.name, msg) results.append((journal.id, 'incomplete', msg)) continue # Ensure default cash account is present branch_cash_account = journal.default_account_id if not branch_cash_account: msg = _("Journal has no default account configured.") _logger.warning("Skipping cash journal %s (%s): %s", journal.name, journal.company_id.name, msg) results.append((journal.id, 'no_account', msg)) continue # Check for existing intercompany transfer entries for this day to prevent duplication existing_move = self.env['account.move'].sudo().search([ ('journal_id', '=', journal.id), ('date', '=', prev_day), ('is_cash_intercompany_transfer', '=', True), ('state', '!=', 'cancel') ], limit=1) if existing_move: msg = _("Centralization already run for this date. (Move: %s)") % existing_move.name _logger.info("Skipping cash journal %s (%s): %s", journal.name, journal.company_id.name, msg) results.append((journal.id, 'already_run', msg)) continue # Find all posted journal items on the branch cash account for the target date, # excluding centralization transfers we generate. lines = self.env['account.move.line'].sudo().search([ ('company_id', '=', journal.company_id.id), ('account_id', '=', branch_cash_account.id), ('date', '=', prev_day), ('parent_state', '=', 'posted'), ('move_id.is_cash_intercompany_transfer', '=', False) ]) # Calculate net movement (debit - credit) debit_sum = sum(lines.mapped('debit')) credit_sum = sum(lines.mapped('credit')) net_balance = debit_sum - credit_sum _logger.info( "Journal %s (%s) net movement on %s: Debits=%s, Credits=%s, Net=%s", journal.name, journal.company_id.name, prev_day, debit_sum, credit_sum, net_balance ) if net_balance <= 0.0: msg = _("No positive net cash movement (Net Balance: %s).") % net_balance _logger.info("Skipping journal %s on %s: %s", journal.name, prev_day, msg) results.append((journal.id, 'no_balance', msg)) continue # Identify the parent cash account ( Kas Besar Hasil Penjualan - 111103) # Search by code 111103 with the parent company's context parent_cash_account = self.env['account.account'].sudo().with_company(parent_company).search([ ('code', '=', '111103'), ('company_ids', 'in', [parent_company.id]) ], limit=1) if not parent_cash_account: parent_cash_account = parent_journal.default_account_id if not parent_cash_account: msg = _("Parent cash account (111103 or default account of parent journal) not found in company %s.") % parent_company.name _logger.error("Skipping journal %s: %s", journal.name, msg) results.append((journal.id, 'no_parent_account', msg)) continue # Handle currency conversion if parent and branch currencies differ parent_currency = parent_company.currency_id branch_currency = journal.company_id.currency_id if branch_currency != parent_currency: amount_parent_curr = branch_currency._convert( net_balance, parent_currency, parent_company, prev_day ) else: amount_parent_curr = net_balance # 1. Create the Branch company journal entry branch_line_ids = [ (0, 0, { 'name': _("Cash Centralization - %s") % prev_day, 'account_id': branch_rk_account.id, 'debit': net_balance, 'credit': 0.0, 'company_id': journal.company_id.id, 'display_type': 'product', }), (0, 0, { 'name': _("Cash Centralization - %s") % prev_day, 'account_id': branch_cash_account.id, 'debit': 0.0, 'credit': net_balance, 'company_id': journal.company_id.id, 'display_type': 'product', }) ] # 2. Create the Parent company journal entry parent_line_ids = [ (0, 0, { 'name': _("Cash Centralization: %s - %s") % (journal.company_id.name, prev_day), 'account_id': parent_cash_account.id, 'debit': amount_parent_curr, 'credit': 0.0, 'partner_id': journal.company_id.partner_id.id, 'company_id': parent_company.id, 'display_type': 'product', }), (0, 0, { 'name': _("Cash Centralization: %s - %s") % (journal.company_id.name, prev_day), 'account_id': parent_rk_account.id, 'debit': 0.0, 'credit': amount_parent_curr, 'partner_id': journal.company_id.partner_id.id, 'company_id': parent_company.id, 'display_type': 'product', }) ] try: # Create and post branch entry branch_move = self.env['account.move'].sudo().with_company(journal.company_id).create({ 'move_type': 'entry', 'date': prev_day, 'company_id': journal.company_id.id, 'journal_id': journal.id, 'ref': f"Cash Centralization - {prev_day}", 'is_cash_intercompany_transfer': True, 'line_ids': branch_line_ids, }) branch_move.action_post() # Create and post parent entry parent_move = self.env['account.move'].sudo().with_company(parent_company).create({ 'move_type': 'entry', 'date': prev_day, 'company_id': parent_company.id, 'journal_id': parent_journal.id, 'partner_id': journal.company_id.partner_id.id, 'ref': f"Cash Centralization - {prev_day} ({journal.company_id.name})", 'is_cash_intercompany_transfer': True, 'line_ids': parent_line_ids, }) parent_move.action_post() msg = _("Successfully created branch entry %s and parent entry %s.") % (branch_move.name, parent_move.name) _logger.info("Successfully centralized cash for %s: %s", journal.name, msg) results.append((journal.id, 'success', msg)) except Exception as e: msg = str(e) _logger.error( "Failed to create cash centralization entries for journal %s on %s: %s", journal.name, prev_day, msg ) results.append((journal.id, 'error', msg)) return results