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

« back to all changes in this revision

Viewing changes to analytic_distribution_supply/invoice.py

  • Committer: jf
  • Date: 2011-03-23 13:23:55 UTC
  • Revision ID: jf@tempo4-20110323132355-agyf1soy7m5ewatr
Initial Import

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: