2
# -*- coding: utf-8 -*-
3
##############################################################################
5
# OpenERP, Open Source Management Solution
6
# Copyright (C) 2011 TeMPO Consulting, MSF. All Rights Reserved
7
# Developer: Olivier DOSSMANN
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU Affero General Public License as
11
# published by the Free Software Foundation, either version 3 of the
12
# License, or (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU Affero General Public License for more details.
19
# You should have received a copy of the GNU Affero General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
##############################################################################
25
from osv import fields
26
from tools.translate import _
27
from collections import defaultdict
29
class account_invoice_line(osv.osv):
30
_name = 'account.invoice.line'
31
_inherit = 'account.invoice.line'
34
'sale_order_lines': fields.many2many('sale.order.line', 'sale_order_line_invoice_rel', 'invoice_id', 'order_line_id', 'Sale Order Lines', readonly=True),
35
'sale_order_line_id': fields.many2one('sale.order.line', string="Sale Order Line", readonly=True,
36
help="Sale Order Line from which this line have been generated (when coming from a sale order)"),
39
account_invoice_line()
41
class account_invoice(osv.osv):
42
_name = 'account.invoice'
43
_inherit = 'account.invoice'
46
'order_ids': fields.many2many('sale.order', 'sale_order_invoice_rel', 'invoice_id', 'order_id', 'Sale Order',
47
help="Sale Order from which invoice have been generated"),
50
def fetch_analytic_distribution(self, cr, uid, ids, context=None):
52
Recover distribution from purchase order. If a commitment is attached to purchase order, then retrieve analytic distribution from commitment voucher.
53
NB: This method only works because there is a link between purchase and invoice.
58
if isinstance(ids, (int, long)):
61
invl_obj = self.pool.get('account.invoice.line')
62
ana_obj = self.pool.get('analytic.distribution')
64
for inv in self.browse(cr, uid, ids, context=context):
65
# Set analytic distribution from purchase order to invoice
66
for po in inv.purchase_ids:
67
# First set invoice global distribution
68
if not inv.analytic_distribution_id and po.analytic_distribution_id:
69
# Fetch PO analytic distribution
70
distrib_id = po.analytic_distribution_id and po.analytic_distribution_id.id or False
71
# If commitment for this PO, fetch analytic distribution. Else take default distrib_id
73
distrib_id = po.commitment_ids[0].analytic_distribution_id and po.commitment_ids[0].analytic_distribution_id.id or distrib_id
75
new_distrib_id = ana_obj.copy(cr, uid, distrib_id, {})
76
if not new_distrib_id:
77
raise osv.except_osv(_('Error'), _('An error occured for analytic distribution copy for invoice.'))
78
# create default funding pool lines
79
ana_obj.create_funding_pool_lines(cr, uid, [new_distrib_id])
80
self.pool.get('account.invoice').write(cr, uid, [inv.id], {'analytic_distribution_id': new_distrib_id,})
81
for so in inv.order_ids:
82
# Create analytic distribution on invoices regarding FO
83
if so.analytic_distribution_id:
84
distrib_id = so.analytic_distribution_id and so.analytic_distribution_id.id or False
86
new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, distrib_id, {})
87
if not new_distrib_id:
88
raise osv.except_osv(_('Error'), _('An error occured for analytic distribution copy for invoice.'))
89
# create default funding pool lines
90
self.pool.get('analytic.distribution').create_funding_pool_lines(cr, uid, [new_distrib_id])
91
self.pool.get('account.invoice').write(cr, uid, [inv.id], {'analytic_distribution_id': new_distrib_id,})
92
# Then set distribution on invoice line regarding purchase order line distribution
93
for invl in inv.invoice_line:
94
if invl.order_line_id:
95
# Fetch PO line analytic distribution or nothing (that implies it take those from PO)
96
distrib_id = invl.order_line_id.analytic_distribution_id and invl.order_line_id.analytic_distribution_id.id or False
97
# Attempt to fetch commitment line analytic distribution or commitment voucher analytic distribution or default distrib_id
98
if invl.order_line_id.commitment_line_ids:
99
distrib_id = invl.order_line_id.commitment_line_ids[0].analytic_distribution_id \
100
and invl.order_line_id.commitment_line_ids[0].analytic_distribution_id.id or distrib_id
102
new_invl_distrib_id = ana_obj.copy(cr, uid, distrib_id, {})
103
if not new_invl_distrib_id:
104
raise osv.except_osv(_('Error'), _('An error occured for analytic distribution copy for invoice.'))
105
# create default funding pool lines
106
ana_obj.create_funding_pool_lines(cr, uid, [new_invl_distrib_id], invl.account_id.id)
107
invl_obj.write(cr, uid, [invl.id], {'analytic_distribution_id': new_invl_distrib_id})
108
# Fetch SO line analytic distribution
109
if invl.sale_order_line_id:
110
distrib_id = invl.sale_order_line_id.analytic_distribution_id and invl.sale_order_line_id.analytic_distribution_id.id or False
112
new_invl_distrib_id = ana_obj.copy(cr, uid, distrib_id, {})
113
if not new_invl_distrib_id:
114
raise osv.except_osv(_('Error'), _('An error occured for analytic distribution copy for invoice.'))
115
# create default funding pool lines
116
ana_obj.create_funding_pool_lines(cr, uid, [new_invl_distrib_id], invl.account_id.id)
117
invl_obj.write(cr, uid, [invl.id], {'analytic_distribution_id': new_invl_distrib_id})
120
def update_commitments(self, cr, uid, ids, context=None):
122
Update engagement lines for given invoice.
123
NB: We use COMMITMENT VOUCHER ANALYTIC DISTRIBUTION for updating!
128
if isinstance(ids, (int, long)):
130
# Browse given invoices
131
for inv in self.browse(cr, uid, ids, context=context):
132
# Prepare some values
133
co_ids = self.pool.get('account.commitment').search(cr, uid, [('purchase_id', 'in', [x.id for x in inv.purchase_ids])], context=context)
137
raise osv.except_osv(_('Error'), _('Multiple Commitment Voucher for the same invoice is not supported yet!'))
138
co = self.pool.get('account.commitment').browse(cr, uid, co_ids, context=context)[0]
139
# If Commitment voucher in draft state we change it to 'validated' without using workflow and engagement lines generation
140
# NB: This permits to avoid modification on commitment voucher when receiving some goods
141
if co.state == 'draft':
142
self.pool.get('account.commitment').write(cr, uid, [co.id], {'state': 'open'}, context=context)
143
# Try to update engagement lines regarding invoice line amounts and account
144
invoice_lines = defaultdict(list)
145
# Group by account (those from purchase order line)
146
for invl in inv.invoice_line:
147
# Do not take invoice line that have no order_line_id (so that are not linked to a purchase order line)
148
if not invl.order_line_id:
150
# Fetch purchase order line account
151
pol = invl.order_line_id
153
a = pol.product_id.product_tmpl_id.property_account_expense.id
155
a = pol.product_id.categ_id.property_account_expense_categ.id
157
raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') % (pol.product_id.name, pol.product_id.id,))
159
a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category').id
160
invoice_lines[a].append(invl)
163
processed_commitment_line = []
164
for account_id in invoice_lines:
166
# compute total amount of all invoice lines that have the same account_id
167
for line in invoice_lines[account_id]:
168
total_amount += line.price_subtotal
169
# search for matching commitment line
170
cl_ids = self.pool.get('account.commitment.line').search(cr, uid, [('commit_id', '=', co.id), ('account_id', '=', account_id)], limit=1,
172
# Do nothing if no commitment line exists for this invoice line. FIXME: waiting for a decision about this case
175
cl = self.pool.get('account.commitment.line').browse(cr, uid, cl_ids, context=context)[0]
176
# if no difference between invoice lines and commitment line: delete engagement lines that come from this commitment_line
177
eng_ids = self.pool.get('account.analytic.line').search(cr, uid, [('commitment_line_id', '=', cl.id)], context=context)
178
if cl.amount == total_amount:
179
processed_commitment_line.append(cl.id)
181
self.pool.get('account.analytic.line').unlink(cr, uid, eng_ids, context=context)
182
self.pool.get('account.commitment.line').write(cr, uid, [cl.id], {'amount': 0.0}, context=context)
184
# Remember difference in diff_lines list
185
diff_lines.append({'cl': cl, 'diff': cl.amount - total_amount, 'new_mnt': total_amount})
186
# Difference lines process
188
for diff_line in diff_lines:
189
# Prepare some values
190
cl = diff_line.get('cl', False)
191
diff = diff_line.get('diff', 0.0)
192
new_mnt = diff_line.get('new_mnt', 0.0)
193
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
195
raise osv.except_osv(_('Error'), _('No commitment line found. Please contact an administrator to resolve this problem.'))
196
distrib_id = cl.analytic_distribution_id and cl.analytic_distribution_id.id or cl.commit_id and cl.commit_id.analytic_distribution_id \
197
and cl.commit_id.analytic_distribution_id.id or False
199
raise osv.except_osv(_('Error'), _('No analytic distribution found.'))
200
# Browse distribution
201
distrib = self.pool.get('analytic.distribution').browse(cr, uid, [distrib_id], context=context)[0]
202
engagement_lines = distrib.analytic_lines
203
for distrib_lines in [distrib.cost_center_lines, distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]:
204
for distrib_line in distrib_lines:
206
'account_id': distrib_line.analytic_id.id,
207
'general_account_id': cl.account_id.id,
209
if distrib_line._name == 'funding.pool.distribution.line':
210
vals.update({'cost_center_id': distrib_line.cost_center_id and distrib_line.cost_center_id.id or False,})
211
# Browse engagement lines to found out matching elements
212
for i in range(0,len(engagement_lines)):
213
if engagement_lines[i]:
214
eng_line = engagement_lines[i]
216
'account_id': eng_line.account_id.id,
217
'general_account_id': eng_line.general_account_id.id,
219
if eng_line.cost_center_id:
220
cmp_vals.update({'cost_center_id': eng_line.cost_center_id.id})
222
# Update analytic line with new amount
223
anal_amount = (distrib_line.percentage * diff) / 100
224
amount = -1 * self.pool.get('res.currency').compute(cr, uid, inv.currency_id.id, company_currency,
225
anal_amount, round=False, context=context)
226
# write new amount to corresponding engagement line
227
self.pool.get('account.analytic.line').write(cr, uid, [eng_line.id],
228
{'amount': amount, 'amount_currency': -1 * anal_amount}, context=context)
229
# delete processed engagement lines
230
engagement_lines[i] = None
231
# update existent commitment line with new amount (new_mnt)
232
commitment_line_new_amount = cl.amount - new_mnt
233
if commitment_line_new_amount < 0.0:
234
commitment_line_new_amount = 0.0
235
self.pool.get('account.commitment.line').write(cr, uid, [cl.id], {'amount': commitment_line_new_amount}, context=context)
236
# add cl to processed_commitment_line
237
processed_commitment_line.append(cl.id)
238
# Update commitment voucher state (if total_amount is inferior to 0.0, then state is done)
239
c_total = self.pool.get('account.commitment')._get_total(cr, uid, [co.id], {}, {}, context=context)
240
if c_total and c_total.get(co.id, 1.0) <= 0.0:
241
self.pool.get('account.commitment').action_commitment_done(cr, uid, [co.id], context=context)
244
def action_open_invoice(self, cr, uid, ids, context=None):
246
Launch engagement lines updating if a commitment is attached to PO that generate this invoice.
251
if isinstance(ids, (int, long)):
253
# Prepare some values
255
# Verify if all invoice have a po that have a commitment
256
for inv in self.browse(cr, uid, ids, context=context):
257
for po in inv.purchase_ids:
258
if po.commitment_ids:
259
to_process.append(inv.id)
260
# UTP-536 : Check if the PO is closed and all SI are draft, then close the CV
261
if po.state == 'done' and all(x.id in ids or x.state != 'draft' for x in po.invoice_ids):
262
self.pool.get('purchase.order')._finish_commitment(cr, uid, [po.id], context=context)
265
self.update_commitments(cr, uid, to_process, context=context)
266
return super(account_invoice, self).action_open_invoice(cr, uid, ids, context=context)
269
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: