forked from OCA/e-commerce
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sale.py
246 lines (220 loc) · 9.33 KB
/
sale.py
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Guewen Baconnier, Sébastien Beau
# Copyright (C) 2011 Akretion Sébastien BEAU <[email protected]>
# Copyright 2013 Camptocamp SA (Guewen Baconnier)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
from openerp import api, models, fields, exceptions, _
import openerp.addons.decimal_precision as dp
_logger = logging.getLogger(__name__)
class SaleOrder(models.Model):
_inherit = 'sale.order'
@api.one
@api.depends('amount_total', 'payment_ids.credit', 'payment_ids.debit')
def _compute_amount(self):
paid_amount = sum(line.credit - line.debit
for line in self.payment_ids)
self.amount_paid = paid_amount
self.residual = self.amount_total - paid_amount
payment_ids = fields.Many2many(
comodel_name='account.move.line',
string='Payments Entries',
domain=[('account_id.type', '=', 'receivable')],
copy=False,
)
payment_method_id = fields.Many2one(
comodel_name='payment.method',
string='Payment Method',
ondelete='restrict',
)
residual = fields.Float(
compute='_compute_amount',
digits_compute=dp.get_precision('Account'),
string='Balance',
store=False,
)
amount_paid = fields.Float(
compute='_compute_amount',
digits_compute=dp.get_precision('Account'),
string='Amount Paid',
store=False,
)
@api.multi
def action_cancel(self):
for sale in self:
if sale.payment_ids:
raise exceptions.Warning(_('Cannot cancel this sales order '
'because automatic payment entries '
'are linked with it.'))
return super(SaleOrder, self).action_cancel()
@api.multi
def automatic_payment(self, amount=None):
""" Create the payment entries to pay a sale order, respecting
the payment terms.
If no amount is defined, it will pay the residual amount of the sale
order.
"""
self.ensure_one()
method = self.payment_method_id
if not method:
raise exceptions.Warning(
_("An automatic payment can not be created for the sale "
"order %s because it has no payment method.") % self.name
)
if not method.journal_id:
raise exceptions.Warning(
_("An automatic payment should be created for the sale order"
" %s but the payment method '%s' has no journal defined.") %
(self.name, method.name)
)
journal = method.journal_id
date = self.date_order[:10]
if amount is None:
amount = self.residual
if self.payment_term:
amounts = self.payment_term.compute(amount, date_ref=date)[0]
else:
amounts = [(date, amount)]
# reversed is cosmetic, compute returns terms in the 'wrong' order
for date, amount in reversed(amounts):
self._add_payment(journal, amount, date)
return True
@api.multi
def add_payment(self, journal_id, amount, date=None, description=None):
""" Generate payment move lines of a certain amount linked
with the sale order.
"""
self.ensure_one()
journal_model = self.env['account.journal']
if date is None:
date = self.date_order
journal = journal_model.browse(journal_id)
self._add_payment(journal, amount, date, description)
return True
@api.multi
def _add_payment(self, journal, amount, date, description=None):
""" Generate move lines entries to pay the sale order. """
move_model = self.env['account.move']
period_model = self.env['account.period']
period = period_model.find(dt=date)
move_name = description or self._get_payment_move_name(journal, period)
move_vals = self._prepare_payment_move(move_name, journal,
period, date)
move_lines = self._prepare_payment_move_lines(move_name, journal,
period, amount, date)
move_vals['line_id'] = [(0, 0, line) for line in move_lines]
move_model.create(move_vals)
@api.model
def _get_payment_move_name(self, journal, period):
sequence = journal.sequence_id
if not sequence:
raise exceptions.Warning(_('Please define a sequence on the '
'journal %s.') % journal.name)
if not sequence.active:
raise exceptions.Warning(_('Please activate the sequence of the '
'journal %s.') % journal.name)
sequence = sequence.with_context(fiscalyear_id=period.fiscalyear_id.id)
# next_by_id not compatible with new api
sequence_model = self.pool['ir.sequence']
name = sequence_model.next_by_id(self.env.cr, self.env.uid,
sequence.id,
context=self.env.context)
return name
@api.multi
def _prepare_payment_move(self, move_name, journal, period, date):
return {'name': move_name,
'journal_id': journal.id,
'date': date,
'ref': self.name,
'period_id': period.id,
}
@api.multi
def _prepare_payment_move_line(self, move_name, journal, period,
amount, date):
# to remove in v9
_logger.warning('Deprecated: _prepare_payment_move_line has been '
'deprecated in favor of _prepare_payment_move_lines')
return self._prepare_payment_move_lines(move_name, journal, period,
amount, date)
@api.multi
def _prepare_payment_move_lines(self, move_name, journal, period,
amount, date):
partner = self.partner_id.commercial_partner_id
company = journal.company_id
currency = self.env['res.currency'].browse()
# if the lines are not in a different currency,
# the amount_currency stays at 0.0
amount_currency = 0.0
if journal.currency and journal.currency != company.currency_id:
# when the journal have a currency, we have to convert
# the amount to the currency of the company and set
# the journal's currency on the lines
currency = journal.currency
company_amount = currency.compute(amount, company.currency_id)
amount_currency, amount = amount, company_amount
# payment line (bank / cash)
debit_line = {
'name': move_name,
'debit': amount,
'credit': 0.0,
'account_id': journal.default_credit_account_id.id,
'journal_id': journal.id,
'period_id': period.id,
'partner_id': partner.id,
'date': date,
'amount_currency': amount_currency,
'currency_id': currency.id,
}
# payment line (receivable)
credit_line = {
'name': move_name,
'debit': 0.0,
'credit': amount,
'account_id': partner.property_account_receivable.id,
'journal_id': journal.id,
'period_id': period.id,
'partner_id': partner.id,
'date': date,
'amount_currency': -amount_currency,
'currency_id': currency.id,
'sale_ids': [(4, self.id)],
}
return debit_line, credit_line
@api.onchange('payment_method_id')
def onchange_payment_method_id_set_payment_term(self):
if not self.payment_method_id:
return
method = self.payment_method_id
if method.payment_term_id:
self.payment_term = method.payment_term_id.id
@api.multi
def action_view_payments(self):
""" Return an action to display the payment linked
with the sale order """
self.ensure_one()
moves = self.mapped('payment_ids.move_id')
xmlid = ('account', 'action_move_journal_line')
action = self.env['ir.actions.act_window'].for_xml_id(*xmlid)
if len(moves) > 1:
action['domain'] = [('id', 'in', moves.ids)]
else:
ref = self.env.ref('account.view_move_form')
action['views'] = [(ref.id, 'form')]
action['res_id'] = moves.id if moves else False
return action