commit 8f13d5bc826665fa325f3c561c0fe9641351b6e1 Author: Suherdy Yacob Date: Wed Oct 22 22:30:58 2025 +0700 first commit diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..9a7e03e --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..2fb2123 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': 'Vendor Payment with Misc Journals', + 'version': '17.0.1.0.0', + 'category': 'Accounting', + 'summary': 'Allow using misc journals for vendor bill payments', + 'description': """ + This module extends the vendor payment functionality to allow using misc journals + for registering payments of vendor bills. When a misc journal is selected, + the payment entry will use the default account of the selected journal. + """, + 'author': 'Suherdy Yacob', + 'depends': [ + 'account', + 'purchase', + ], + 'data': [ + 'views/account_payment_views.xml', + ], + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..2ac8b3b --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_payment +from . import account_payment_register \ No newline at end of file diff --git a/models/account_payment.py b/models/account_payment.py new file mode 100644 index 0000000..4e98f25 --- /dev/null +++ b/models/account_payment.py @@ -0,0 +1,212 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError + + +class AccountPayment(models.Model): + _inherit = 'account.payment' + + @api.depends('payment_type', 'partner_type') + def _compute_available_journal_ids(self): + """ + Override to include misc journals for vendor payments + """ + # Call the parent method to get the original computation with bank/cash journals + super()._compute_available_journal_ids() + + # Then extend with general journals for supplier payments + for pay in self: + if pay.partner_type == 'supplier': + # Include all general journals (not just those with payment methods) + # We'll handle payment method availability separately + all_general_journals = self.env['account.journal'].search([ + '|', + ('company_id', 'parent_of', self.env.company.id), + ('company_id', 'child_of', self.env.company.id), + ('type', '=', 'general'), + ]) + + pay.available_journal_ids |= all_general_journals + + @api.depends('payment_type', 'journal_id', 'currency_id') + def _compute_payment_method_line_fields(self): + """ + Override to include payment methods for general journals + """ + for pay in self: + available_payment_method_lines = pay.journal_id._get_available_payment_method_lines(pay.payment_type) + + # For general journals, if no payment methods are available, provide defaults + if pay.journal_id.type == 'general' and not available_payment_method_lines: + # Get the manual payment methods (these work with general journals) + if pay.payment_type == 'outbound': + manual_out = self.env.ref('account.account_payment_method_manual_out', raise_if_not_found=False) + if manual_out: + # Create a payment method line for this journal if it doesn't exist + method_line = self.env['account.payment.method.line'].search([ + ('journal_id', '=', pay.journal_id.id), + ('payment_method_id', '=', manual_out.id) + ], limit=1) + + if not method_line: + method_line = self.env['account.payment.method.line'].create({ + 'name': 'Manual (Outbound)', + 'payment_method_id': manual_out.id, + 'journal_id': pay.journal_id.id, + }) + + available_payment_method_lines = method_line + elif pay.payment_type == 'inbound': + manual_in = self.env.ref('account.account_payment_method_manual_in', raise_if_not_found=False) + if manual_in: + # Create a payment method line for this journal if it doesn't exist + method_line = self.env['account.payment.method.line'].search([ + ('journal_id', '=', pay.journal_id.id), + ('payment_method_id', '=', manual_in.id) + ], limit=1) + + if not method_line: + method_line = self.env['account.payment.method.line'].create({ + 'name': 'Manual (Inbound)', + 'payment_method_id': manual_in.id, + 'journal_id': pay.journal_id.id, + }) + + available_payment_method_lines = method_line + + pay.available_payment_method_line_ids = available_payment_method_lines + to_exclude = pay._get_payment_method_codes_to_exclude() + if to_exclude: + pay.available_payment_method_line_ids = pay.available_payment_method_line_ids.filtered(lambda x: x.code not in to_exclude) + + @api.depends('available_payment_method_line_ids') + def _compute_payment_method_line_id(self): + """ + Override to ensure payment method is selected for general journals + """ + for pay in self: + available_payment_method_lines = pay.available_payment_method_line_ids + + # Select the first available one by default. + if pay.payment_method_line_id in available_payment_method_lines: + pay.payment_method_line_id = pay.payment_method_line_id + elif available_payment_method_lines: + pay.payment_method_line_id = available_payment_method_lines[0]._origin + else: + # For general journals, we might need to handle this differently + # But we'll let the constraint handle validation + pay.payment_method_line_id = False + + def _synchronize_from_moves(self, changed_fields): + """ + Override to allow general journals for payments + """ + # Remove the check that restricts journals to only bank/cash types + if self._context.get('skip_account_move_synchronization'): + return + + for pay in self.with_context(skip_account_move_synchronization=True): + + # After the migration to 14.0, the journal entry could be shared between the account.payment and the + # account.bank.statement.line. In that case, the synchronization will only be made with the statement line. + if pay.move_id.statement_line_id: + continue + + move = pay.move_id + move_vals_to_write = {} + payment_vals_to_write = {} + + if 'journal_id' in changed_fields: + # Remove the original restriction - allow general journals too + if pay.journal_id.type not in ('bank', 'cash', 'general'): + raise UserError(_("A payment must belongs to a bank, cash, or general journal.")) + + # Continue with the rest of the original method + if 'line_ids' in changed_fields: + all_lines = move.line_ids + liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines() + + if len(liquidity_lines) != 1: + raise UserError(_( + "Journal Entry %s is not valid. In order to proceed, the journal items must " + "include one and only one outstanding payments/receipts account.", + move.display_name, + )) + + if len(counterpart_lines) != 1: + raise UserError(_( + "Journal Entry %s is not valid. In order to proceed, the journal items must " + "include one and only one receivable/payable account (with an exception of " + "internal transfers).", + move.display_name, + )) + + if any(line.currency_id != all_lines[0].currency_id for line in all_lines): + raise UserError(_( + "Journal Entry %s is not valid. In order to proceed, the journal items must " + "share the same currency.", + move.display_name, + )) + + if any(line.partner_id != all_lines[0].partner_id for line in all_lines): + raise UserError(_( + "Journal Entry %s is not valid. In order to proceed, the journal items must " + "share the same partner.", + move.display_name, + )) + + if counterpart_lines.account_id.account_type == 'asset_receivable': + partner_type = 'customer' + else: + partner_type = 'supplier' + + liquidity_amount = liquidity_lines.amount_currency + + move_vals_to_write.update({ + 'currency_id': liquidity_lines.currency_id.id, + 'partner_id': liquidity_lines.partner_id.id, + }) + payment_vals_to_write.update({ + 'amount': abs(liquidity_amount), + 'partner_type': partner_type, + 'currency_id': liquidity_lines.currency_id.id, + 'destination_account_id': counterpart_lines.account_id.id, + 'partner_id': liquidity_lines.partner_id.id, + }) + if liquidity_amount > 0.0: + payment_vals_to_write.update({'payment_type': 'inbound'}) + elif liquidity_amount < 0.0: + payment_vals_to_write.update({'payment_type': 'outbound'}) + + move.write(move._cleanup_write_orm_values(move, move_vals_to_write)) + pay.write(move._cleanup_write_orm_values(pay, payment_vals_to_write)) + + @api.depends('journal_id', 'payment_type', 'payment_method_line_id') + def _compute_outstanding_account_id(self): + """ + Override to use the default account of misc journal when selected + """ + for pay in self: + # If using a general/misc journal, use its default account + if pay.journal_id.type == 'general': + if pay.journal_id.default_account_id: + pay.outstanding_account_id = pay.journal_id.default_account_id + else: + # Fallback to the original logic if no default account is set + if pay.payment_type == 'inbound': + pay.outstanding_account_id = (pay.payment_method_line_id.payment_account_id + or pay.journal_id.company_id.account_journal_payment_debit_account_id) + elif pay.payment_type == 'outbound': + pay.outstanding_account_id = (pay.payment_method_line_id.payment_account_id + or pay.journal_id.company_id.account_journal_payment_credit_account_id) + else: + pay.outstanding_account_id = False + else: + # For bank/cash journals, use the original logic + if pay.payment_type == 'inbound': + pay.outstanding_account_id = (pay.payment_method_line_id.payment_account_id + or pay.journal_id.company_id.account_journal_payment_debit_account_id) + elif pay.payment_type == 'outbound': + pay.outstanding_account_id = (pay.payment_method_line_id.payment_account_id + or pay.journal_id.company_id.account_journal_payment_credit_account_id) + else: + pay.outstanding_account_id = False \ No newline at end of file diff --git a/models/account_payment_register.py b/models/account_payment_register.py new file mode 100644 index 0000000..85a6bdd --- /dev/null +++ b/models/account_payment_register.py @@ -0,0 +1,94 @@ +from odoo import models, api + + +class AccountPaymentRegister(models.TransientModel): + _inherit = 'account.payment.register' + + @api.depends('payment_type', 'partner_type') + def _compute_available_journal_ids(self): + """ + Override to include misc journals for vendor payments in the register payment wizard + """ + # Call the parent method to get the original computation with bank/cash journals + super()._compute_available_journal_ids() + + # Then extend with general journals for supplier payments + for pay in self: + if pay.partner_type == 'supplier': + # Include all general journals for supplier payments + all_general_journals = self.env['account.journal'].search([ + '|', + ('company_id', 'parent_of', self.env.company.id), + ('company_id', 'child_of', self.env.company.id), + ('type', '=', 'general'), + ]) + + pay.available_journal_ids |= all_general_journals + + @api.depends('payment_type', 'journal_id', 'currency_id') + def _compute_payment_method_line_fields(self): + """ + Override to include payment methods for general journals in register payment wizard + """ + for pay in self: + available_payment_method_lines = pay.journal_id._get_available_payment_method_lines(pay.payment_type) + + # For general journals, if no payment methods are available, provide defaults + if pay.journal_id.type == 'general' and not available_payment_method_lines: + # Get the manual payment methods (these work with general journals) + if pay.payment_type == 'outbound': + manual_out = self.env.ref('account.account_payment_method_manual_out', raise_if_not_found=False) + if manual_out: + # Create a payment method line for this journal if it doesn't exist + method_line = self.env['account.payment.method.line'].search([ + ('journal_id', '=', pay.journal_id.id), + ('payment_method_id', '=', manual_out.id) + ], limit=1) + + if not method_line: + method_line = self.env['account.payment.method.line'].create({ + 'name': 'Manual (Outbound)', + 'payment_method_id': manual_out.id, + 'journal_id': pay.journal_id.id, + }) + + available_payment_method_lines = method_line + elif pay.payment_type == 'inbound': + manual_in = self.env.ref('account.account_payment_method_manual_in', raise_if_not_found=False) + if manual_in: + # Create a payment method line for this journal if it doesn't exist + method_line = self.env['account.payment.method.line'].search([ + ('journal_id', '=', pay.journal_id.id), + ('payment_method_id', '=', manual_in.id) + ], limit=1) + + if not method_line: + method_line = self.env['account.payment.method.line'].create({ + 'name': 'Manual (Inbound)', + 'payment_method_id': manual_in.id, + 'journal_id': pay.journal_id.id, + }) + + available_payment_method_lines = method_line + + pay.available_payment_method_line_ids = available_payment_method_lines + # Note: account.payment.register doesn't have _get_payment_method_codes_to_exclude method + # So we skip the exclusion logic for this model + + @api.depends('available_payment_method_line_ids') + def _compute_payment_method_line_id(self): + """ + Override to ensure payment method is selected for general journals in register payment wizard + """ + for pay in self: + available_payment_method_lines = pay.available_payment_method_line_ids + + # Select the first available one by default. + if pay.payment_method_line_id in available_payment_method_lines: + pay.payment_method_line_id = pay.payment_method_line_id + elif available_payment_method_lines: + pay.payment_method_line_id = available_payment_method_lines[0]._origin + else: + # For general journals, we might need to handle this differently + # But we'll let the constraint handle validation + pay.payment_method_line_id = False \ No newline at end of file diff --git a/views/__init__.py b/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/views/account_payment_views.xml b/views/account_payment_views.xml new file mode 100644 index 0000000..d67a79e --- /dev/null +++ b/views/account_payment_views.xml @@ -0,0 +1,26 @@ + + + + + account.payment.form.inherit.vendor.payment.misc + account.payment + + + + + + + + + + + account.register.payment.form.inherit.vendor.payment.misc + account.payment.register + + + + + + + + \ No newline at end of file