purchase_advance_payment/wizard/create_advance_payment_wizard.py

258 lines
10 KiB
Python

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)