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
'order_line_id': fields.many2one('purchase.order.line', string="Purchase Order Line", readonly=True,
35
help="Purchase Order Line from which this invoice line has been generated (when coming from a purchase order)."),
38
account_invoice_line()
40
class account_invoice(osv.osv):
41
_name = 'account.invoice'
42
_inherit = 'account.invoice'
45
'purchase_ids': fields.many2many('purchase.order', 'purchase_invoice_rel', 'invoice_id', 'purchase_id', 'Purchase Order',
46
help="Purchase Order from which invoice have been generated"),
49
def fetch_analytic_distribution(self, cr, uid, ids, context={}):
51
Recover distribution from purchase order. If a commitment is attached to purchase order, then retrieve analytic distribution from commitment voucher.
52
NB: This method only works because there is a link between purchase and invoice.
57
if isinstance(ids, (int, long)):
60
invl_obj = self.pool.get('account.invoice.line')
61
ana_obj = self.pool.get('analytic.distribution')
63
for inv in self.browse(cr, uid, ids, context=context):
64
# Set analytic distribution from purchase order to invoice
65
for po in inv.purchase_ids:
66
# First set invoice global distribution
67
if not inv.analytic_distribution_id and po.analytic_distribution_id:
68
# Fetch PO analytic distribution
69
distrib_id = po.analytic_distribution_id and po.analytic_distribution_id.id or False
70
# If commitment for this PO, fetch analytic distribution. Else take default distrib_id
72
distrib_id = po.commitment_ids[0].analytic_distribution_id and po.commitment_ids[0].analytic_distribution_id.id or distrib_id
74
new_distrib_id = ana_obj.copy(cr, uid, distrib_id, {})
75
if not new_distrib_id:
76
raise osv.except_osv(_('Error'), _('An error occured for analytic distribution copy for invoice.'))
77
# create default funding pool lines
78
ana_obj.create_funding_pool_lines(cr, uid, [new_distrib_id])
79
self.pool.get('account.invoice').write(cr, uid, [inv.id], {'analytic_distribution_id': new_distrib_id,})
80
# Then set distribution on invoice line regarding purchase order line distribution
81
for invl in inv.invoice_line:
82
if invl.order_line_id:
83
# Fetch PO line analytic distribution or nothing (that implies it take those from PO)
84
distrib_id = invl.order_line_id.analytic_distribution_id and invl.order_line_id.analytic_distribution_id.id or False
85
# Attempt to fetch commitment line analytic distribution or commitment voucher analytic distribution or default distrib_id
86
if invl.order_line_id.commitment_line_ids:
87
distrib_id = invl.order_line_id.commitment_line_ids[0].analytic_distribution_id \
88
and invl.order_line_id.commitment_line_ids[0].analytic_distribution_id.id or distrib_id
90
new_invl_distrib_id = ana_obj.copy(cr, uid, distrib_id, {})
91
if not new_invl_distrib_id:
92
raise osv.except_osv(_('Error'), _('An error occured for analytic distribution copy for invoice.'))
93
# create default funding pool lines
94
ana_obj.create_funding_pool_lines(cr, uid, [new_invl_distrib_id])
95
invl_obj.write(cr, uid, [invl.id], {'analytic_distribution_id': new_invl_distrib_id})
98
def update_commitments(self, cr, uid, ids, context={}):
100
Update engagement lines for given invoice.
101
NB: We use COMMITMENT VOUCHER ANALYTIC DISTRIBUTION for updating!
106
if isinstance(ids, (int, long)):
108
# Browse given invoices
109
for inv in self.browse(cr, uid, ids, context=context):
110
# Prepare some values
111
co_ids = self.pool.get('account.commitment').search(cr, uid, [('purchase_id', 'in', [x.id for x in inv.purchase_ids])], context=context)
115
raise osv.except_osv(_('Error'), _('Multiple Commitment Voucher for the same invoice is not supported yet!'))
116
co = self.pool.get('account.commitment').browse(cr, uid, co_ids, context=context)[0]
117
# If Commitment voucher in draft state we change it to 'validated' without using workflow and engagement lines generation
118
# NB: This permits to avoid modification on commitment voucher when receiving some goods
119
if co.state == 'draft':
120
self.pool.get('account.commitment').write(cr, uid, [co.id], {'state': 'open'}, context=context)
121
# Try to update engagement lines regarding invoice line amounts and account
122
invoice_lines = defaultdict(list)
124
for invl in inv.invoice_line:
125
invoice_lines[invl.account_id.id].append(invl)
128
processed_commitment_line = []
129
for account_id in invoice_lines:
131
# compute total amount of all invoice lines that have the same account_id
132
for line in invoice_lines[account_id]:
133
total_amount += line.price_subtotal
134
# search for matching commitment line
135
cl_ids = self.pool.get('account.commitment.line').search(cr, uid, [('commit_id', '=', co.id), ('account_id', '=', account_id)], limit=1,
137
# Do nothing if no commitment line exists for this invoice line. FIXME: waiting for a decision about this case
140
cl = self.pool.get('account.commitment.line').browse(cr, uid, cl_ids, context=context)[0]
141
# if no difference between invoice lines and commitment line: delete engagement lines that come from this commitment_line
142
eng_ids = self.pool.get('account.analytic.line').search(cr, uid, [('commitment_line_id', '=', cl.id)], context=context)
143
if cl.amount == total_amount:
144
processed_commitment_line.append(cl.id)
146
self.pool.get('account.analytic.line').unlink(cr, uid, eng_ids, context=context)
147
self.pool.get('account.commitment.line').write(cr, uid, [cl.id], {'amount': 0.0}, context=context)
149
# Remember difference in diff_lines list
150
diff_lines.append({'cl': cl, 'diff': cl.amount - total_amount, 'new_mnt': total_amount})
151
# Difference lines process
153
for diff_line in diff_lines:
154
# Prepare some values
155
cl = diff_line.get('cl', False)
156
diff = diff_line.get('diff', 0.0)
157
new_mnt = diff_line.get('new_mnt', 0.0)
158
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
160
raise osv.except_osv(_('Error'), _('No commitment line found. Please contact an administrator to resolve this problem.'))
161
distrib_id = cl.analytic_distribution_id and cl.analytic_distribution_id.id or cl.commit_id and cl.commit_id.analytic_distribution_id \
162
and cl.commit_id.analytic_distribution_id.id or False
164
raise osv.except_osv(_('Error'), _('No analytic distribution found.'))
165
# Browse distribution
166
distrib = self.pool.get('analytic.distribution').browse(cr, uid, [distrib_id], context=context)[0]
167
engagement_lines = distrib.analytic_lines
168
for distrib_lines in [distrib.cost_center_lines, distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]:
169
for distrib_line in distrib_lines:
171
'account_id': distrib_line.analytic_id.id,
172
'general_account_id': cl.account_id.id,
174
if distrib_line._name == 'funding.pool.distribution.line':
175
vals.update({'cost_center_id': distrib_line.cost_center_id and distrib_line.cost_center_id.id or False,})
176
# Browse engagement lines to found out matching elements
177
for i in range(0,len(engagement_lines)):
178
if engagement_lines[i]:
179
eng_line = engagement_lines[i]
181
'account_id': eng_line.account_id.id,
182
'general_account_id': eng_line.general_account_id.id,
184
if eng_line.cost_center_id:
185
cmp_vals.update({'cost_center_id': eng_line.cost_center_id.id})
187
# Update analytic line with new amount
188
anal_amount = (distrib_line.percentage * diff) / 100
189
amount = -1 * self.pool.get('res.currency').compute(cr, uid, inv.currency_id.id, company_currency,
190
anal_amount, round=False, context=context)
191
# write new amount to corresponding engagement line
192
eng_res = self.pool.get('account.analytic.line').write(cr, uid, [eng_line.id],
193
{'amount': amount, 'amount_currency': -1 * anal_amount}, context=context)
194
# delete processed engagement lines
195
engagement_lines[i] = None
196
# update existent commitment line with new amount (new_mnt)
197
commitment_line_new_amount = cl.amount - new_mnt
198
if commitment_line_new_amount < 0.0:
199
commitment_line_new_amount = 0.0
200
self.pool.get('account.commitment.line').write(cr, uid, [cl.id], {'amount': commitment_line_new_amount}, context=context)
201
# add cl to processed_commitment_line
202
processed_commitment_line.append(cl.id)
203
# Update commitment voucher state (if total_amount is inferior to 0.0, then state is done)
204
c_total = self.pool.get('account.commitment')._get_total(cr, uid, [co.id], {}, {}, context=context)
205
if c_total and c_total.get(co.id, 1.0) <= 0.0:
206
self.pool.get('account.commitment').action_commitment_done(cr, uid, [co.id], context=context)
209
def action_open_invoice(self, cr, uid, ids, context={}):
211
Launch engagement lines updating if a commitment is attached to PO that generate this invoice.
216
if isinstance(ids, (int, long)):
218
# Prepare some values
220
# Verify if all invoice have a po that have a commitment
221
for inv in self.browse(cr, uid, ids, context=context):
222
for po in inv.purchase_ids:
223
if po.commitment_ids:
224
to_process.append(inv.id)
226
res = self.update_commitments(cr, uid, to_process, context=context)
227
return super(account_invoice, self).action_open_invoice(cr, uid, ids, context=context)
230
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: