mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 08:32:05 +02:00
246 lines
10 KiB
Python
246 lines
10 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import SUPERUSER_ID, _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.fields import Command
|
|
from odoo.tools import formatLang
|
|
|
|
|
|
class SaleAdvancePaymentInv(models.TransientModel):
|
|
_name = 'sale.advance.payment.inv'
|
|
_description = "Sales Advance Payment Invoice"
|
|
|
|
advance_payment_method = fields.Selection(
|
|
selection=[
|
|
('delivered', "Regular invoice"),
|
|
('percentage', "Down payment (percentage)"),
|
|
('fixed', "Down payment (fixed amount)"),
|
|
],
|
|
string="Create Invoice",
|
|
default='delivered',
|
|
required=True,
|
|
help="A standard invoice is issued with all the order lines ready for invoicing,"
|
|
"according to their invoicing policy (based on ordered or delivered quantity).")
|
|
count = fields.Integer(string="Order Count", compute='_compute_count')
|
|
sale_order_ids = fields.Many2many(
|
|
'sale.order', default=lambda self: self.env.context.get('active_ids'))
|
|
|
|
# Down Payment logic
|
|
has_down_payments = fields.Boolean(
|
|
string="Has down payments", compute="_compute_has_down_payments")
|
|
deduct_down_payments = fields.Boolean(string="Deduct down payments", default=True)
|
|
|
|
# New Down Payment
|
|
amount = fields.Float(
|
|
string="Down Payment",
|
|
help="The percentage of amount to be invoiced in advance.")
|
|
fixed_amount = fields.Monetary(
|
|
string="Down Payment Amount (Fixed)",
|
|
help="The fixed amount to be invoiced in advance.")
|
|
currency_id = fields.Many2one(
|
|
comodel_name='res.currency',
|
|
compute='_compute_currency_id',
|
|
store=True)
|
|
company_id = fields.Many2one(
|
|
comodel_name='res.company',
|
|
compute='_compute_company_id',
|
|
store=True)
|
|
amount_invoiced = fields.Monetary(
|
|
string="Already invoiced",
|
|
compute="_compute_invoice_amounts",
|
|
help="Only confirmed down payments are considered.")
|
|
|
|
# UI
|
|
display_draft_invoice_warning = fields.Boolean(compute="_compute_display_draft_invoice_warning")
|
|
consolidated_billing = fields.Boolean(
|
|
string="Consolidated Billing", default=True,
|
|
help="Create one invoice for all orders related to same customer, same invoicing address"
|
|
" and same delivery address."
|
|
)
|
|
|
|
#=== COMPUTE METHODS ===#
|
|
|
|
@api.depends('sale_order_ids')
|
|
def _compute_count(self):
|
|
for wizard in self:
|
|
wizard.count = len(wizard.sale_order_ids)
|
|
|
|
@api.depends('sale_order_ids')
|
|
def _compute_has_down_payments(self):
|
|
for wizard in self:
|
|
wizard.has_down_payments = bool(
|
|
wizard.sale_order_ids.order_line.filtered('is_downpayment')
|
|
)
|
|
|
|
# next computed fields are only used for down payments invoices and therefore should only
|
|
# have a value when 1 unique SO is invoiced through the wizard
|
|
@api.depends('sale_order_ids')
|
|
def _compute_currency_id(self):
|
|
self.currency_id = False
|
|
for wizard in self:
|
|
if wizard.count == 1:
|
|
wizard.currency_id = wizard.sale_order_ids.currency_id
|
|
|
|
@api.depends('sale_order_ids')
|
|
def _compute_company_id(self):
|
|
self.company_id = False
|
|
for wizard in self:
|
|
if wizard.count == 1:
|
|
wizard.company_id = wizard.sale_order_ids.company_id
|
|
|
|
@api.depends('sale_order_ids')
|
|
def _compute_display_draft_invoice_warning(self):
|
|
for wizard in self:
|
|
invoice_states = wizard.sale_order_ids._origin.sudo().invoice_ids.mapped('state')
|
|
wizard.display_draft_invoice_warning = 'draft' in invoice_states
|
|
|
|
@api.depends('sale_order_ids')
|
|
def _compute_invoice_amounts(self):
|
|
for wizard in self:
|
|
wizard.amount_invoiced = sum(wizard.sale_order_ids._origin.mapped('amount_invoiced'))
|
|
|
|
#=== ONCHANGE METHODS ===#
|
|
|
|
@api.onchange('advance_payment_method')
|
|
def _onchange_advance_payment_method(self):
|
|
if self.advance_payment_method == 'percentage':
|
|
amount = self.default_get(['amount']).get('amount')
|
|
return {'value': {'amount': amount}}
|
|
|
|
#=== CONSTRAINT METHODS ===#
|
|
|
|
def _check_amount_is_positive(self):
|
|
for wizard in self:
|
|
if wizard.advance_payment_method == 'percentage' and wizard.amount <= 0.00:
|
|
raise UserError(_('The value of the down payment amount must be positive.'))
|
|
elif wizard.advance_payment_method == 'fixed' and wizard.fixed_amount <= 0.00:
|
|
raise UserError(_('The value of the down payment amount must be positive.'))
|
|
|
|
#=== ACTION METHODS ===#
|
|
|
|
def create_invoices(self):
|
|
self._check_amount_is_positive()
|
|
invoices = self._create_invoices(self.sale_order_ids)
|
|
return self.sale_order_ids.action_view_invoice(invoices=invoices)
|
|
|
|
def view_draft_invoices(self):
|
|
return {
|
|
'name': _('Draft Invoices'),
|
|
'type': 'ir.actions.act_window',
|
|
'view_mode': 'list',
|
|
'views': [(False, 'list'), (False, 'form')],
|
|
'res_model': 'account.move',
|
|
'domain': [('line_ids.sale_line_ids.order_id', 'in', self.sale_order_ids.ids), ('state', '=', 'draft')],
|
|
}
|
|
|
|
#=== BUSINESS METHODS ===#
|
|
|
|
def _create_invoices(self, sale_orders):
|
|
self.ensure_one()
|
|
if self.advance_payment_method == 'delivered':
|
|
return sale_orders._create_invoices(final=self.deduct_down_payments, grouped=not self.consolidated_billing)
|
|
else:
|
|
self.sale_order_ids.ensure_one()
|
|
self = self.with_company(self.company_id)
|
|
order = self.sale_order_ids
|
|
|
|
AccountTax = self.env['account.tax']
|
|
order_lines = order.order_line.filtered(lambda x: not x.display_type)
|
|
base_lines = [line._prepare_base_line_for_taxes_computation() for line in order_lines]
|
|
AccountTax._add_tax_details_in_base_lines(base_lines, order.company_id)
|
|
AccountTax._round_base_lines_tax_details(base_lines, order.company_id)
|
|
|
|
if self.advance_payment_method == 'percentage':
|
|
amount_type = 'percent'
|
|
amount = self.amount
|
|
else: # self.advance_payment_method == 'fixed':
|
|
amount_type = 'fixed'
|
|
amount = self.fixed_amount
|
|
|
|
down_payment_base_lines = AccountTax._prepare_down_payment_lines(
|
|
base_lines=base_lines,
|
|
company=self.company_id,
|
|
amount_type=amount_type,
|
|
amount=amount,
|
|
computation_key=f'down_payment,{self.id}',
|
|
)
|
|
|
|
# Update the sale order.
|
|
order._create_down_payment_section_line_if_needed()
|
|
so_lines = order._create_down_payment_lines_from_base_lines(down_payment_base_lines)
|
|
|
|
# Create the invoice.
|
|
invoice_values = self.with_context(accounts=[
|
|
base_line['account_id'] or self._get_down_payment_account(base_line['product_id'])
|
|
for base_line in down_payment_base_lines
|
|
])._prepare_down_payment_invoice_values(
|
|
order=order,
|
|
so_lines=so_lines,
|
|
)
|
|
invoice_sudo = self.env['account.move'].sudo().create(invoice_values)
|
|
|
|
# Unsudo the invoice after creation if not already sudoed
|
|
invoice = invoice_sudo.sudo(self.env.su)
|
|
poster = self.env.user._is_internal() and self.env.user.id or SUPERUSER_ID
|
|
invoice.with_user(poster).message_post_with_source(
|
|
'mail.message_origin_link',
|
|
render_values={'self': invoice, 'origin': order},
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|
|
|
|
title = _("Down payment invoice")
|
|
order.with_user(poster).message_post(
|
|
body=_("%s has been created", invoice._get_html_link(title=title)),
|
|
)
|
|
|
|
return invoice
|
|
|
|
# TODO: add accounts to method params in master
|
|
def _prepare_down_payment_invoice_values(self, order, so_lines):
|
|
""" Prepare the values to create a down payment invoice.
|
|
|
|
:param order: The current sale order.
|
|
:param so_lines: The "fake" down payment SO lines created on the sale order.
|
|
:return: The values to create a new invoice.
|
|
"""
|
|
self.ensure_one()
|
|
accounts = self.env.context.get('accounts')
|
|
return {
|
|
**order._prepare_invoice(),
|
|
'invoice_line_ids': [
|
|
Command.create(self._prepare_down_payment_invoice_line_values(order, so_line, self.company_id.downpayment_account_id or account))
|
|
for so_line, account in zip(so_lines, accounts)
|
|
],
|
|
}
|
|
|
|
def _prepare_down_payment_invoice_line_values(self, order, so_line, account):
|
|
""" Prepare the invoice line values to be part of a down payment invoice.
|
|
|
|
:param order: The current sale order.
|
|
:param so_line: The "fake" down payment SO line created on the sale order.
|
|
:param account: The down payment account to use.
|
|
:return: The values to create a new invoice line.
|
|
"""
|
|
self.ensure_one()
|
|
self = self.with_context(lang=order._get_lang())
|
|
|
|
if self.advance_payment_method == 'percentage':
|
|
name = self.env._("Down payment of %s%%", formatLang(self.env, self.amount))
|
|
else:
|
|
name = self.env._("Down Payment")
|
|
|
|
return so_line._prepare_invoice_line(
|
|
name=name,
|
|
quantity=1.0,
|
|
**({'account_id': account.id} if account else {}),
|
|
)
|
|
|
|
def _get_down_payment_account(self, product):
|
|
""" Retrieve the down payment account to use.
|
|
:param product: A product.
|
|
:return: An accounting account or None if not found.
|
|
"""
|
|
product_account = product.product_tmpl_id.get_product_accounts(
|
|
fiscal_pos=self.sale_order_ids.fiscal_position_id
|
|
)
|
|
return product_account.get('downpayment') or product_account.get('income')
|