258 lines
10 KiB
Python
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)
|