commit bd8af2382a5ab97152bfa35c32cbc3cbccb1893d Author: Suherdy Yacob Date: Tue Jun 9 17:03:33 2026 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07d48cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..30711ca --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +Account Shared Bank Cash Auto Entry +=================================== + +This module automatically creates and posts cash centralization entries between branch companies and the parent company based on the net cash movement of the previous day. + +Key Features: +* Run automatically every day at 10:00 AM WIB (03:00 AM UTC). +* Calculates net cash balance of the default cash account (111103) from the previous day. +* Accounts for vendor payments (from the vendor_payment_misc_auto_entry module) and other cash movements. +* Automatically converts currencies if the parent and branch company currencies are different. +* Generates and posts the corresponding intercompany journal entries in both companies. + +Configuration: +* Relies on the configuration set in the parent module "account_shared_bank_cash". +* Configure the Parent Company, Parent Journal, and Intercompany accounts on the branch company's cash journal. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..f2155f7 --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import models +from . import wizard + diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..3342125 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Account Shared Bank Cash Auto Entry', + 'version': '1.0', + 'category': 'Accounting', + 'summary': 'Automatically generate cash centralization journal entries next day at 10:00 AM WIB', + 'author': 'Suherdy Yacob', + 'depends': ['account_shared_bank_cash'], + 'data': [ + 'security/ir.model.access.csv', + 'wizard/account_cash_centralization_wizard_views.xml', + 'views/account_journal_views.xml', + 'data/ir_cron.xml', + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/data/ir_cron.xml b/data/ir_cron.xml new file mode 100644 index 0000000..efc84c2 --- /dev/null +++ b/data/ir_cron.xml @@ -0,0 +1,14 @@ + + + + Account Cash Shared Centralization Auto Entry + + code + model._cron_create_cash_intercompany_entries() + 1 + days + + + + + diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..3b95454 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import account_move +from . import account_journal diff --git a/models/account_journal.py b/models/account_journal.py new file mode 100644 index 0000000..13b5a59 --- /dev/null +++ b/models/account_journal.py @@ -0,0 +1,210 @@ +# -*- 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 + diff --git a/models/account_move.py b/models/account_move.py new file mode 100644 index 0000000..dbc8cec --- /dev/null +++ b/models/account_move.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields + +class AccountMove(models.Model): + _inherit = 'account.move' + + is_cash_intercompany_transfer = fields.Boolean( + string='Is Cash Intercompany Transfer', + copy=False, + default=False, + help='Indicates if this journal entry was automatically created for cash intercompany centralization.' + ) diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..b03e3bf --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_cash_centralization_wizard_user,account.cash.centralization.wizard.user,model_account_cash_centralization_wizard,account.group_account_user,1,1,1,1 diff --git a/views/account_journal_views.xml b/views/account_journal_views.xml new file mode 100644 index 0000000..cacb924 --- /dev/null +++ b/views/account_journal_views.xml @@ -0,0 +1,18 @@ + + + + account.journal.form.inherit.auto.entry + account.journal + + + + +