from odoo import models, fields, api from odoo.exceptions import UserError class CreateAdvancePaymentWizard(models.TransientModel): _name = 'create.advance.payment.wizard' _description = 'Create Advance Payment Wizard' purchase_order_id = fields.Many2one( 'purchase.order', string='Purchase Order', required=True, readonly=True ) partner_id = fields.Many2one( 'res.partner', string='Vendor', related='purchase_order_id.partner_id', readonly=True ) journal_id = fields.Many2one( 'account.journal', string='Journal', required=True, domain=[('type', 'in', ['bank', 'cash'])] ) payment_method_line_id = fields.Many2one( 'account.payment.method.line', string='Payment Method', domain="[('id', 'in', available_payment_method_line_ids)]", help='Manual: Pay by any method outside of Odoo.\n' 'Check: Pay by check and print it from Odoo.\n' 'Batch Deposit: Collect several checks at once.' ) available_payment_method_line_ids = fields.Many2many( 'account.payment.method.line', compute='_compute_available_payment_method_line_ids' ) amount = fields.Monetary( string='Amount', required=True, currency_field='currency_id' ) currency_id = fields.Many2one( 'res.currency', string='Currency', related='purchase_order_id.currency_id', readonly=True ) date = fields.Date( string='Date', required=True, default=fields.Date.context_today ) memo = fields.Char( string='Memo' ) expense_account_id = fields.Many2one( 'account.account', string='Expense Account', compute='_compute_expense_account', store=True, readonly=True ) @api.model def default_get(self, fields_list): res = super().default_get(fields_list) if self._context.get('default_purchase_order_id'): po = self.env['purchase.order'].browse(self._context['default_purchase_order_id']) res['amount'] = po.amount_residual res['memo'] = f'Advance payment for {po.name}' return res @api.depends('journal_id') def _compute_available_payment_method_line_ids(self): """Compute available payment methods based on selected journal""" for wizard in self: if wizard.journal_id: wizard.available_payment_method_line_ids = wizard.journal_id.outbound_payment_method_line_ids else: wizard.available_payment_method_line_ids = False @api.onchange('journal_id') def _onchange_journal_id(self): """Reset payment method when journal changes""" if self.journal_id: # Auto-select the first available payment method (usually 'manual') available_methods = self.journal_id.outbound_payment_method_line_ids if available_methods: # Prefer 'manual' payment method manual_method = available_methods.filtered( lambda l: l.payment_method_id.code == 'manual' )[:1] self.payment_method_line_id = manual_method or available_methods[:1] else: self.payment_method_line_id = False else: self.payment_method_line_id = False @api.depends('purchase_order_id') def _compute_expense_account(self): """Get expense account from default advance payment product""" for wizard in self: deposit_product_id = self.env['ir.config_parameter'].sudo().get_param( 'purchase_advance_payment.deposit_product_id') if deposit_product_id: product = self.env['product.product'].browse(int(deposit_product_id)) # Get expense account from product account = product.property_account_expense_id or product.categ_id.property_account_expense_categ_id wizard.expense_account_id = account.id else: wizard.expense_account_id = False def action_create_payment(self): """Create advance payment with proper journal entries""" self.ensure_one() if self.amount <= 0: raise UserError("Amount must be greater than zero.") # Get expense account from default deposit product if not self.expense_account_id: raise UserError( "Please configure a default advance payment product in Purchase settings " "with a valid expense account." ) # Get outstanding payment account from journal # For outbound payments, first check journal-specific account, then company default outstanding_account = None # Try to get from journal's outbound payment method if self.journal_id.outbound_payment_method_line_ids: outstanding_account = self.journal_id.outbound_payment_method_line_ids[0].payment_account_id # Fall back to company default if not set on journal if not outstanding_account: outstanding_account = self.journal_id.company_id.account_journal_payment_credit_account_id if not outstanding_account: raise UserError( f"Please configure an outstanding payment account for journal '{self.journal_id.name}'.\n" f"You can set it in:\n" f"1. Journal level: Accounting > Configuration > Journals > {self.journal_id.name} > " f"Outgoing Payments tab > Payment Method > Outstanding Payments Account\n" f"OR\n" f"2. Company level: Accounting > Configuration > Settings > Default Accounts > " f"Outstanding Payments Account" ) # Use the selected payment method line or get the appropriate one payment_method_line = self.payment_method_line_id if not payment_method_line: # Fallback: try to get manual payment method payment_method_line = self.journal_id.outbound_payment_method_line_ids.filtered( lambda l: l.payment_method_id.code == 'manual' )[:1] if not payment_method_line: # Fallback to first available outbound payment method payment_method_line = self.journal_id.outbound_payment_method_line_ids[:1] if not payment_method_line: raise UserError( f"No outbound payment method is configured for journal '{self.journal_id.name}'.\n" f"Please configure a payment method in: Accounting > Configuration > Journals > " f"{self.journal_id.name} > Outgoing Payments tab" ) # Create payment # CRITICAL: Set payment_type and partner_type FIRST, then journal_id, then payment_method_line_id # This ensures Odoo's onchange logic correctly filters and sets the payment method payment_vals = { 'payment_type': 'outbound', 'partner_type': 'supplier', 'partner_id': self.partner_id.id, 'journal_id': self.journal_id.id, 'payment_method_line_id': payment_method_line.id, 'amount': self.amount, 'currency_id': self.currency_id.id, 'date': self.date, 'ref': self.memo or f'Advance payment for {self.purchase_order_id.name}', 'purchase_order_id': self.purchase_order_id.id, 'is_advance_payment': True, } payment = self.env['account.payment'].create(payment_vals) # Force recompute of payment method line to ensure it's correctly set # This is necessary because Odoo might have changed it during create if payment.payment_method_line_id != payment_method_line: payment.write({'payment_method_line_id': payment_method_line.id}) # Create deposit line in purchase order immediately self._create_deposit_line() # Return action to open the created payment return { 'type': 'ir.actions.act_window', 'name': 'Advance Payment', 'res_model': 'account.payment', 'res_id': payment.id, 'view_mode': 'form', 'target': 'current', } def _create_deposit_line(self): """Create deposit line in purchase order""" # Get or create deposit product deposit_product_id = self.env['ir.config_parameter'].sudo().get_param( 'purchase_advance_payment.deposit_product_id') if not deposit_product_id: raise UserError( "Please configure a default advance payment product in Purchase settings." ) deposit_product = self.env['product.product'].browse(int(deposit_product_id)) # Check if deposit line already exists existing_deposit_line = self.purchase_order_id.order_line.filtered(lambda l: l.is_deposit) # Calculate new total advance payment new_advance_total = self.purchase_order_id.advance_payment_total + self.amount if existing_deposit_line: # Update existing deposit line existing_deposit_line.write({ 'product_qty': 1, 'price_unit': -new_advance_total, 'taxes_id': [(6, 0, [])], }) else: # Create new deposit line deposit_vals = { 'order_id': self.purchase_order_id.id, 'product_id': deposit_product.id, 'name': f'Advance payment for {self.purchase_order_id.name}', 'product_qty': 1, 'product_uom': deposit_product.uom_id.id, 'price_unit': -self.amount, 'is_deposit': True, 'date_planned': fields.Datetime.now(), 'taxes_id': [(6, 0, [])], } self.env['purchase.order.line'].create(deposit_vals)