diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d276bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Python +*.py[cod] +__pycache__/ +*.so + +# Odoo +*.po~ +*.pot~ + +# Editor / System +.DS_Store +.vscode/ +*.swp +*.swo +*~ diff --git a/__manifest__.py b/__manifest__.py index d7c165d..d5456a4 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Vendor Payment with Misc Journals', - 'version': '17.0.1.0.0', + 'version': '19.0.1.0.0', 'category': 'Accounting', 'summary': 'Allow using misc journals for vendor bill payments', 'description': """ diff --git a/models/account_payment.py b/models/account_payment.py index aed736c..1127ad6 100644 --- a/models/account_payment.py +++ b/models/account_payment.py @@ -1,219 +1,219 @@ -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() - - # Skip validation if payment has amount_substract (from vendor_payment_diff_amount module) - # This module creates additional lines that don't fit the standard pattern - has_substract = hasattr(pay, 'amount_substract') and pay.amount_substract and pay.amount_substract > 0 - - if not has_substract: - 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, - )) - - # Allow for additional lines (like substract account from vendor_payment_diff_amount) - # Check if we have at least one counterpart line, not exactly one - if len(counterpart_lines) < 1: - raise UserError(_( - "Journal Entry %s is not valid. In order to proceed, the journal items must " - "include at least 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: +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.env.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() + + # Skip validation if payment has amount_substract (from vendor_payment_diff_amount module) + # This module creates additional lines that don't fit the standard pattern + has_substract = hasattr(pay, 'amount_substract') and pay.amount_substract and pay.amount_substract > 0 + + if not has_substract: + 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, + )) + + # Allow for additional lines (like substract account from vendor_payment_diff_amount) + # Check if we have at least one counterpart line, not exactly one + if len(counterpart_lines) < 1: + raise UserError(_( + "Journal Entry %s is not valid. In order to proceed, the journal items must " + "include at least 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