From 98eca2d0cc90863c3b2beb3e26fa80a8ef6fb627 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Tue, 9 Jun 2026 07:11:56 +0700 Subject: [PATCH] first commit --- .gitignore | 3 + README.md | 11 +++ __init__.py | 1 + __manifest__.py | 21 +++++ models/__init__.py | 1 + models/account_payment.py | 135 ++++++++++++++++++++++++++++++++ views/account_payment_views.xml | 27 +++++++ 7 files changed, 199 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 models/__init__.py create mode 100644 models/account_payment.py create mode 100644 views/account_payment_views.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d1a15e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.pyo +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..658f602 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Vendor Payment with Misc Journals - Auto Second Entry + +This module automatically generates a second journal entry when a vendor payment is confirmed using a general (misc) journal. + +## Features + +- Creates a linked second journal entry upon payment confirmation. +- Debits the misc journal default account (e.g. 111101). +- Credits the branch company cash journal default account (e.g. 111103). +- Keeps the second entry in sync with the payment lifecycle (posts, resets to draft, cancels, and unlinks when the payment is updated). +- Adds a convenient stat button on the payment form to view the linked second entry. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..883733a --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,21 @@ +{ + 'name': 'Vendor Payment with Misc Journals - Auto Second Entry', + 'version': '19.0.1.0.0', + 'category': 'Accounting', + 'summary': 'Automatically generate second journal entry for vendor payments using misc journals', + 'description': """ +This module automatically generates a second journal entry when a vendor payment is confirmed using a general journal. +The second entry debits the misc journal default account (111101) and credits the branch company cash journal default account (111103). + """, + 'author': 'Suherdy Yacob', + 'depends': [ + 'account', + 'vendor_payment_misc', + ], + 'data': [ + 'views/account_payment_views.xml', + ], + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..ab350b8 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +from . import account_payment diff --git a/models/account_payment.py b/models/account_payment.py new file mode 100644 index 0000000..e4ea6bc --- /dev/null +++ b/models/account_payment.py @@ -0,0 +1,135 @@ +from odoo import models, fields, api, _, Command +from odoo.exceptions import UserError + +class AccountPayment(models.Model): + _inherit = 'account.payment' + + second_move_id = fields.Many2one( + 'account.move', + string='Second Journal Entry', + copy=False, + ondelete='set null', + help='The second journal entry automatically created to transfer funds from branch cash to Kas Kecil.' + ) + + def action_post(self): + # Call super first to post the main entry + super().action_post() + + for payment in self: + # Only generate/post the second entry if the journal type is 'general' + if payment.journal_id.type == 'general': + if not payment.second_move_id: + # Find the cash journal for this branch/company + cash_journal = self.env['account.journal'].search([ + ('company_id', '=', payment.company_id.id), + ('type', '=', 'cash') + ], limit=1) + + if not cash_journal or not cash_journal.default_account_id: + raise UserError(_("No cash journal with a default account found for company %s.") % payment.company_id.name) + + if not payment.journal_id.default_account_id: + raise UserError(_("No default account found on general journal %s.") % payment.journal_id.name) + + debit_account = payment.journal_id.default_account_id + credit_account = cash_journal.default_account_id + + amount = payment.amount + currency = payment.currency_id + company = payment.company_id + + if currency != company.currency_id: + balance = currency._convert( + amount, + company.currency_id, + company, + payment.date or fields.Date.today() + ) + else: + balance = amount + + debit_line_vals = { + 'name': _("Second Entry Debit for %s") % payment.name, + 'account_id': debit_account.id, + 'debit': balance, + 'credit': 0.0, + 'partner_id': payment.partner_id.id, + } + credit_line_vals = { + 'name': _("Second Entry Credit for %s") % payment.name, + 'account_id': credit_account.id, + 'debit': 0.0, + 'credit': balance, + 'partner_id': payment.partner_id.id, + } + + if currency != company.currency_id: + debit_line_vals.update({ + 'currency_id': currency.id, + 'amount_currency': amount, + }) + credit_line_vals.update({ + 'currency_id': currency.id, + 'amount_currency': -amount, + }) + + line_ids = [ + Command.create(debit_line_vals), + Command.create(credit_line_vals), + ] + + move_vals = { + 'move_type': 'entry', + 'ref': payment.name, + 'date': payment.date, + 'journal_id': payment.journal_id.id, + 'company_id': payment.company_id.id, + 'partner_id': payment.partner_id.id, + 'currency_id': payment.currency_id.id, + 'line_ids': line_ids, + } + + # Create and post the second move + second_move = self.env['account.move'].create(move_vals) + second_move.action_post() + payment.write({'second_move_id': second_move.id}) + elif payment.second_move_id.state == 'draft': + # If it already exists and is draft (e.g. after resetting payment to draft), post it + payment.second_move_id.action_post() + + def action_draft(self): + super().action_draft() + for payment in self: + if payment.second_move_id: + if payment.second_move_id.state != 'draft': + payment.second_move_id.button_draft() + + def action_cancel(self): + super().action_cancel() + for payment in self: + if payment.second_move_id: + if payment.second_move_id.state == 'draft': + payment.second_move_id.unlink() + else: + payment.second_move_id.button_cancel() + + def unlink(self): + for payment in self: + if payment.second_move_id: + second_move = payment.second_move_id + if second_move.state != 'draft': + second_move.button_draft() + second_move.unlink() + return super().unlink() + + def button_open_second_journal_entry(self): + self.ensure_one() + return { + 'name': _("Second Journal Entry"), + 'type': 'ir.actions.act_window', + 'res_model': 'account.move', + 'context': {'create': False}, + 'view_mode': 'form', + 'res_id': self.second_move_id.id, + } diff --git a/views/account_payment_views.xml b/views/account_payment_views.xml new file mode 100644 index 0000000..69b3b72 --- /dev/null +++ b/views/account_payment_views.xml @@ -0,0 +1,27 @@ + + + + account.payment.form.inherit.double.entry + account.payment + + + +
+ +
+ + + + +
+
+