~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to analytic_distribution_supply/invoice.py

  • Committer: Olivier DOSSMANN
  • Date: 2014-03-31 09:31:46 UTC
  • mto: This revision was merged to the branch mainline in revision 2086.
  • Revision ID: od@tempo-consulting.fr-20140331093146-tgvxnly1kc1hbv1s
UF-2171 [ADD] Analytic distribution reset button for recurring models

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
##############################################################################
 
4
#
 
5
#    OpenERP, Open Source Management Solution
 
6
#    Copyright (C) 2011 TeMPO Consulting, MSF. All Rights Reserved
 
7
#    Developer: Olivier DOSSMANN
 
8
#
 
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.
 
13
#
 
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.
 
18
#
 
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/>.
 
21
#
 
22
##############################################################################
 
23
 
 
24
from osv import osv
 
25
from osv import fields
 
26
from tools.translate import _
 
27
from collections import defaultdict
 
28
 
 
29
class account_invoice_line(osv.osv):
 
30
    _name = 'account.invoice.line'
 
31
    _inherit = 'account.invoice.line'
 
32
 
 
33
    _columns = {
 
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)"),
 
37
    }
 
38
 
 
39
account_invoice_line()
 
40
 
 
41
class account_invoice(osv.osv):
 
42
    _name = 'account.invoice'
 
43
    _inherit = 'account.invoice'
 
44
 
 
45
    _columns = {
 
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"),
 
48
    }
 
49
 
 
50
    def fetch_analytic_distribution(self, cr, uid, ids, context=None):
 
51
        """
 
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.
 
54
        """
 
55
        # Some verifications
 
56
        if not context:
 
57
            context = {}
 
58
        if isinstance(ids, (int, long)):
 
59
            ids = [ids]
 
60
        # Prepare some values
 
61
        invl_obj = self.pool.get('account.invoice.line')
 
62
        ana_obj = self.pool.get('analytic.distribution')
 
63
        # Browse all invoices
 
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
 
72
                    if po.commitment_ids:
 
73
                        distrib_id = po.commitment_ids[0].analytic_distribution_id and po.commitment_ids[0].analytic_distribution_id.id or distrib_id
 
74
                    if 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
 
85
                    if distrib_id:
 
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
 
101
                    if 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
 
111
                    if distrib_id:
 
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})
 
118
        return True
 
119
 
 
120
    def update_commitments(self, cr, uid, ids, context=None):
 
121
        """
 
122
        Update engagement lines for given invoice.
 
123
        NB: We use COMMITMENT VOUCHER ANALYTIC DISTRIBUTION for updating!
 
124
        """
 
125
        # Some verifications
 
126
        if not context:
 
127
            context = {}
 
128
        if isinstance(ids, (int, long)):
 
129
            ids = [ids]
 
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)
 
134
            if not co_ids:
 
135
                continue
 
136
            if len(co_ids) > 1:
 
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:
 
149
                    continue
 
150
                # Fetch purchase order line account
 
151
                pol = invl.order_line_id
 
152
                if pol.product_id:
 
153
                    a = pol.product_id.product_tmpl_id.property_account_expense.id
 
154
                    if not a:
 
155
                        a = pol.product_id.categ_id.property_account_expense_categ.id
 
156
                    if not a:
 
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,))
 
158
                else:
 
159
                    a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category').id
 
160
                invoice_lines[a].append(invl)
 
161
            # Browse result
 
162
            diff_lines = []
 
163
            processed_commitment_line = []
 
164
            for account_id in invoice_lines:
 
165
                total_amount = 0.0
 
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,
 
171
                    context=context)
 
172
                # Do nothing if no commitment line exists for this invoice line. FIXME: waiting for a decision about this case
 
173
                if not cl_ids:
 
174
                    continue
 
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)
 
180
                    if eng_ids:
 
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)
 
183
                else:
 
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
 
187
            if diff_lines:
 
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
 
194
                    if not cl:
 
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
 
198
                    if not distrib_id:
 
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:
 
205
                            vals = {
 
206
                                'account_id': distrib_line.analytic_id.id,
 
207
                                'general_account_id': cl.account_id.id,
 
208
                            }
 
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]
 
215
                                    cmp_vals = {
 
216
                                        'account_id': eng_line.account_id.id,
 
217
                                        'general_account_id': eng_line.general_account_id.id,
 
218
                                    }
 
219
                                    if eng_line.cost_center_id:
 
220
                                        cmp_vals.update({'cost_center_id': eng_line.cost_center_id.id})
 
221
                                    if cmp_vals == vals:
 
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)
 
242
        return True
 
243
 
 
244
    def action_open_invoice(self, cr, uid, ids, context=None):
 
245
        """
 
246
        Launch engagement lines updating if a commitment is attached to PO that generate this invoice.
 
247
        """
 
248
        # Some verifications
 
249
        if not context:
 
250
            context = {}
 
251
        if isinstance(ids, (int, long)):
 
252
            ids = [ids]
 
253
        # Prepare some values
 
254
        to_process = []
 
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)
 
263
 
 
264
        # Process invoices
 
265
        self.update_commitments(cr, uid, to_process, context=context)
 
266
        return super(account_invoice, self).action_open_invoice(cr, uid, ids, context=context)
 
267
 
 
268
account_invoice()
 
269
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: