1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class SaleAdvancePaymentInv(models.TransientModel):
_name = "sale.advance.payment.inv"
_description = "Sales Advance Payment Invoice"
@api.model
def _count(self):
return len(self._context.get('active_ids', []))
@api.model
def _default_product_id(self):
product_id = self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id')
return self.env['product.product'].browse(int(product_id)).exists()
@api.model
def _default_deposit_account_id(self):
return self._default_product_id()._get_product_accounts()['income']
@api.model
def _default_deposit_taxes_id(self):
return self._default_product_id().taxes_id
@api.model
def _default_has_down_payment(self):
if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False):
sale_order = self.env['sale.order'].browse(self._context.get('active_id'))
return sale_order.order_line.filtered(
lambda sale_order_line: sale_order_line.is_downpayment
)
return False
@api.model
def _default_currency_id(self):
if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False):
sale_order = self.env['sale.order'].browse(self._context.get('active_id'))
return sale_order.currency_id
advance_payment_method = fields.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).")
deduct_down_payments = fields.Boolean('Deduct down payments', default=True)
has_down_payments = fields.Boolean('Has down payments', default=_default_has_down_payment, readonly=True)
product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')],
default=_default_product_id)
count = fields.Integer(default=_count, string='Order Count')
amount = fields.Float('Down Payment Amount', digits='Account', help="The percentage of amount to be invoiced in advance, taxes excluded.")
currency_id = fields.Many2one('res.currency', string='Currency', default=_default_currency_id)
fixed_amount = fields.Monetary('Down Payment Amount (Fixed)', help="The fixed amount to be invoiced in advance, taxes excluded.")
deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)],
help="Account used for deposits", default=_default_deposit_account_id)
deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id)
@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}}
return {}
def _prepare_invoice_values(self, order, name, amount, so_line):
invoice_vals = {
'ref': order.client_order_ref,
'move_type': 'out_invoice',
'invoice_origin': order.name,
'invoice_user_id': order.user_id.id,
'narration': order.note,
'partner_id': order.partner_invoice_id.id,
'fiscal_position_id': (order.fiscal_position_id or order.fiscal_position_id.get_fiscal_position(order.partner_id.id)).id,
'partner_shipping_id': order.partner_shipping_id.id,
'currency_id': order.pricelist_id.currency_id.id,
'payment_reference': order.reference,
'invoice_payment_term_id': order.payment_term_id.id,
'partner_bank_id': order.company_id.partner_id.bank_ids[:1].id,
'team_id': order.team_id.id,
'campaign_id': order.campaign_id.id,
'medium_id': order.medium_id.id,
'source_id': order.source_id.id,
'invoice_line_ids': [(0, 0, {
'name': name,
'price_unit': amount,
'quantity': 1.0,
'product_id': self.product_id.id,
'product_uom_id': so_line.product_uom.id,
'tax_ids': [(6, 0, so_line.tax_id.ids)],
'sale_line_ids': [(6, 0, [so_line.id])],
'analytic_tag_ids': [(6, 0, so_line.analytic_tag_ids.ids)],
'analytic_account_id': order.analytic_account_id.id or False,
})],
}
return invoice_vals
def _get_advance_details(self, order):
context = {'lang': order.partner_id.lang}
if self.advance_payment_method == 'percentage':
if all(self.product_id.taxes_id.mapped('price_include')):
amount = order.amount_total * self.amount / 100
else:
amount = order.amount_untaxed * self.amount / 100
name = _("Down payment of %s%%") % (self.amount)
else:
amount = self.fixed_amount
name = _('Down Payment')
del context
return amount, name
def _create_invoice(self, order, so_line, amount):
if (self.advance_payment_method == 'percentage' and self.amount <= 0.00) or (self.advance_payment_method == 'fixed' and self.fixed_amount <= 0.00):
raise UserError(_('The value of the down payment amount must be positive.'))
amount, name = self._get_advance_details(order)
invoice_vals = self._prepare_invoice_values(order, name, amount, so_line)
if order.fiscal_position_id:
invoice_vals['fiscal_position_id'] = order.fiscal_position_id.id
invoice = self.env['account.move'].sudo().create(invoice_vals).with_user(self.env.uid)
invoice.message_post_with_view('mail.message_origin_link',
values={'self': invoice, 'origin': order},
subtype_id=self.env.ref('mail.mt_note').id)
return invoice
def _prepare_so_line(self, order, analytic_tag_ids, tax_ids, amount):
context = {'lang': order.partner_id.lang}
so_values = {
'name': _('Down Payment: %s') % (time.strftime('%m %Y'),),
'price_unit': amount,
'product_uom_qty': 0.0,
'order_id': order.id,
'discount': 0.0,
'product_uom': self.product_id.uom_id.id,
'product_id': self.product_id.id,
'analytic_tag_ids': analytic_tag_ids,
'tax_id': [(6, 0, tax_ids)],
'is_downpayment': True,
'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10,
}
del context
return so_values
def create_invoices(self):
sale_orders = self.env['sale.order'].browse(self._context.get('active_ids', []))
if self.advance_payment_method == 'delivered':
sale_orders._create_invoices(final=self.deduct_down_payments)
else:
# Create deposit product if necessary
if not self.product_id:
vals = self._prepare_deposit_product()
self.product_id = self.env['product.product'].create(vals)
self.env['ir.config_parameter'].sudo().set_param('sale.default_deposit_product_id', self.product_id.id)
sale_line_obj = self.env['sale.order.line']
for order in sale_orders:
amount, name = self._get_advance_details(order)
if self.product_id.invoice_policy != 'order':
raise UserError(_('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.'))
if self.product_id.type != 'service':
raise UserError(_("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product."))
taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids
analytic_tag_ids = []
for line in order.order_line:
analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids]
so_line_values = self._prepare_so_line(order, analytic_tag_ids, tax_ids, amount)
so_line = sale_line_obj.create(so_line_values)
self._create_invoice(order, so_line, amount)
if self._context.get('open_invoices', False):
return sale_orders.action_view_invoice()
return {'type': 'ir.actions.act_window_close'}
def _prepare_deposit_product(self):
return {
'name': 'Down payment',
'type': 'service',
'invoice_policy': 'order',
'property_account_income_id': self.deposit_account_id.id,
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
'company_id': False,
}
|