~unifield-team/unifield-wm/us-671-homere

« back to all changes in this revision

Viewing changes to purchase_override/purchase.py

  • Committer: jf
  • Date: 2012-04-17 15:29:16 UTC
  • mfrom: (631.3.7 UF_828)
  • Revision ID: jf@tempo4-20120417152916-svm6ioq8ur2bi5tu
UF-955 [DEV] Reporting (Month-end) - 2 remaining reports
lp:~unifield-team/unifield-wm/UF_955

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
##############################################################################
3
3
#
4
4
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 TeMPO Consulting, MSF
 
5
#    Copyright (C) 2011 TeMPO Consulting, MSF 
6
6
#
7
7
#    This program is free software: you can redistribute it and/or modify
8
8
#    it under the terms of the GNU Affero General Public License as
23
23
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
24
from tools.translate import _
25
25
import netsvc
26
 
import time
27
 
from mx.DateTime import Parser
28
 
from mx.DateTime import RelativeDateTime
29
 
from time import strftime
30
 
from osv.orm import browse_record, browse_null
 
26
from mx.DateTime import *
 
27
 
31
28
from workflow.wkf_expr import _eval_expr
32
 
 
33
 
from dateutil.relativedelta import relativedelta
34
 
from datetime import datetime
35
 
 
36
 
import decimal_precision as dp
37
 
 
38
 
from purchase_override import PURCHASE_ORDER_STATE_SELECTION
39
 
 
40
 
class purchase_order_confirm_wizard(osv.osv):
41
 
    _name = 'purchase.order.confirm.wizard'
42
 
    _rec_name = 'order_id'
43
 
 
44
 
    _columns = {
45
 
            'order_id': fields.many2one('purchase.order', string='Purchase Order', readonly=True),
46
 
            'errors': fields.text(string='Error message', readonly=True),
47
 
        }
48
 
 
49
 
    def validate_order(self, cr, uid, ids, context=None):
50
 
        wf_service = netsvc.LocalService("workflow")
51
 
        for wiz in self.browse(cr, uid, ids, context=context):
52
 
            wf_service.trg_validate(uid, 'purchase.order', wiz.order_id.id, 'purchase_confirmed_wait', cr)
53
 
        return {'type': 'ir.actions.act_window_close'}
54
 
 
55
 
purchase_order_confirm_wizard()
 
29
import logging
56
30
 
57
31
class purchase_order(osv.osv):
58
32
    _name = 'purchase.order'
59
33
    _inherit = 'purchase.order'
60
34
 
61
 
    def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
62
 
        '''
63
 
        update the supplier info of corresponding products
64
 
        '''
65
 
        info_obj = self.pool.get('product.supplierinfo')
66
 
        pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
67
 
        for rfq in self.browse(cr, uid, ids, context=context):
68
 
            for line in rfq.order_line:
69
 
                # if the price is updated and a product selected
70
 
                if line.price_unit and line.product_id:
71
 
                    # get the product
72
 
                    product = line.product_id
73
 
                    # find the corresponding suppinfo with sequence -99
74
 
                    info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
75
 
                                                             ('sequence', '=', -99),], context=context)
76
 
 
77
 
                    if info_99_list:
78
 
                        # we drop it
79
 
                        info_obj.unlink(cr, uid, info_99_list, context=context)
80
 
 
81
 
                    # create the new one
82
 
                    values = {'name': rfq.partner_id.id,
83
 
                              'product_name': False,
84
 
                              'product_code': False,
85
 
                              'sequence' : -99,
86
 
                              #'product_uom': line.product_uom.id,
87
 
                              #'min_qty': 0.0,
88
 
                              #'qty': function
89
 
                              'product_id' : product.product_tmpl_id.id,
90
 
                              'delay' : int(rfq.partner_id.default_delay),
91
 
                              #'pricelist_ids': created just after
92
 
                              #'company_id': default value
93
 
                              }
94
 
 
95
 
                    new_info_id = info_obj.create(cr, uid, values, context=context)
96
 
                    # price lists creation - 'pricelist.partnerinfo
97
 
                    values = {'suppinfo_id': new_info_id,
98
 
                              'min_quantity': 1.00,
99
 
                              'price': line.price_unit,
100
 
                              'uom_id': line.product_uom.id,
101
 
                              'currency_id': line.currency_id.id,
102
 
                              'valid_till': rfq.valid_till,
103
 
                              'purchase_order_line_id': line.id,
104
 
                              'comment': 'RfQ original quantity for price : %s' % line.product_qty,
105
 
                              }
106
 
                    pricelist_info_obj.create(cr, uid, values, context=context)
107
 
 
108
 
        return True
109
 
 
110
 
    def generate_po_from_rfq(self, cr, uid, ids, context=None):
111
 
        '''
112
 
        generate a po from the selected request for quotation
113
 
        '''
114
 
        # Objects
115
 
        line_obj = self.pool.get('purchase.order.line')
116
 
 
117
 
        # Some verifications
118
 
        if context is None:
119
 
            context = {}
120
 
        if isinstance(ids, (int, long)):
121
 
            ids = [ids]
122
 
 
123
 
        # update price lists
124
 
        self.update_supplier_info(cr, uid, ids, context=context)
125
 
        # copy the po with rfq_ok set to False
126
 
        data = self.read(cr, uid, ids[0], ['name', 'amount_total'], context=context)
127
 
        if not data.get('amount_total', 0.00):
128
 
            raise osv.except_osv(
129
 
                _('Error'),
130
 
                _('Generation of PO aborted because no price defined on lines.'),
131
 
            )
132
 
        new_po_id = self.copy(cr, uid, ids[0], {'name': False, 'rfq_ok': False, 'origin': data['name']}, context=dict(context,keepOrigin=True))
133
 
        # Remove lines with 0.00 as unit price
134
 
        no_price_line_ids = line_obj.search(cr, uid, [
135
 
            ('order_id', '=', new_po_id),
136
 
            ('price_unit', '=', 0.00),
137
 
        ], context=context)
138
 
        line_obj.unlink(cr, uid, no_price_line_ids, context=context)
139
 
 
140
 
        data = self.read(cr, uid, new_po_id, ['name'], context=context)
141
 
        # log message describing the previous action
142
 
        self.log(cr, uid, new_po_id, _('The Purchase Order %s has been generated from Request for Quotation.')%data['name'])
143
 
        # close the current po
144
 
        wf_service = netsvc.LocalService("workflow")
145
 
        wf_service.trg_validate(uid, 'purchase.order', ids[0], 'rfq_done', cr)
146
 
 
147
 
        return new_po_id
148
 
 
149
 
    def copy(self, cr, uid, p_id, default=None, context=None):
 
35
    def copy(self, cr, uid, id, default=None, context=None):
150
36
        '''
151
37
        Remove loan_id field on new purchase.order
152
38
        '''
153
39
        if not default:
154
40
            default = {}
155
 
        if context is None:
156
 
            context = {}
157
 
 
158
 
        # if the copy comes from the button duplicate
159
 
        if context.get('from_button'):
160
 
            default.update({'is_a_counterpart': False})
161
 
        default.update({'loan_id': False, 'merged_line_ids': False, 'partner_ref': False})
162
 
        if not context.get('keepOrigin', False):
163
 
            default.update({'origin': False})
164
 
 
165
 
        return super(purchase_order, self).copy(cr, uid, p_id, default, context=context)
166
 
 
 
41
        default.update({'loan_id': False})
 
42
        return super(purchase_order, self).copy(cr, uid, id, default, context=context)
 
43
    
167
44
    # @@@purchase.purchase_order._invoiced
168
45
    def _invoiced(self, cursor, user, ids, name, arg, context=None):
169
46
        res = {}
174
51
            res[purchase.id] = invoiced
175
52
        return res
176
53
    # @@@end
177
 
 
 
54
    
178
55
    # @@@purchase.purchase_order._shipped_rate
179
56
    def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
180
57
        res = {}
181
 
        sp_obj = self.pool.get('stock.picking')
182
 
        inv_obj = self.pool.get('account.invoice')
183
58
        for purchase in self.browse(cursor, user, ids, context=context):
184
 
            if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type in ('internal', 'esc')) or \
 
59
            if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type == 'internal') or \
185
60
                purchase.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']):
186
61
                res[purchase.id] = purchase.shipped_rate
187
62
            else:
188
63
                tot = 0.0
189
 
                # UTP-808: Deleted invoices amount should be taken in this process. So what we do:
190
 
                # 1/ Take all closed stock picking linked to the purchase
191
 
                # 2/ Search invoices linked to these stock picking
192
 
                # 3/ Take stock picking not linked to an invoice
193
 
                # 4/ Use these non-invoiced closed stock picking to add their amount to the "invoiced" amount
194
64
                for invoice in purchase.invoice_ids:
195
65
                    if invoice.state not in ('draft','cancel'):
196
66
                        tot += invoice.amount_untaxed
197
 
                stock_pickings = sp_obj.search(cursor, user, [('purchase_id', '=', purchase.id), ('state', '=', 'done')])
198
 
                if stock_pickings:
199
 
                    sp_ids = list(stock_pickings)
200
 
                    if isinstance(stock_pickings, (int, long)):
201
 
                        stock_pickings = [stock_pickings]
202
 
                    for sp in stock_pickings:
203
 
                        inv_ids = inv_obj.search(cursor, user, [('picking_id', '=', sp)])
204
 
                        if inv_ids:
205
 
                            sp_ids.remove(sp)
206
 
                    if sp_ids:
207
 
                        for stock_picking in sp_obj.browse(cursor, user, sp_ids):
208
 
                            for line in stock_picking.move_lines:
209
 
                                tot += line.product_qty * line.price_unit
210
67
                if purchase.amount_untaxed:
211
68
                    res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
212
69
                else:
213
70
                    res[purchase.id] = 0.0
214
71
        return res
215
72
    # @@@end
216
 
 
217
 
    def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
218
 
        '''
219
 
        Returns the Unifield configuration value
220
 
        '''
221
 
        res = {}
222
 
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
223
 
 
224
 
        for order in ids:
225
 
            res[order] = setup.allocation_setup
226
 
 
227
 
        return res
228
 
 
229
 
    def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
230
 
        res = {}
231
 
        for order in self.browse(cr, uid, ids, context=context):
232
 
            res[order.id] = True
233
 
            if order.order_line:
234
 
                res[order.id] = False
235
 
        return res
236
 
 
237
 
    def _po_from_x(self, cr, uid, ids, field_names, args, context=None):
238
 
        """fields.function multi for 'po_from_ir' and 'po_from_fo' fields."""
239
 
        res = {}
240
 
        pol_obj = self.pool.get('purchase.order.line')
241
 
        sol_obj = self.pool.get('sale.order.line')
242
 
        for po_data in self.read(cr, uid, ids, ['order_line'], context=context):
243
 
            res[po_data['id']] = {'po_from_ir': False, 'po_from_fo': False}
244
 
            pol_ids = po_data.get('order_line')
245
 
            if pol_ids:
246
 
                pol_datas = pol_obj.read(
247
 
                    cr, uid, pol_ids, ['procurement_id'], context=context)
248
 
                proc_ids = [pol['procurement_id'][0]
249
 
                            for pol in pol_datas if pol.get('procurement_id')]
250
 
                if proc_ids:
251
 
                    # po_from_ir
252
 
                    sol_ids = sol_obj.search(
253
 
                        cr, uid,
254
 
                        [('procurement_id', 'in', proc_ids)],
255
 
                        context=context)
256
 
                    res[po_data['id']]['po_from_ir'] = bool(sol_ids)
257
 
                    # po_from_fo
258
 
                    sol_ids = sol_obj.search(
259
 
                        cr, uid,
260
 
                        [('procurement_id', 'in', proc_ids),
261
 
                         ('order_id.procurement_request', '=', False)],
262
 
                        context=context)
263
 
                    res[po_data['id']]['po_from_fo'] = bool(sol_ids)
264
 
        return res
265
 
 
266
 
    def _get_dest_partner_names(self, cr, uid, ids, field_name, args, context=None):
267
 
        res = {}
268
 
        for po_r in self.read(cr, uid, ids, ['dest_partner_ids'], context=context):
269
 
            names = ''
270
 
            if po_r['dest_partner_ids']:
271
 
                name_tuples = self.pool.get('res.partner').name_get(cr, uid, po_r['dest_partner_ids'], context=context)
272
 
                if name_tuples:
273
 
                    names_list = [nt[1] for nt in name_tuples]
274
 
                    names = "; ".join(names_list)
275
 
            res[po_r['id']] = names
276
 
        return res
277
 
 
278
 
    def _get_project_ref(self, cr, uid, ids, field_name, args, context=None):
279
 
        '''
280
 
        Get the name of the POs at project side
281
 
        '''
282
 
        if isinstance(ids, (int, long)):
283
 
            ids = [ids]
284
 
 
285
 
        res = {}
286
 
        for po in ids:
287
 
            res[po] = {
288
 
                'fnct_project_ref': '',
289
 
                'sourced_references': '',
290
 
            }
291
 
 
292
 
            so_ids = self.get_so_ids_from_po_ids(cr, uid, po, context=context)
293
 
            for so in self.pool.get('sale.order').browse(cr, uid, so_ids, context=context):
294
 
                if so.client_order_ref:
295
 
                    if res[po]['fnct_project_ref']:
296
 
                        res[po]['fnct_project_ref'] += ' - '
297
 
                    res[po]['fnct_project_ref'] += so.client_order_ref
298
 
 
299
 
                if res[po]['sourced_references']:
300
 
                    res[po]['sourced_references'] += ','
301
 
                res[po]['sourced_references'] += so.name
302
 
 
303
 
        return res
304
 
 
305
 
    def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
306
 
        '''
307
 
        Return True if the system configuration VAT management is set to True
308
 
        '''
309
 
        vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
310
 
        res = {}
311
 
        for id in ids:
312
 
            res[id] = vat_ok
313
 
 
314
 
        return res
315
 
 
316
 
    def _get_requested_date_in_past(self, cr, uid, ids, field_name, args, context=None):
317
 
        if isinstance(ids, (int, long)):
318
 
            ids = [ids]
319
 
 
320
 
        res = {}
321
 
        for po in self.read(cr, uid, ids, ['delivery_requested_date', 'rfq_ok'], context=context):
322
 
            res[po['id']] = po['delivery_requested_date'] and not po['rfq_ok'] and po['delivery_requested_date'] < time.strftime('%Y-%m-%d') or False
323
 
 
324
 
        return res
325
 
 
 
73
    
326
74
    _columns = {
327
 
        'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
328
 
                                        ('donation_st', 'Standard donation'), ('loan', 'Loan'),
 
75
        'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'), 
 
76
                                        ('donation_st', 'Standard donation'), ('loan', 'Loan'), 
329
77
                                        ('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
330
 
                                        ('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
 
78
                                        ('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
331
79
        'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
332
80
        'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
333
81
        'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
334
 
        # we increase the size of the 'details' field from 30 to 86
335
 
        'details': fields.char(size=86, string='Details', states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
336
 
        'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
 
82
        'details': fields.char(size=30, string='Details', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
 
83
        'invoiced': fields.function(_invoiced, method=True, string='Invoiced & Paid', type='boolean', help="It indicates that an invoice has been paid"),
337
84
        'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
338
 
        'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
 
85
        'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
339
86
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
340
87
        'date_order':fields.date(string='Creation Date', readonly=True, required=True,
341
88
                            states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
342
 
        'name': fields.char('Order Reference', size=64, required=True, select=True, readonly=True,
 
89
        'name': fields.char('Order Reference', size=64, required=True, select=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)]},
343
90
                            help="unique number of the purchase order,computed automatically when the purchase order is created"),
344
91
        'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
345
92
        'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'rfq_sent':[('readonly',False)], 'confirmed': [('readonly',False)]}),
346
 
        'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)],'cancel':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
 
93
        'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
347
94
        'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
348
 
            states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
 
95
            states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
349
96
        'dest_partner_id': fields.many2one('res.partner', string='Destination partner', domain=[('partner_type', '=', 'internal')]),
350
 
        'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
 
97
        'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True, 
351
98
                                              help="The address where the invoice will be sent."),
352
99
        'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
353
100
            help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
356
103
                "Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
357
104
        ),
358
105
        'merged_line_ids': fields.one2many('purchase.order.merged.line', 'order_id', string='Merged line'),
359
 
        'date_confirm': fields.date(string='Confirmation date'),
360
 
        'allocation_setup': fields.function(_get_allocation_setup, type='selection',
361
 
                                            selection=[('allocated', 'Allocated'),
362
 
                                                       ('unallocated', 'Unallocated'),
363
 
                                                       ('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
364
 
        'unallocation_ok': fields.boolean(string='Unallocated PO'),
365
 
        # we increase the size of the partner_ref field from 64 to 128
366
 
        'partner_ref': fields.char('Supplier Reference', size=128),
367
 
        'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
368
 
        'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
369
 
        'active': fields.boolean('Active', readonly=True),
370
 
        'po_from_ir': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from IR ?', multi='po_from_x'),
371
 
        'po_from_fo': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from FO ?', multi='po_from_x'),
372
 
        'canceled_end': fields.boolean(string='Canceled End', readonly=True),
373
 
        'is_a_counterpart': fields.boolean('Counterpart?', help="This field is only for indicating that the order is a counterpart"),
374
 
        'po_updated_by_sync': fields.boolean('PO updated by sync', readonly=False),
375
 
        'origin': fields.text('Source Document',
376
 
                        help="Reference of the document that generated this purchase order request."),
377
 
        # UF-2267: Store also the parent PO as reference in the sourced PO
378
 
        'parent_order_name': fields.many2one('purchase.order', string='Parent PO name', help='If the PO is created from a re-source FO, this field contains the relevant original PO name'),
379
 
        'project_ref': fields.char(size=256, string='Project Ref.'),
380
 
        'message_esc': fields.text(string='ESC Message'),
381
 
        'fnct_project_ref': fields.function(_get_project_ref, method=True, string='Project Ref.',
382
 
                                            type='char', size=256, store=False, multi='so_info'),
383
 
        'dest_partner_ids': fields.many2many('res.partner', 'res_partner_purchase_order_rel', 'purchase_order_id', 'partner_id', 'Customers'),  # uf-2223
384
 
        'dest_partner_names': fields.function(_get_dest_partner_names, type='char', size=256,  string='Customers', method=True),  # uf-2223
385
 
        'split_po': fields.boolean('Created by split PO', readonly=True),
386
 
        'sourced_references': fields.function(
387
 
            _get_project_ref,
388
 
            method=True,
389
 
            string='Sourced references',
390
 
            type='text',
391
 
            store=False,
392
 
            multi='so_info',
393
 
        ),
394
 
        'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
395
 
        'requested_date_in_past': fields.function(
396
 
            _get_requested_date_in_past,
397
 
            method=True,
398
 
            string='Requested date in past',
399
 
            type='boolean',
400
 
            store=False,
401
 
        ),
402
106
    }
403
 
 
 
107
    
404
108
    _defaults = {
405
109
        'order_type': lambda *a: 'regular',
406
110
        'priority': lambda *a: 'normal',
407
111
        'categ': lambda *a: 'other',
408
112
        'loan_duration': 2,
409
113
        'from_yml_test': lambda *a: False,
410
 
        'invoice_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.partner_id.id, ['invoice'])['invoice'],
 
114
        'invoice_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.id, ['invoice'])['invoice'],
411
115
        'invoice_method': lambda *a: 'picking',
412
 
        'dest_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.partner_id.id, ['delivery'])['delivery'],
413
 
        'no_line': lambda *a: True,
414
 
        'active': True,
415
 
        'name': lambda *a: False,
416
 
        'is_a_counterpart': False,
417
 
        'parent_order_name': False,
418
 
        'canceled_end': False,
419
 
        'split_po': False,
420
 
        'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
421
116
    }
422
117
 
423
 
    def _check_po_from_fo(self, cr, uid, ids, context=None):
424
 
        if not context:
425
 
            context = {}
426
 
        retour = True
427
 
        for po in self.browse(cr, uid, ids, context=context):
428
 
            if po.partner_id.partner_type == 'internal' and po.po_from_fo:
429
 
                retour = False
430
 
        return retour
431
 
 
432
 
    _constraints = [
433
 
        (_check_po_from_fo, 'You cannot choose an internal supplier for this purchase order', []),
434
 
    ]
435
 
 
436
 
    def _check_service(self, cr, uid, ids, vals, context=None):
437
 
        '''
438
 
        Avoid the saving of a PO with non service products on Service PO
439
 
        '''
440
 
        # UTP-871 : Remove check of service
441
 
        return True
442
 
 
443
 
        if isinstance(ids, (int, long)):
444
 
            ids = [ids]
445
 
        if context is None:
446
 
            context = {}
447
 
        if context.get('import_in_progress'):
448
 
            return True
449
 
 
450
 
        for order in self.browse(cr, uid, ids, context=context):
451
 
            for line in order.order_line:
452
 
                if vals.get('categ', order.categ) == 'transport' and line.product_id and (line.product_id.type not in ('service', 'service_recep') or not line.product_id.transport_ok):
453
 
                    raise osv.except_osv(_('Error'), _('The product [%s]%s is not a \'Transport\' product. You can purchase only \'Transport\' products on a \'Transport\' purchase order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
454
 
                    return False
455
 
                elif vals.get('categ', order.categ) == 'service' and line.product_id and line.product_id.type not in ('service', 'service_recep'):
456
 
                    raise osv.except_osv(_('Error'), _('The product [%s] %s is not a \'Service\' product. You can purchase only \'Service\' products on a \'Service\' purchase order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
457
 
                    return False
458
 
 
459
 
        return True
460
 
 
461
 
    def purchase_cancel(self, cr, uid, ids, context=None):
462
 
        '''
463
 
        Call the wizard to ask if you want to re-source the line
464
 
        '''
465
 
        line_obj = self.pool.get('purchase.order.line')
466
 
        wiz_obj = self.pool.get('purchase.order.cancel.wizard')
467
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
468
 
        so_obj = self.pool.get('sale.order')
469
 
        data_obj = self.pool.get('ir.model.data')
470
 
        wf_service = netsvc.LocalService("workflow")
471
 
 
472
 
        if context is None:
473
 
            context = {}
474
 
 
475
 
        if isinstance(ids, (int, long)):
476
 
            ids = [ids]
477
 
 
478
 
        if context.get('rfq_ok', False):
479
 
            view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'rfq_cancel_wizard_form_view')[1]
480
 
        else:
481
 
            view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'purchase_order_cancel_wizard_form_view')[1]
482
 
 
483
 
        so_to_cancel_ids = set()
484
 
        for po in self.browse(cr, uid, ids, context=context):
485
 
            for l in po.order_line:
486
 
                if line_obj.get_sol_ids_from_pol_ids(cr, uid, [l.id], context=context):
487
 
                    wiz_id = wiz_obj.create(cr, uid, {
488
 
                        'order_id': po.id,
489
 
                        'last_lines': wiz_obj._get_last_lines(cr, uid, po.id, context=context),
490
 
                    }, context=context)
491
 
                    return {'type': 'ir.actions.act_window',
492
 
                            'res_model': 'purchase.order.cancel.wizard',
493
 
                            'res_id': wiz_id,
494
 
                            'view_type': 'form',
495
 
                            'view_mode': 'form',
496
 
                            'view_id': [view_id],
497
 
                            'target': 'new',
498
 
                            'context': context}
499
 
                else:
500
 
                    exp_sol_ids = exp_sol_obj.search(cr, uid, [('po_id', '=', po.id)], context=context)
501
 
                    for exp in exp_sol_obj.browse(cr, uid, exp_sol_ids, context=context):
502
 
                        if not exp.order_id.order_line:
503
 
                            so_to_cancel_ids.add(exp.order_id.id)
504
 
 
505
 
            wf_service.trg_validate(uid, 'purchase.order', po.id, 'purchase_cancel', cr)
506
 
 
507
 
        # Ask user to choose what must be done on the FO/IR
508
 
        if so_to_cancel_ids:
509
 
            context.update({
510
 
                'from_po': True,
511
 
                'po_ids': list(ids),
512
 
            })
513
 
            return so_obj.open_cancel_wizard(cr, uid, set(so_to_cancel_ids), context=context)
514
 
 
515
 
 
516
 
        return True
517
 
 
518
 
    def unlink(self, cr, uid, ids, context=None):
519
 
        '''
520
 
        No unlink for PO linked to a FO
521
 
        '''
522
 
        if self.get_so_ids_from_po_ids(cr, uid, ids, context=context):
523
 
            raise osv.except_osv(_('Error'), _('You cannot remove a Purchase order that is linked to a Field Order or an Internal Request. Please cancel it instead.'))
524
 
 
525
 
        return super(purchase_order, self).unlink(cr, uid, ids, context=context)
526
 
 
527
 
    def _check_restriction_line(self, cr, uid, ids, context=None):
528
 
        '''
529
 
        Check restriction on products
530
 
        '''
531
 
        if isinstance(ids, (int, long)):
532
 
            ids = [ids]
533
 
 
534
 
        line_obj = self.pool.get('purchase.order.line')
535
 
        res = True
536
 
 
537
 
        for order in self.browse(cr, uid, ids, context=context):
538
 
            res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in order.order_line], context=context)
539
 
 
540
 
        return res
541
 
 
542
 
 
543
 
    def default_get(self, cr, uid, fields, context=None):
544
 
        '''
545
 
        Fill the unallocated_ok field according to Unifield setup
546
 
        '''
547
 
        res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
548
 
 
549
 
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
550
 
        res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
551
 
        if setup.allocation_setup == 'unallocated':
552
 
            res.update({'unallocation_ok': True})
553
 
 
554
 
        res.update({'name': False})
555
 
 
556
 
        return res
557
 
 
558
 
 
559
118
    def _check_user_company(self, cr, uid, company_id, context=None):
560
119
        '''
561
120
        Remove the possibility to make a PO to user's company
568
127
 
569
128
    def write(self, cr, uid, ids, vals, context=None):
570
129
        '''
571
 
        Check if the partner is correct.
572
 
        # UTP-114 demand purchase_list PO to be "from picking" as invoice_method
 
130
        Check if the partner is correct
573
131
        '''
574
132
        if 'partner_id' in vals:
575
133
            self._check_user_company(cr, uid, vals['partner_id'], context=context)
576
 
 
577
 
        self._check_service(cr, uid, ids, vals, context=context)
578
 
 
579
 
        for order in self.browse(cr, uid, ids, context=context):
580
 
            partner_type = self.pool.get('res.partner').browse(cr, uid, vals.get('partner_id', order.partner_id.id), context=context).partner_type
581
 
            if vals.get('order_type'):
582
 
                if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
583
 
                    vals.update({'invoice_method': 'manual'})
584
 
                elif vals.get('order_type') in ['direct',] and partner_type != 'esc':
585
 
                    vals.update({'invoice_method': 'order'})
586
 
                elif vals.get('order_type') in ['direct',] and partner_type == 'esc':
587
 
                    vals.update({'invoice_method': 'manual'})
588
 
                else:
589
 
                    vals.update({'invoice_method': 'picking'})
590
 
            # we need to update the location_id because it is readonly and so does not pass in the vals of create and write
591
 
            vals = self._get_location_id(cr, uid, vals,  warehouse_id=vals.get('warehouse_id', order.warehouse_id.id), context=context)
592
 
 
593
 
        res = super(purchase_order, self).write(cr, uid, ids, vals, context=context)
594
 
 
595
 
        # Delete expected sale order line
596
 
        if 'state' in vals and vals.get('state') not in ('draft', 'confirmed'):
597
 
            exp_sol_ids = self.pool.get('expected.sale.order.line').search(cr, uid, [('po_id', 'in', ids)], context=context)
598
 
            self.pool.get('expected.sale.order.line').unlink(cr, uid, exp_sol_ids, context=context)
599
 
 
600
 
        return res
601
 
 
602
 
    def onchange_internal_type(self, cr, uid, ids, order_type, partner_id, categ, dest_partner_id=False, warehouse_id=False, delivery_requested_date=False):
 
134
            
 
135
        if vals.get('order_type'):
 
136
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan', 'in_kind']:
 
137
                vals.update({'invoice_method': 'manual'})
 
138
            elif vals.get('order_type') in ['direct', 'purchase_list']:
 
139
                vals.update({'invoice_method': 'order'})
 
140
            else:
 
141
                vals.update({'invoice_method': 'picking'})
 
142
 
 
143
        return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
 
144
    
 
145
    def onchange_internal_type(self, cr, uid, ids, order_type, partner_id):
603
146
        '''
604
147
        Changes the invoice method of the purchase order according to
605
148
        the choosen order type
607
150
        '''
608
151
        partner_obj = self.pool.get('res.partner')
609
152
        v = {}
610
 
        # the domain on the onchange was replace by a several fields.function that you can retrieve in the
611
 
        # file msf_custom_settings/view/purchase_view.xml: domain="[('supplier', '=', True), ('id', '!=', company_id), ('check_partner_po', '=', order_type),  ('check_partner_rfq', '=', tender_id)]"
612
 
#        d = {'partner_id': []}
 
153
        d = {'partner_id': []}
613
154
        w = {}
614
155
        local_market = None
615
 
 
 
156
        
616
157
        # Search the local market partner id
617
158
        data_obj = self.pool.get('ir.model.data')
618
159
        data_id = data_obj.search(cr, uid, [('module', '=', 'order_types'), ('model', '=', 'res.partner'), ('name', '=', 'res_partner_local_market')] )
619
160
        if data_id:
620
161
            local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
621
 
 
622
 
        if order_type == 'loan':
623
 
            setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
624
 
 
625
 
            if not setup.field_orders_ok:
626
 
                return {'value': {'order_type': 'regular'},
627
 
                        'warning': {'title': 'Error',
628
 
                                    'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
629
 
 
630
 
        if order_type in ['donation_exp', 'donation_st', 'loan']:
 
162
        
 
163
        if order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']:
631
164
            v['invoice_method'] = 'manual'
632
 
        elif order_type in ['direct']:
 
165
        elif order_type in ['direct', 'purchase_list']:
633
166
            v['invoice_method'] = 'order'
634
 
        elif order_type in ['in_kind', 'purchase_list']:
635
 
            v['invoice_method'] = 'picking'
636
 
        else:
637
 
            v['invoice_method'] = 'picking'
638
 
 
639
 
        company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
640
 
 
641
 
        if order_type == 'direct' and dest_partner_id and dest_partner_id != company_id:
642
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
643
 
            v.update({'dest_address_id': cp_address_id})
644
 
        elif order_type == 'direct':
645
 
            v.update({'dest_address_id': False, 'dest_partner_id': False})
646
 
        else:
647
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, company_id, ['delivery'])['delivery']
648
 
            v.update({'dest_address_id': cp_address_id, 'dest_partner_id': company_id})
 
167
            d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
 
168
        else:
 
169
            v['invoice_method'] = 'picking'
649
170
 
650
171
        if partner_id and partner_id != local_market:
651
172
            partner = partner_obj.browse(cr, uid, partner_id)
652
 
            if partner.partner_type in ('internal', 'esc') and order_type in ('regular', 'direct'):
 
173
            if partner.partner_type == 'internal' and order_type == 'regular':
653
174
                v['invoice_method'] = 'manual'
654
175
            elif partner.partner_type not in ('external', 'esc') and order_type == 'direct':
655
 
                v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
 
176
                v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False})
 
177
                d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
656
178
                w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
657
179
                          'title': 'An error has occured !'})
658
180
        elif partner_id and partner_id == local_market and order_type != 'purchase_list':
659
181
            v['partner_id'] = None
 
182
            v['dest_address_id'] = None
660
183
            v['partner_address_id'] = None
661
184
            v['pricelist_id'] = None
662
 
 
 
185
            
663
186
        if order_type == 'purchase_list':
664
187
            if local_market:
665
188
                partner = self.pool.get('res.partner').browse(cr, uid, local_market)
666
189
                v['partner_id'] = partner.id
667
190
                if partner.address:
 
191
                    v['dest_address_id'] = partner.address[0].id
668
192
                    v['partner_address_id'] = partner.address[0].id
669
193
                if partner.property_product_pricelist_purchase:
670
194
                    v['pricelist_id'] = partner.property_product_pricelist_purchase.id
671
 
        elif order_type == 'direct':
672
 
            v['cross_docking_ok'] = False
673
 
 
674
 
        return {'value': v, 'warning': w}
675
 
 
 
195
        
 
196
        return {'value': v, 'domain': d, 'warning': w}
 
197
    
676
198
    def onchange_partner_id(self, cr, uid, ids, part, *a, **b):
677
199
        '''
678
200
        Fills the Requested and Confirmed delivery dates
679
201
        '''
680
202
        if isinstance(ids, (int, long)):
681
203
            ids = [ids]
682
 
 
 
204
        
683
205
        res = super(purchase_order, self).onchange_partner_id(cr, uid, ids, part, *a, **b)
684
 
 
 
206
        
685
207
        if part:
686
208
            partner_obj = self.pool.get('res.partner')
687
 
            product_obj = self.pool.get('product.product')
688
209
            partner = partner_obj.browse(cr, uid, part)
689
 
            if ids:
690
 
                # Check the restrction of product in lines
691
 
                if ids:
692
 
                    product_obj = self.pool.get('product.product')
693
 
                    for order in self.browse(cr, uid, ids):
694
 
                        for line in order.order_line:
695
 
                            if line.product_id:
696
 
                                res, test = product_obj._on_change_restriction_error(cr, uid, line.product_id.id, field_name='partner_id', values=res, vals={'partner_id': part})
697
 
                                if test:
698
 
                                    res.setdefault('value', {}).update({'partner_address_id': False})
699
 
                                    return res
700
 
            if partner.partner_type in ('internal', 'esc'):
 
210
            if partner.partner_type == 'internal':
701
211
                res['value']['invoice_method'] = 'manual'
702
 
            elif ids and partner.partner_type == 'intermission':
703
 
                try:
704
 
                    intermission = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
705
 
                        'analytic_account_project_intermission')[1]
706
 
                except ValueError:
707
 
                    intermission = 0
708
 
                cr.execute('''select po.id from purchase_order po
709
 
                    left join purchase_order_line pol on pol.order_id = po.id
710
 
                    left join cost_center_distribution_line cl1 on cl1.distribution_id = po.analytic_distribution_id
711
 
                    left join cost_center_distribution_line cl2 on cl2.distribution_id = pol.analytic_distribution_id
712
 
                    where po.id in %s and (cl1.analytic_id!=%s or cl2.analytic_id!=%s)''', (tuple(ids), intermission, intermission))
713
 
                if cr.rowcount > 0:
714
 
                    res.setdefault('warning', {})
715
 
                    msg = _('You set an intermission partner, at validation Cost Centers will be changed to intermission.')
716
 
                    if res.get('warning', {}).get('message'):
717
 
                        res['warning']['message'] += msg
718
 
                    else:
719
 
                        res['warning'] = {'title': _('Warning'), 'message': msg}
720
 
        return res
721
 
 
722
 
    # Be careful during integration, the onchange_warehouse_id method is also defined on UF-965
723
 
    def onchange_warehouse_id(self, cr, uid, ids,  warehouse_id, order_type, dest_address_id):
724
 
        '''
725
 
        Change the destination address to the destination address of the company if False
726
 
        '''
727
 
        res = super(purchase_order, self).onchange_warehouse_id(cr, uid, ids, warehouse_id)
728
 
 
729
 
        if not res.get('value', {}).get('dest_address_id') and order_type!='direct':
730
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id, ['delivery'])['delivery']
731
 
            if 'value' in res:
732
 
                res['value'].update({'dest_address_id': cp_address_id})
733
 
            else:
734
 
                res.update({'value': {'dest_address_id': cp_address_id}})
735
 
        if order_type == 'direct' or dest_address_id:
736
 
            if 'dest_address_id' in res.get('value', {}):
737
 
                res['value'].pop('dest_address_id')
738
 
 
739
 
        return res
740
 
 
 
212
        
 
213
        return res
 
214
    
741
215
    def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
742
216
        '''
743
217
        Fill automatically the destination address according to the destination partner
744
218
        '''
745
219
        v = {}
746
 
 
 
220
        d = {}
 
221
        
747
222
        if not context:
748
223
            context = {}
749
 
 
 
224
        
750
225
        if not dest_partner_id:
751
226
            v.update({'dest_address_id': False})
 
227
            d.update({'dest_address_id': []})
 
228
        
 
229
        d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
 
230
        
 
231
        delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
 
232
        v.update({'dest_address_id': delivery_addr['delivery']})
 
233
        
 
234
        return {'value': v, 'domain': d}
 
235
 
 
236
    def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
 
237
        '''
 
238
        Change the logged message
 
239
        '''
 
240
        if context is None:
 
241
            context = {}
 
242
        if 'po' in kwargs:
 
243
            po = kwargs['po']
 
244
            return _("Purchase order '%s' is validated.") % (po.name,)
752
245
        else:
753
 
            delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
754
 
            v.update({'dest_address_id': delivery_addr['delivery']})
755
 
        return {'value': v}
756
 
 
757
 
    def change_currency(self, cr, uid, ids, context=None):
758
 
        '''
759
 
        Launches the wizard to change the currency and update lines
760
 
        '''
761
 
        if not context:
762
 
            context = {}
763
 
 
764
 
        if isinstance(ids, (int, long)):
765
 
            ids = [ids]
766
 
 
767
 
        for order in self.browse(cr, uid, ids, context=context):
768
 
            data = {'order_id': order.id,
769
 
                    'partner_id': order.partner_id.id,
770
 
                    'partner_type': order.partner_id.partner_type,
771
 
                    'new_pricelist_id': order.pricelist_id.id,
772
 
                    'currency_rate': 1.00,
773
 
                    'old_pricelist_id': order.pricelist_id.id}
774
 
            wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
775
 
            return {'type': 'ir.actions.act_window',
776
 
                    'res_model': 'purchase.order.change.currency',
777
 
                    'view_type': 'form',
778
 
                    'view_mode': 'form',
779
 
                    'res_id': wiz,
780
 
                    'target': 'new'}
781
 
 
782
 
        return True
783
 
 
784
 
    def order_line_change(self, cr, uid, ids, order_line):
785
 
        res = {'no_line': True}
786
 
 
787
 
        if order_line:
788
 
            res = {'no_line': False}
789
 
 
790
 
        return {'value': res}
791
 
 
792
 
    def _get_destination_ok(self, cr, uid, lines, context):
793
 
        dest_ok = False
794
 
        for line in lines:
795
 
            is_inkind = line.order_id and line.order_id.order_type == 'in_kind' or False
796
 
            dest_ok = line.account_4_distribution and line.account_4_distribution.destination_ids or False
797
 
            if not dest_ok:
798
 
                if is_inkind:
799
 
                    raise osv.except_osv(_('Error'), _('No destination found. An In-kind Donation account is probably missing for this line: %s.') % (line.name or ''))
800
 
                raise osv.except_osv(_('Error'), _('No destination found for this line: %s.') % (line.name or '',))
801
 
        return dest_ok
802
 
 
803
 
    def check_analytic_distribution(self, cr, uid, ids, context=None):
804
 
        """
805
 
        Check analytic distribution validity for given PO.
806
 
        Also check that partner have a donation account (is PO is in_kind)
807
 
        """
808
 
        # Objects
809
 
        ad_obj = self.pool.get('analytic.distribution')
810
 
        ccdl_obj = self.pool.get('cost.center.distribution.line')
811
 
        pol_obj = self.pool.get('purchase.order.line')
812
 
 
813
 
        if context is None:
814
 
            context = {}
815
 
 
816
 
        if isinstance(ids, (int, long)):
817
 
            ids = [ids]
818
 
 
819
 
        # Analytic distribution verification
820
 
        for po in self.browse(cr, uid, ids, context=context):
821
 
            try:
822
 
                intermission_cc = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
823
 
                                                'analytic_account_project_intermission')[1]
824
 
            except ValueError:
825
 
                intermission_cc = 0
826
 
 
827
 
            if po.order_type == 'in_kind' and not po.partner_id.donation_payable_account:
828
 
                if not po.partner_id.donation_payable_account:
829
 
                    raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
830
 
 
831
 
            if po.partner_id and po.partner_id.partner_type == 'intermission':
832
 
                if not intermission_cc:
833
 
                    raise osv.except_osv(_('Error'), _('No Intermission Cost Center found!'))
834
 
 
835
 
            for pol in po.order_line:
836
 
                distrib = pol.analytic_distribution_id  or po.analytic_distribution_id  or False
837
 
                # Raise an error if no analytic distribution found
838
 
                if not distrib:
839
 
                    # UFTP-336: For the case of a new line added from Coordo, it's a push flow, no need to check the AD! VERY SPECIAL CASE
840
 
                    if not po.order_type in ('loan', 'donation_st', 'donation_exp', 'in_kind') and not po.push_fo:
841
 
                        raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
842
 
 
843
 
                    # UF-2031: If no distrib accepted (for loan, donation), then do not process the distrib
844
 
                    return True
845
 
                elif pol.analytic_distribution_state != 'valid':
846
 
                    id_ad = ad_obj.create(cr, uid, {})
847
 
                    ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_center_lines or po.analytic_distribution_id.cost_center_lines
848
 
                    bro_dests = self._get_destination_ok(cr, uid, [pol], context=context)
849
 
                    for line in ad_lines:
850
 
                        # fetch compatible destinations then use on of them:
851
 
                        # - destination if compatible
852
 
                        # - else default destination of given account
853
 
                        if line.destination_id in bro_dests:
854
 
                            bro_dest_ok = line.destination_id
855
 
                        else:
856
 
                            bro_dest_ok = pol.account_4_distribution.default_destination_id
857
 
                        # Copy cost center line to the new distribution
858
 
                        ccdl_obj.copy(cr, uid, line.id, {
859
 
                            'distribution_id': id_ad,
860
 
                            'destination_id': bro_dest_ok.id,
861
 
                            'partner_type': pol.order_id.partner_id.partner_type,
862
 
                        })
863
 
                        # Write result
864
 
                        pol_obj.write(cr, uid, [pol.id], {'analytic_distribution_id': id_ad})
865
 
                else:
866
 
                    ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_center_lines or po.analytic_distribution_id.cost_center_lines
867
 
                    for line in ad_lines:
868
 
                        if not line.partner_type:
869
 
                            ccdl_obj.write(cr, uid, [line.id], {
870
 
                                'partner_type': pol.order_id.partner_id.partner_type,
871
 
                            })
872
 
 
873
 
        return True
874
 
 
875
 
    def wkf_picking_done(self, cr, uid, ids, context=None):
876
 
        '''
877
 
        Change the shipped boolean and the state of the PO
878
 
        '''
879
 
        for order in self.browse(cr, uid, ids, context=context):
880
 
            if order.order_type == 'direct':
881
 
                self.write(cr, uid, order.id, {'state': 'approved'}, context=context)
882
 
            else:
883
 
                self.write(cr, uid, order.id, {'shipped':1,'state':'approved'}, context=context)
884
 
 
885
 
        return True
886
 
 
887
 
    def confirm_button(self, cr, uid, ids, context=None):
888
 
        '''
889
 
        check the supplier partner type (partner_type)
890
 
 
891
 
        confirmation is needed for internal, inter-mission and inter-section
892
 
 
893
 
        ('internal', 'Internal'), ('section', 'Inter-section'), ('intermission', 'Intermission')
894
 
        '''
895
 
        # data
896
 
        name = _("You're about to confirm a PO that is synchronized and should be consequently confirmed by the supplier (automatically at his equivalent FO confirmation). Are you sure you want to force the confirmation at your level (you won't get the supplier's update)?")
897
 
        model = 'confirm'
898
 
        step = 'default'
899
 
        question = "You're about to confirm a PO that is synchronized and should be consequently confirmed by the supplier (automatically at his equivalent FO confirmation). Are you sure you want to force the confirmation at your level (you won't get the supplier's update)?"
900
 
        clazz = 'purchase.order'
901
 
        func = '_purchase_approve'
902
 
        args = [ids]
903
 
        kwargs = {}
904
 
 
905
 
        for obj in self.browse(cr, uid, ids, context=context):
906
 
            if obj.partner_id.partner_type in ('internal', 'section', 'intermission'):
907
 
                # open the wizard
908
 
                wiz_obj = self.pool.get('wizard')
909
 
                # open the selected wizard
910
 
                res = wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=dict(context, question=question,
911
 
                                                                                                        callback={'clazz': clazz,
912
 
                                                                                                                  'func': func,
913
 
                                                                                                                  'args': args,
914
 
                                                                                                                  'kwargs': kwargs}))
915
 
                return res
916
 
 
917
 
        # otherwise call function directly
918
 
        return self.purchase_approve(cr, uid, ids, context=context)
919
 
 
920
 
    def _purchase_approve(self, cr, uid, ids, context=None):
921
 
        '''
922
 
        interface for call from wizard
923
 
 
924
 
        if called from wizard without opening a new dic -> return close
925
 
        if called from wizard with new dic -> open new wizard
926
 
 
927
 
        if called from button directly, this interface is not called
928
 
        '''
929
 
        res = self.purchase_approve(cr, uid, ids, context=context)
930
 
        if not isinstance(res, dict):
931
 
            return {'type': 'ir.actions.act_window_close'}
932
 
        return res
933
 
 
934
 
    def purchase_approve(self, cr, uid, ids, context=None):
935
 
        '''
936
 
        If the PO is a DPO, check the state of the stock moves
937
 
        '''
938
 
        # Objects
939
 
        sale_line_obj = self.pool.get('sale.order.line')
940
 
        stock_move_obj = self.pool.get('stock.move')
941
 
        wiz_obj = self.pool.get('purchase.order.confirm.wizard')
942
 
 
943
 
        if isinstance(ids, (int, long)):
944
 
            ids = [ids]
945
 
 
946
 
        wf_service = netsvc.LocalService("workflow")
947
 
        move_obj = self.pool.get('stock.move')
948
 
 
949
 
        for order in self.browse(cr, uid, ids, context=context):
950
 
            if not order.delivery_confirmed_date:
951
 
                raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
952
 
 
953
 
            if order.order_type == 'direct':
954
 
                todo = []
955
 
                todo2 = []
956
 
 
957
 
                for line in order.order_line:
958
 
                    if line.procurement_id: todo.append(line.procurement_id.id)
959
 
 
960
 
                if todo:
961
 
                    todo2 = sale_line_obj.search(cr, uid, [('procurement_id', 'in', todo)], context=context)
962
 
 
963
 
                if todo2:
964
 
                    sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
965
 
                    error_moves = []
966
 
                    for move in move_obj.browse(cr, uid, sm_ids, context=context):
967
 
                        backmove_ids = stock_move_obj.search(cr, uid, [('backmove_id', '=', move.id)])
968
 
                        if move.state == 'done':
969
 
                            error_moves.append(move)
970
 
                        if backmove_ids:
971
 
                            for bmove in move_obj.browse(cr, uid, backmove_ids):
972
 
                                error_moves.append(bmove)
973
 
 
974
 
                    if error_moves:
975
 
                        errors = '''You are trying to confirm a Direct Purchase Order.
976
 
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected
977
 
stock moves which are already processed : '''
978
 
                        for m in error_moves:
979
 
                            errors = '%s \n %s' % (errors, '''
980
 
        * Picking : %s - Product : [%s] %s - Product Qty. : %s %s \n''' % (m.picking_id.name, m.product_id.default_code, m.product_id.name, m.product_qty, m.product_uom.name))
981
 
 
982
 
                        errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
983
 
 
984
 
                        wiz_id = wiz_obj.create(cr, uid, {'order_id': order.id,
985
 
                                                          'errors': errors})
986
 
                        return {'type': 'ir.actions.act_window',
987
 
                                'res_model': 'purchase.order.confirm.wizard',
988
 
                                'res_id': wiz_id,
989
 
                                'view_type': 'form',
990
 
                                'view_mode': 'form',
991
 
                                'target': 'new'}
992
 
 
993
 
            # If no errors, validate the DPO
994
 
            wf_service.trg_validate(uid, 'purchase.order', order.id, 'purchase_confirmed_wait', cr)
995
 
 
996
 
        return True
997
 
 
998
 
    def get_so_ids_from_po_ids(self, cr, uid, ids, context=None, sol_ids=[]):
999
 
        '''
1000
 
        receive the list of purchase order ids
1001
 
 
1002
 
        return the list of sale order ids corresponding (through procurement process)
1003
 
        '''
1004
 
        # Some verifications
1005
 
        if not context:
1006
 
            context = {}
1007
 
        if isinstance(ids, (int, long)):
1008
 
            ids = [ids]
1009
 
 
1010
 
        # objects
1011
 
        sol_obj = self.pool.get('sale.order.line')
1012
 
        # sale order list
1013
 
        so_ids = []
1014
 
 
1015
 
        # get the sale order lines
1016
 
        if not sol_ids:
1017
 
            sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
1018
 
        if sol_ids:
1019
 
            # list of dictionaries for each sale order line
1020
 
            datas = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
1021
 
            # we retrieve the list of sale order ids
1022
 
            for data in datas:
1023
 
                if data['order_id'] and data['order_id'][0] not in so_ids:
1024
 
                    so_ids.append(data['order_id'][0])
1025
 
 
1026
 
        for po in self.browse(cr, uid, ids, context=context):
1027
 
            for line in po.order_line:
1028
 
                if line.procurement_id and line.procurement_id.sale_id and line.procurement_id.sale_id.id not in so_ids:
1029
 
                    so_ids.append(line.procurement_id.sale_id.id)
1030
 
 
1031
 
        return so_ids
1032
 
 
1033
 
    def get_sol_ids_from_po_ids(self, cr, uid, ids, context=None):
1034
 
        '''
1035
 
        receive the list of purchase order ids
1036
 
 
1037
 
        return the list of sale order line ids corresponding (through procurement process)
1038
 
        '''
1039
 
        # Some verifications
1040
 
        if not context:
1041
 
            context = {}
1042
 
        if isinstance(ids, (int, long)):
1043
 
            ids = [ids]
1044
 
 
1045
 
        # objects
1046
 
        sol_obj = self.pool.get('sale.order.line')
1047
 
        # procurement ids list
1048
 
        proc_ids = []
1049
 
        # sale order lines list
1050
 
        sol_ids = []
1051
 
 
1052
 
        for po in self.browse(cr, uid, ids, context=context):
1053
 
            for line in po.order_line:
1054
 
                if line.procurement_id:
1055
 
                    proc_ids.append(line.procurement_id.id)
1056
 
        # get the corresponding sale order line list
1057
 
        if proc_ids:
1058
 
            sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
1059
 
        return sol_ids
1060
 
 
1061
 
    # @@@override purchase->purchase.py>purchase_order>wkf_confirm_order
1062
 
    def wkf_confirm_order(self, cr, uid, ids, context=None):
1063
 
        '''
1064
 
        Update the confirmation date of the PO at confirmation.
1065
 
        Check analytic distribution.
1066
 
        '''
1067
 
        # Objects
1068
 
        po_line_obj = self.pool.get('purchase.order.line')
1069
 
 
1070
 
        if context is None:
1071
 
            context = {}
1072
 
 
1073
 
        if isinstance(ids, (int, long)):
1074
 
            ids = [ids]
1075
 
 
1076
 
        todo = []
1077
 
 
1078
 
        for po in self.browse(cr, uid, ids, context=context):
1079
 
            line_error = []
1080
 
            if po.order_type == 'regular':
1081
 
                cr.execute('SELECT line_number FROM purchase_order_line WHERE (price_unit*product_qty < 0.01 OR price_unit = 0.00) AND order_id = %s', (po.id,))
1082
 
                line_errors = cr.dictfetchall()
1083
 
                for l_id in line_errors:
1084
 
                    if l_id not in line_error:
1085
 
                        line_error.append(l_id['line_number'])
1086
 
 
1087
 
            if len(line_error) > 0:
1088
 
                errors = ' / '.join(str(x) for x in line_error)
1089
 
                raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price or 0.00 Subtotal. Lines in exception : %s') % errors)
1090
 
 
1091
 
            # Check if the pricelist of the order is good according to currency of the partner
1092
 
            pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', po.partner_id.partner_type)], context=context)
1093
 
            if po.pricelist_id.id not in pricelist_ids:
1094
 
                raise osv.except_osv(_('Error'), _('The currency used on the order is not compatible with the supplier. Please change the currency to choose a compatible currency.'))
1095
 
 
1096
 
            if not po.split_po and not po.order_line:
1097
 
                raise osv.except_osv(_('Error !'), _('You can not validate a purchase order without Purchase Order Lines.'))
1098
 
 
1099
 
            if po.order_type == 'purchase_list' and po.amount_total == 0:  # UFTP-69
1100
 
                raise osv.except_osv(_('Error'), _('You can not validate a purchase list with a total amount of 0.'))
1101
 
 
1102
 
            for line in po.order_line:
1103
 
                if line.state=='draft':
1104
 
                    todo.append(line.id)
1105
 
 
1106
 
            message = _("Purchase order '%s' is validated.") % (po.name,)
1107
 
            self.log(cr, uid, po.id, message)
1108
 
            # hook for corresponding Fo update
1109
 
            self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
1110
 
 
1111
 
        po_line_obj.action_confirm(cr, uid, todo, context)
1112
 
 
1113
 
        self.write(cr, uid, ids, {'state' : 'confirmed',
1114
 
                                  'validator' : uid,
1115
 
                                  'date_confirm': strftime('%Y-%m-%d')}, context=context)
1116
 
 
1117
 
        self.check_analytic_distribution(cr, uid, ids, context=context)
1118
 
 
1119
 
        return True
1120
 
 
1121
 
    def common_code_from_wkf_approve_order(self, cr, uid, ids, context=None):
1122
 
        '''
1123
 
        delivery confirmed date at po level is mandatory
1124
 
        update corresponding date at line level if needed.
1125
 
        Check analytic distribution
1126
 
        Check that no line have a 0 price unit.
1127
 
        '''
1128
 
        # Objects
1129
 
        po_line_obj = self.pool.get('purchase.order.line')
1130
 
 
1131
 
        if context is None:
1132
 
            context = {}
1133
 
 
1134
 
        if isinstance(ids, (int, long)):
1135
 
            ids = [ids]
1136
 
 
1137
 
        # Check analytic distribution
1138
 
        self.check_analytic_distribution(cr, uid, ids, context=context)
1139
 
        for po in self.browse(cr, uid, ids, context=context):
1140
 
            # prepare some values
1141
 
            is_regular = po.order_type == 'regular' # True if order_type is regular, else False
1142
 
            line_error = []
1143
 
            # msf_order_date checks
1144
 
            if po.state == 'approved' and not po.delivery_confirmed_date:
1145
 
                raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
1146
 
            # for all lines, if the confirmed date is not filled, we copy the header value
1147
 
            if is_regular:
1148
 
                cr.execute('SELECT line_number FROM purchase_order_line WHERE (price_unit*product_qty < 0.01 OR price_unit = 0.00) AND order_id = %s', (po.id,))
1149
 
                line_errors = cr.dictfetchall()
1150
 
                for l_id in line_errors:
1151
 
                    if l_id not in line_error:
1152
 
                        line_error.append(l_id['line_number'])
1153
 
 
1154
 
            if len(line_error) > 0:
1155
 
                errors = ' / '.join(str(x) for x in line_error)
1156
 
                raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price or 0.00 Subtotal. Lines in exception : %s') % errors)
1157
 
 
1158
 
            lines_to_update = po_line_obj.search(
1159
 
                cr, uid,
1160
 
                [('order_id', '=', po.id), ('confirmed_delivery_date', '=', False)],
1161
 
                context=context)
1162
 
 
1163
 
            po_line_obj.write(cr, uid, lines_to_update, {'confirmed_delivery_date': po.delivery_confirmed_date}, context=context)
1164
 
        # MOVE code for COMMITMENT into wkf_approve_order
1165
 
        return True
1166
 
 
1167
 
    def create_extra_lines_on_fo(self, cr, uid, ids, context=None):
1168
 
        '''
1169
 
        Creates FO/IR lines according to PO extra lines
1170
 
        '''
1171
 
        # Objects
1172
 
        sol_obj = self.pool.get('sale.order.line')
1173
 
        so_obj = self.pool.get('sale.order')
1174
 
        ad_obj = self.pool.get('analytic.distribution')
1175
 
        proc_obj = self.pool.get('procurement.order')
1176
 
        pick_obj = self.pool.get('stock.picking')
1177
 
        move_obj = self.pool.get('stock.move')
1178
 
 
1179
 
        if context is None:
1180
 
            context = {}
1181
 
 
1182
 
        if isinstance(ids, (int, long)):
1183
 
            ids = [ids]
1184
 
 
1185
 
        lines = []
1186
 
        sol_ids = set()
1187
 
        for order in self.browse(cr, uid, ids, context=context):
1188
 
            for l in order.order_line:
1189
 
                link_so_id = l.link_so_id and l.link_so_id.state in ('sourced', 'progress', 'manual')
1190
 
                if link_so_id and (not l.procurement_id or not l.procurement_id.sale_id):
1191
 
                    lines.append(l)
1192
 
 
1193
 
        for l in lines:
1194
 
            # Copy the AD
1195
 
            new_distrib = False
1196
 
            if l.analytic_distribution_id:
1197
 
                new_distrib = ad_obj.copy(cr, uid, l.analytic_distribution_id.id, {}, context=context)
1198
 
            elif not l.analytic_distribution_id and l.order_id and l.order_id.analytic_distribution_id:
1199
 
                new_distrib = ad_obj.copy(cr, uid, l.order_id.analytic_distribution_id.id, {}, context=context)
1200
 
            # Creates the FO lines
1201
 
            tmp_sale_context = context.get('sale_id')
1202
 
            # create new line in FOXXXX-Y
1203
 
            context['sale_id'] = l.link_so_id.id
1204
 
            vals = {'order_id': l.link_so_id.id,
1205
 
                    'product_id': l.product_id.id,
1206
 
                    'product_uom': l.product_uom.id,
1207
 
                    'product_uom_qty': l.product_qty,
1208
 
                    'price_unit': l.price_unit,
1209
 
                    'procurement_id': l.procurement_id and l.procurement_id.id or False,
1210
 
                    'type': 'make_to_order',
1211
 
                    'supplier': l.order_id.partner_id.id,
1212
 
                    'analytic_distribution_id': new_distrib,
1213
 
                    'created_by_po': not l.order_id.rfq_ok and l.order_id.id or False,
1214
 
                    'created_by_po_line': not l.order_id.rfq_ok and l.id or False,
1215
 
                    'created_by_rfq': l.order_id.rfq_ok and l.order_id.id or False,
1216
 
                    'created_by_rfq_line': l.order_id.rfq_ok and l.id or False,
1217
 
                    'po_cft': l.order_id.rfq_ok and 'rfq' or 'po',
1218
 
                    'sync_sourced_origin': l.instance_sync_order_ref and l.instance_sync_order_ref.name or False,
1219
 
                    #'is_line_split': l.is_line_split,
1220
 
                    'name': '[%s] %s' % (l.product_id.default_code, l.product_id.name)}
1221
 
 
1222
 
            new_line_id = sol_obj.create(cr, uid, vals, context=context)
1223
 
 
1224
 
            # Put the sale_id in the procurement order
1225
 
            if l.procurement_id:
1226
 
                proc_obj.write(cr, uid, [l.procurement_id.id], {
1227
 
                    'sale_id': l.link_so_id.id,
1228
 
                    'purchase_id': l.order_id.id,
1229
 
                }, context=context)
1230
 
            # Create new line in FOXXXX (original FO)
1231
 
            if l.link_so_id.original_so_id_sale_order:
1232
 
                context['sale_id'] = l.link_so_id.original_so_id_sale_order.id
1233
 
                vals.update({'order_id': l.link_so_id.original_so_id_sale_order.id,
1234
 
                             'state': 'done'})
1235
 
                sol_obj.create(cr, uid, vals, context=context)
1236
 
            context['sale_id'] = tmp_sale_context
1237
 
 
1238
 
            # If the order is an Internal request with External location, create a new
1239
 
            # stock move on the picking ticket (if not closed)
1240
 
            # Get move data and create the move
1241
 
            if l.link_so_id.procurement_request and l.link_so_id.location_requestor_id.usage == 'customer' and l.product_id.type == 'product':
1242
 
                # Get OUT linked to IR
1243
 
                pick_to_confirm = None
1244
 
                out_ids = pick_obj.search(cr, uid, [
1245
 
                    ('sale_id', '=', l.link_so_id.id),
1246
 
                    ('type', '=', 'out'),
1247
 
                    ('state', 'in', ['draft', 'confirmed', 'assigned']),
1248
 
                ], context=context)
1249
 
                if not out_ids:
1250
 
                    picking_data = so_obj._get_picking_data(cr, uid, l.link_so_id)
1251
 
                    out_ids = [pick_obj.create(cr, uid, picking_data, context=context)]
1252
 
                    pick_to_confirm = out_ids
1253
 
 
1254
 
                ir_line = sol_obj.browse(cr, uid, new_line_id, context=context)
1255
 
                move_data = so_obj._get_move_data(cr, uid, l.link_so_id, ir_line, out_ids[0], context=context)
1256
 
                move_obj.create(cr, uid, move_data, context=context)
1257
 
 
1258
 
                if pick_to_confirm:
1259
 
                    pick_obj.action_confirm(cr, uid, pick_to_confirm, context=context)
1260
 
 
1261
 
            sol_ids.add(l.link_so_id.id)
1262
 
 
1263
 
        if sol_ids:
1264
 
            so_obj.action_ship_proc_create(cr, uid, list(sol_ids), context=context)
1265
 
 
1266
 
        return True
1267
 
 
1268
 
    def wkf_confirm_wait_order(self, cr, uid, ids, context=None):
1269
 
        """
1270
 
        Checks:
1271
 
        1/ if all purchase line could take an analytic distribution
1272
 
        2/ if a commitment voucher should be created after PO approbation
1273
 
 
1274
 
        _> originally in purchase.py from analytic_distribution_supply
1275
 
 
1276
 
        Checks if the Delivery Confirmed Date has been filled
1277
 
 
1278
 
        _> originally in order_dates.py from msf_order_date
1279
 
        """
1280
 
        # Some verifications
1281
 
        if not context:
1282
 
            context = {}
1283
 
        if isinstance(ids, (int, long)):
1284
 
            ids = [ids]
1285
 
 
1286
 
        # objects
1287
 
        sol_obj = self.pool.get('sale.order.line')
1288
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
1289
 
        so_obj =  self.pool.get('sale.order')
1290
 
 
1291
 
        # Create extra lines on the linked FO/IR
1292
 
        self.create_extra_lines_on_fo(cr, uid, ids, context=context)
1293
 
 
1294
 
        # code from wkf_approve_order
1295
 
        self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
1296
 
        # set the state of purchase order to confirmed_wait
1297
 
        self.write(cr, uid, ids, {'state': 'confirmed_wait'}, context=context)
1298
 
        sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
1299
 
 
1300
 
        # corresponding sale order
1301
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context, sol_ids=sol_ids)
1302
 
        # UF-2509: so_ids is a list, not an int
1303
 
        exp_sol_ids = exp_sol_obj.search(cr, uid, [('order_id', 'in', so_ids)], context=context)
1304
 
        # from so, list corresponding po
1305
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1306
 
        for exp_sol in exp_sol_obj.browse(cr, uid, exp_sol_ids, context=context):
1307
 
            # UFTP-335: Added a check in if to avoid False value being taken
1308
 
            if exp_sol.po_id and exp_sol.po_id.id not in all_po_ids:
1309
 
                all_po_ids.append(exp_sol.po_id.id)
1310
 
        list_po_name = ', '.join([linked_po.name for linked_po in self.browse(cr, uid, all_po_ids, context) if linked_po.id != ids[0]])
1311
 
        self.log(cr, uid, ids[0], _("The order %s is in confirmed (waiting) state and will be confirmed once the related orders [%s] would have been confirmed"
1312
 
                                 ) % (self.read(cr, uid, ids, ['name'])[0]['name'], list_po_name))
1313
 
        # sale order lines with modified state
1314
 
        if sol_ids:
1315
 
            sol_obj.write(cr, uid, sol_ids, {'state': 'confirmed'}, context=context)
1316
 
 
1317
 
        # !!BEWARE!! we must update the So lines before any writing to So objects
1318
 
        for po in self.browse(cr, uid, ids, context=context):
1319
 
            # hook for corresponding Fo update
1320
 
            context['wait_order'] = True
1321
 
            self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po, so_ids=so_ids)
1322
 
            del context['wait_order']
1323
 
 
1324
 
        return True
1325
 
 
1326
 
    def compute_confirmed_delivery_date(self, cr, uid, ids, confirmed, prep_lt, ship_lt, est_transport_lead_time, db_date_format, context=None):
1327
 
        '''
1328
 
        compute the confirmed date
1329
 
 
1330
 
        confirmed must be string
1331
 
        return string corresponding to database format
1332
 
        '''
1333
 
        assert type(confirmed) == str
1334
 
        confirmed = datetime.strptime(confirmed, db_date_format)
1335
 
        confirmed = confirmed + relativedelta(days=prep_lt or 0)
1336
 
        confirmed = confirmed + relativedelta(days=ship_lt or 0)
1337
 
        confirmed = confirmed + relativedelta(days=est_transport_lead_time or 0)
1338
 
        confirmed = confirmed.strftime(db_date_format)
1339
 
 
1340
 
        return confirmed
1341
 
 
1342
 
    def _hook_confirm_order_update_corresponding_so(self, cr, uid, ids, context=None, *args, **kwargs):
1343
 
        '''
1344
 
        Add a hook to update correspondingn so
1345
 
        '''
1346
 
        # Some verifications
1347
 
        if context is None:
1348
 
            context = {}
1349
 
        if isinstance(ids, (int, long)):
1350
 
            ids = [ids]
1351
 
 
1352
 
        # objects
1353
 
        po = kwargs['po']
1354
 
        so_ids= kwargs.get('so_ids')
1355
 
        pol_obj = self.pool.get('purchase.order.line')
1356
 
        so_obj = self.pool.get('sale.order')
1357
 
        sol_obj = self.pool.get('sale.order.line')
1358
 
        move_obj = self.pool.get('stock.move')
1359
 
        proc_obj = self.pool.get('procurement.order')
1360
 
        pick_obj = self.pool.get('stock.picking')
1361
 
        uom_obj = self.pool.get('product.uom')
1362
 
        ad_obj = self.pool.get('analytic.distribution')
1363
 
        date_tools = self.pool.get('date.tools')
1364
 
        fields_tools = self.pool.get('fields.tools')
1365
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1366
 
        wf_service = netsvc.LocalService("workflow")
1367
 
 
1368
 
        # update corresponding fo if exist
1369
 
        if so_ids is None:
1370
 
            so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1371
 
        ctx = context.copy()
1372
 
        ctx['no_store_function'] = ['sale.order.line']
1373
 
        store_to_call = []
1374
 
        picks_to_check = {}
1375
 
        if so_ids:
1376
 
            # date values
1377
 
            ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
1378
 
            prep_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='preparation_lead_time', context=context)
1379
 
 
1380
 
            for line in po.order_line:
1381
 
                # get the corresponding so line
1382
 
                sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
1383
 
                if sol_ids:
1384
 
                    store_to_call += sol_ids
1385
 
 
1386
 
                    sol = sol_obj.browse(cr, uid, sol_ids[0], context=context)
1387
 
                    so = sol.order_id
1388
 
                    # do not update Internal Requests with internal requestor location
1389
 
                    if so and so.procurement_request and so.location_requestor_id.usage != 'customer':
1390
 
                        continue
1391
 
 
1392
 
                    line_confirmed = False
1393
 
                    # compute confirmed date for line
1394
 
                    if line.confirmed_delivery_date:
1395
 
                        line_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, line.confirmed_delivery_date,
1396
 
                                                                              prep_lt, ship_lt, so.est_transport_lead_time,
1397
 
                                                                              db_date_format, context=context)
1398
 
 
1399
 
                    # we update the corresponding sale order line
1400
 
                    # {sol: pol}
1401
 
                    # compute the price_unit value - we need to specify the date
1402
 
                    date_context = {'date': po.date_order}
1403
 
 
1404
 
                    # convert from currency of pol to currency of sol
1405
 
                    price_unit_converted = self.pool.get('res.currency').compute(cr, uid, line.currency_id.id,
1406
 
                                                                                 sol.currency_id.id, line.price_unit or 0.0,
1407
 
                                                                                 round=False, context=date_context)
1408
 
 
1409
 
                    if so.order_type == 'regular' and price_unit_converted < 0.00001:
1410
 
                        price_unit_converted = 0.00001
1411
 
 
1412
 
                    line_qty = line.product_qty
1413
 
                    if line.procurement_id:
1414
 
                        other_po_lines = pol_obj.search(cr, uid, [
1415
 
                            ('procurement_id', '=', line.procurement_id.id),
1416
 
                            ('id', '!=', line.id),
1417
 
                            '|', ('order_id.id', '=', line.order_id.id), ('order_id.state', 'in', ['sourced', 'approved']),
1418
 
                        ], context=context)
1419
 
                        for opl in pol_obj.browse(cr, uid, other_po_lines, context=context):
1420
 
                            if opl.product_uom.id != line.product_uom.id:
1421
 
                                line_qty += uom_obj._compute_qty(cr, uid, opl.product_uom.id, opl.product_qty, line.product_uom.id)
1422
 
                            else:
1423
 
                                line_qty += opl.product_qty
1424
 
 
1425
 
                    fields_dic = {'product_id': line.product_id and line.product_id.id or False,
1426
 
                                  'name': line.name,
1427
 
                                  'default_name': line.default_name,
1428
 
                                  'default_code': line.default_code,
1429
 
                                  'product_uom_qty': line_qty,
1430
 
                                  'product_uom': line.product_uom and line.product_uom.id or False,
1431
 
                                  'product_uos_qty': line_qty,
1432
 
                                  'product_uos': line.product_uom and line.product_uom.id or False,
1433
 
                                  'price_unit': price_unit_converted,
1434
 
                                  'nomenclature_description': line.nomenclature_description,
1435
 
                                  'nomenclature_code': line.nomenclature_code,
1436
 
                                  'comment': line.comment,
1437
 
                                  'nomen_manda_0': line.nomen_manda_0 and line.nomen_manda_0.id or False,
1438
 
                                  'nomen_manda_1': line.nomen_manda_1 and line.nomen_manda_1.id or False,
1439
 
                                  'nomen_manda_2': line.nomen_manda_2 and line.nomen_manda_2.id or False,
1440
 
                                  'nomen_manda_3': line.nomen_manda_3 and line.nomen_manda_3.id or False,
1441
 
                                  'nomen_sub_0': line.nomen_sub_0 and line.nomen_sub_0.id or False,
1442
 
                                  'nomen_sub_1': line.nomen_sub_1 and line.nomen_sub_1.id or False,
1443
 
                                  'nomen_sub_2': line.nomen_sub_2 and line.nomen_sub_2.id or False,
1444
 
                                  'nomen_sub_3': line.nomen_sub_3 and line.nomen_sub_3.id or False,
1445
 
                                  'nomen_sub_4': line.nomen_sub_4 and line.nomen_sub_4.id or False,
1446
 
                                  'nomen_sub_5': line.nomen_sub_5 and line.nomen_sub_5.id or False,
1447
 
                                  'confirmed_delivery_date': line_confirmed,
1448
 
                                  #'is_line_split': line.is_line_split,
1449
 
                                  }
1450
 
                    """
1451
 
                    UFTP-336: Update the analytic distribution at FO line when
1452
 
                              PO is confirmed if lines are created at tender
1453
 
                              or RfQ because there is no AD on FO line.
1454
 
                    """
1455
 
                    if sol.created_by_tender or sol.created_by_rfq:
1456
 
                        new_distrib = False
1457
 
                        if line.analytic_distribution_id:
1458
 
                            new_distrib = ad_obj.copy(cr, uid, line.analytic_distribution_id.id, {}, context=context)
1459
 
                        elif not line.analytic_distribution_id and line.order_id and line.order_id.analytic_distribution_id:
1460
 
                            new_distrib = ad_obj.copy(cr, uid, line.order_id.analytic_distribution_id.id, {}, context=context)
1461
 
 
1462
 
                        fields_dic['analytic_distribution_id'] = new_distrib
1463
 
 
1464
 
                    # write the line
1465
 
                    sol_obj.write(cr, uid, sol_ids, fields_dic, context=ctx)
1466
 
 
1467
 
                    if so.procurement_request and so.location_requestor_id.usage == 'customer' \
1468
 
                                              and line.procurement_id.move_id \
1469
 
                                              and not line.procurement_id.move_id.processed_stock_move:
1470
 
                        out_move_id = line.procurement_id.move_id
1471
 
                        # If there is a non-stockable or service product, remove the OUT
1472
 
                        # stock move and update the stock move on the procurement
1473
 
                        if context.get('wait_order') and line.product_id.type in ('service', 'service_recep', 'consu') and out_move_id.picking_id:
1474
 
                            out_pick_id = out_move_id.picking_id.id
1475
 
                            proc_obj.write(cr, uid, [line.procurement_id.id], {
1476
 
                                'move_id': line.move_dest_id.id,
1477
 
                            }, context=context)
1478
 
                            move_obj.write(cr, uid, [out_move_id.id], {'state': 'draft'})
1479
 
                            if out_pick_id:
1480
 
                                picks_to_check.setdefault(out_pick_id, [])
1481
 
                                picks_to_check[out_pick_id].append(out_move_id.id)
1482
 
                            else:
1483
 
                                move_obj.unlink(cr, uid, [out_move_id.id], context=context)
1484
 
 
1485
 
                            continue
1486
 
 
1487
 
                        minus_qty = 0.00
1488
 
                        bo_moves = []
1489
 
                        if out_move_id.picking_id and out_move_id.picking_id.backorder_id:
1490
 
                            bo_moves = move_obj.search(cr, uid, [
1491
 
                                ('picking_id', '=', out_move_id.picking_id.backorder_id.id),
1492
 
                                ('sale_line_id', '=', out_move_id.sale_line_id.id),
1493
 
                                ('state', '=', 'done'),
1494
 
                            ], context=context)
1495
 
                            while bo_moves:
1496
 
                                boms = move_obj.browse(cr, uid, bo_moves, context=context)
1497
 
                                bo_moves = []
1498
 
                                for bom in boms:
1499
 
                                    if bom.product_uom.id != out_move_id.product_uom.id:
1500
 
                                        minus_qty += uom_obj._compute_qty(cr, uid, bom.product_uom.id, bom.product_qty, out_move_id.product_uom.id)
1501
 
                                    else:
1502
 
                                        minus_qty += bom.product_qty
1503
 
                                        if bom.picking_id and bom.picking_id.backorder_id:
1504
 
                                            bo_moves.extend(move_obj.search(cr, uid, [
1505
 
                                                ('picking_id', '=', bom.picking_id.backorder_id.id),
1506
 
                                                ('sale_line_id', '=', bom.sale_line_id.id),
1507
 
                                                ('state', '=', 'done'),
1508
 
                                            ], context=context))
1509
 
 
1510
 
                        if out_move_id.product_uom.id != line.product_uom.id:
1511
 
                            minus_qty = uom_obj._compute_qty(cr, uid, out_move_id.product_uom.id, minus_qty, line.product_uom.id)
1512
 
 
1513
 
                        if out_move_id.state == 'assigned':
1514
 
                            move_obj.cancel_assign(cr, uid, [out_move_id.id])
1515
 
                        elif out_move_id.state in ('cancel', 'done'):
1516
 
                            continue
1517
 
                        else:
1518
 
                            move_dic = {
1519
 
                                'name': line.name,
1520
 
                                'product_uom': line.product_uom and line.product_uom.id or False,
1521
 
                                'product_uos': line.product_uom and line.product_uom.id or False,
1522
 
                                'product_qty': line_qty - minus_qty,
1523
 
                                'product_uos_qty': line_qty - minus_qty,
1524
 
                            }
1525
 
                            if line.product_id:
1526
 
                                move_dic['product_id'] = line.product_id.id
1527
 
                            if line.product_uom:
1528
 
                                move_dic.update({
1529
 
                                    'product_uom': line.product_uom.id,
1530
 
                                    'product_uos': line.product_uom.id,
1531
 
                                })
1532
 
                            move_obj.write(cr, uid, [out_move_id.id], move_dic, context=context)
1533
 
 
1534
 
            if store_to_call:
1535
 
                sol_obj._call_store_function(cr, uid, store_to_call, keys=None, bypass=False, context=context)
1536
 
            # compute so dates -- only if we get a confirmed value, because rts is mandatory on So side
1537
 
            # update after lines update, as so write triggers So workflow, and we dont want the Out document
1538
 
            # to be created with old So datas
1539
 
            if po.delivery_confirmed_date:
1540
 
                for so in so_obj.browse(cr, uid, so_ids, context=context):
1541
 
                    # Fo rts = Po confirmed date + prep_lt
1542
 
                    delivery_confirmed_date = datetime.strptime(po.delivery_confirmed_date, db_date_format)
1543
 
                    so_rts = delivery_confirmed_date + relativedelta(days=prep_lt or 0)
1544
 
                    so_rts = so_rts.strftime(db_date_format)
1545
 
 
1546
 
                    # Fo confirmed date = confirmed date + prep_lt + ship_lt + transport_lt
1547
 
                    so_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, po.delivery_confirmed_date,
1548
 
                                                                        prep_lt, ship_lt, so.est_transport_lead_time,
1549
 
                                                                        db_date_format, context=context)
1550
 
                    # write data to so
1551
 
                    so_obj.write(cr, uid, [so.id], {'delivery_confirmed_date': so_confirmed,
1552
 
                                                   'ready_to_ship_date': so_rts}, context=context)
1553
 
                    wf_service.trg_write(uid, 'sale.order', so.id, cr)
1554
 
 
1555
 
        for out_pick_id, out_move_ids in picks_to_check.iteritems():
1556
 
            full_out = pick_obj.read(cr, uid, out_pick_id, ['move_lines'])['move_lines']
1557
 
            for om_id in out_move_ids:
1558
 
                if om_id in full_out:
1559
 
                    full_out.remove(om_id)
1560
 
 
1561
 
            if out_pick_id and not full_out:
1562
 
                pick_obj.write(cr, uid, [out_pick_id], {'state': 'draft'}, context=context)
1563
 
                move_obj.unlink(cr, uid, out_move_ids)
1564
 
                pick_obj.unlink(cr, uid, out_pick_id)
1565
 
            else:
1566
 
                move_obj.unlink(cr, uid, out_move_ids)
1567
 
 
1568
 
        return True
1569
 
 
1570
 
    def check_if_product(self, cr, uid, ids, context=None):
1571
 
        """
1572
 
        Check if all line have a product before confirming the Purchase Order
1573
 
        """
1574
 
        if isinstance(ids, (int, long)):
1575
 
            ids = [ids]
1576
 
 
1577
 
        for po in self.browse(cr, uid, ids, context=context):
1578
 
            if po.order_line:
1579
 
                for line in po.order_line:
1580
 
                    if not line.product_id:
1581
 
                        raise osv.except_osv(_('Error !'), _('You should have a product on all Purchase Order lines to be able to confirm the Purchase Order.') )
1582
 
        return True
1583
 
 
1584
 
    def sourcing_document_state(self, cr, uid, ids, context=None):
1585
 
        """
1586
 
        Returns all documents that are in the sourcing for a given PO 
1587
 
        """
1588
 
        if not context:
1589
 
            context = {}
1590
 
 
1591
 
        if isinstance(ids, (int, long)):
1592
 
            ids = [ids]
1593
 
 
1594
 
        sol_obj = self.pool.get('sale.order.line')
1595
 
        so_obj = self.pool.get('sale.order')
1596
 
 
1597
 
        # corresponding sale order
1598
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1599
 
        # from so, list corresponding po
1600
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1601
 
 
1602
 
        # from listed po, list corresponding so
1603
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1604
 
 
1605
 
        all_sol_not_confirmed_ids = []
1606
 
        # if we have sol_ids, we are treating a po which is make_to_order from sale order
1607
 
        if all_so_ids:
1608
 
            all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
1609
 
                                                                 ('type', '=', 'make_to_order'),
1610
 
                                                                 ('product_id', '!=', False),
1611
 
                                                                 ('procurement_id.state', '!=', 'cancel'),
1612
 
                                                                 ('state', 'not in', ['confirmed', 'done'])], context=context)
1613
 
 
1614
 
        return so_ids, all_po_ids, all_so_ids, all_sol_not_confirmed_ids
1615
 
 
1616
 
 
1617
 
    def all_po_confirmed(self, cr, uid, ids, context=None):
1618
 
        '''
1619
 
        condition for the po to leave the act_confirmed_wait state
1620
 
 
1621
 
        if the po is from scratch (no procurement), or from replenishment mechanism (procurement but no sale order line)
1622
 
        the method will return True and therefore the po workflow is not blocked
1623
 
 
1624
 
        only 'make_to_order' sale order lines are checked, we dont care on state of 'make_to_stock' sale order line
1625
 
        _> anyway, thanks to Fo split, make_to_stock and make_to_order so lines are separated in different sale orders
1626
 
        '''
1627
 
        # Some verifications
1628
 
        if not context:
1629
 
            context = {}
1630
 
        if isinstance(ids, (int, long)):
1631
 
            ids = [ids]
1632
 
 
1633
 
        # objects
1634
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
1635
 
        sol_obj = self.pool.get('sale.order.line')
1636
 
        so_obj = self.pool.get('sale.order')
1637
 
 
1638
 
        # corresponding sale order
1639
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1640
 
        # from so, list corresponding po
1641
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1642
 
 
1643
 
        # from listed po, list corresponding so
1644
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1645
 
        # if we have sol_ids, we are treating a po which is make_to_order from sale order
1646
 
        if all_so_ids:
1647
 
            # we retrieve the list of ids of all sale order line if type 'make_to_order' with state != 'confirmed'
1648
 
            # with product_id (if no product id, no procurement, no po, so should not be taken into account)
1649
 
            # in case of grouped po, multiple Fo depend on this po, all Po of these Fo need to be completed
1650
 
            # and all Fo will be confirmed together. Because IN of grouped Po need corresponding OUT document of all Fo
1651
 
            # internal request are automatically 'confirmed'
1652
 
            # not take done into account, because IR could be done as they are confirmed before the Po are all done
1653
 
            # see video in uf-1050 for detail
1654
 
            all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
1655
 
                                                                 ('type', '=', 'make_to_order'),
1656
 
                                                                 ('product_id', '!=', False),
1657
 
                                                                 ('procurement_id.state', '!=', 'cancel'),
1658
 
                                                                 ('state', 'not in', ['confirmed', 'done'])], context=context)
1659
 
 
1660
 
            all_exp_sol_not_confirmed_ids = exp_sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids)], context=context)
1661
 
 
1662
 
            # if any lines exist, we return False
1663
 
            if all_sol_not_confirmed_ids or all_exp_sol_not_confirmed_ids:
1664
 
                return False
1665
 
 
1666
 
        return True
1667
 
 
1668
 
    def wkf_confirm_trigger(self, cr, uid, ids, context=None):
1669
 
        '''
1670
 
        trigger corresponding so then po
1671
 
        '''
1672
 
        # Some verifications
1673
 
        if not context:
1674
 
            context = {}
1675
 
        if isinstance(ids, (int, long)):
1676
 
            ids = [ids]
1677
 
 
1678
 
        # objects
1679
 
        so_obj = self.pool.get('sale.order')
1680
 
        wf_service = netsvc.LocalService("workflow")
1681
 
 
1682
 
        # corresponding sale order
1683
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1684
 
        # from so, list corresponding po first level
1685
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1686
 
        # from listed po, list corresponding so
1687
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1688
 
        # from all so, list all corresponding po second level
1689
 
        all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
1690
 
 
1691
 
        not_confirmed_po = self.search(cr, uid, [
1692
 
            ('id', 'not in', all_po_for_all_so_ids),
1693
 
            ('state', '=', 'confirmed_wait'),
1694
 
        ], context=context)
1695
 
 
1696
 
        # we trigger all the corresponding sale order -> test_lines is called on these so
1697
 
        for so_id in all_so_ids:
1698
 
            wf_service.trg_write(uid, 'sale.order', so_id, cr)
1699
 
 
1700
 
        # we trigger pos of all sale orders -> all_po_confirm is called on these po
1701
 
        for po_id in all_po_for_all_so_ids:
1702
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1703
 
 
1704
 
        for po_id in not_confirmed_po:
1705
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1706
 
 
1707
 
        return True
1708
 
 
 
246
            return super(purchase_order, self)._hook_confirm_order_message(cr, uid, context, args, kwargs)
 
247
    
1709
248
    def wkf_approve_order(self, cr, uid, ids, context=None):
1710
249
        '''
1711
250
        Checks if the invoice should be create from the purchase order
1714
253
        '''
1715
254
        line_obj = self.pool.get('purchase.order.line')
1716
255
        move_obj = self.pool.get('stock.move')
1717
 
        uf_config = self.pool.get('unifield.setup.configuration')
1718
256
        wf_service = netsvc.LocalService("workflow")
1719
 
 
 
257
        
1720
258
        if isinstance(ids, (int, long)):
1721
259
            ids = [ids]
1722
 
 
1723
 
        # duplicated code with wkf_confirm_wait_order because of backward compatibility issue with yml tests for dates,
1724
 
        # which doesnt execute wkf_confirm_wait_order (null value in column "date_expected" violates not-null constraint for stock.move otherwise)
1725
 
        # msf_order_date checks
1726
 
        self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
1727
 
 
1728
 
        setup = uf_config.get_config(cr, uid)
1729
 
 
 
260
            
 
261
        todo = []
 
262
        todo2 = []
 
263
        todo3 = []
1730
264
        for order in self.browse(cr, uid, ids):
1731
 
            if order.order_type == 'purchase_list' and order.amount_total == 0:  # UFTP-69
1732
 
                # total amount could be set to 0 after it was Validated
1733
 
                # or no lines
1734
 
                # (after wkf_confirm_order total amount check)
1735
 
                raise osv.except_osv(_('Error'), _('You can not confirm a purchase list with a total amount of 0.'))
1736
 
 
1737
 
            # Create commitments for each PO only if po is "from picking"
1738
 
            # UTP-114: No Commitment Voucher on PO that are 'purchase_list'!
1739
 
            if (order.invoice_method in ['picking', 'order'] and not order.from_yml_test and order.order_type not in ['in_kind', 'purchase_list'] and order.partner_id.partner_type != 'intermission') or (order.invoice_method == 'manual' and order.order_type == 'direct' and order.partner_id.partner_type == 'esc'):
1740
 
                # UTP-827: no commitment if they are imported for ESC partners
1741
 
                if not (order.partner_id.partner_type == 'esc' and setup.import_commitments):
1742
 
                    self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
1743
 
            todo = []
1744
 
            todo2 = []
1745
 
            todo3 = []
1746
 
            todo4 = {}
1747
 
            if order.partner_id.partner_type in ('internal', 'esc') and order.order_type == 'regular' or \
1748
 
                         order.order_type in ['donation_exp', 'donation_st', 'loan']:
 
265
            if order.partner_id.partner_type == 'internal' and order.order_type == 'regular' or \
 
266
                         order.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']:
1749
267
                self.write(cr, uid, [order.id], {'invoice_method': 'manual'})
1750
268
                line_obj.write(cr, uid, [x.id for x in order.order_line], {'invoiced': 1})
1751
269
 
1752
270
            message = _("Purchase order '%s' is confirmed.") % (order.name,)
1753
271
            self.log(cr, uid, order.id, message)
1754
 
 
 
272
            
1755
273
            if order.order_type == 'direct':
1756
 
                if order.partner_id.partner_type != 'esc':
1757
 
                    self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
 
274
                self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
1758
275
                for line in order.order_line:
1759
 
                    if line.procurement_id:
1760
 
                        todo.append(line.procurement_id.id)
1761
 
                        todo4.update({line.procurement_id.id: line.id})
1762
 
 
1763
 
            if todo:
1764
 
                todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
1765
 
 
1766
 
            if todo2:
1767
 
                sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
1768
 
                self.pool.get('stock.move').action_confirm(cr, uid, sm_ids, context=context)
1769
 
                stock_location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')[1]
1770
 
                cross_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
1771
 
                non_stock_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1772
 
                for move in move_obj.browse(cr, uid, sm_ids, context=context):
1773
 
                    # Reset the location_id to Stock
1774
 
                    location_id = stock_location_id
1775
 
                    # Search if this move has been processed
1776
 
                    backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
1777
 
                    if move.state != 'done' and not backmove_ids and not move.backmove_id:
1778
 
                        if move.product_id.type in ('service', 'service_recep'):
1779
 
                            location_id = cross_id
1780
 
                        elif move.product_id.type == 'consu':
1781
 
                            location_id = non_stock_id
1782
 
                        move_obj.write(cr, uid, [move.id], {'dpo_id': order.id,
1783
 
                                                            'state': 'done',
1784
 
                                                            'dpo_line_id': todo4.get(move.sale_line_id.procurement_id.id, False),
1785
 
                                                            'location_id': location_id,
1786
 
                                                            'location_dest_id': location_id,
1787
 
                                                            'date': strftime('%Y-%m-%d %H:%M:%S')}, context=context)
1788
 
                        wf_service.trg_trigger(uid, 'stock.move', move.id, cr)
1789
 
                        if move.picking_id:
1790
 
                            all_move_closed = True
1791
 
                            # Check if the picking should be updated
1792
 
                            if move.picking_id.subtype == 'picking':
1793
 
                                for m in move.picking_id.move_lines:
1794
 
                                    if m.id not in sm_ids and m.state != 'done':
1795
 
                                        all_move_closed = False
1796
 
                            # If all stock moves of the picking is done, trigger the workflow
1797
 
                            if all_move_closed:
1798
 
                                todo3.append(move.picking_id.id)
1799
 
 
1800
 
            if todo3:
1801
 
                for pick_id in todo3:
1802
 
                    wf_service.trg_validate(uid, 'stock.picking', pick_id, 'button_confirm', cr)
1803
 
                    wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
1804
 
 
1805
 
        # @@@override@purchase.purchase.order.wkf_approve_order
1806
 
        self.write(cr, uid, ids, {'state': 'approved', 'date_approve': strftime('%Y-%m-%d')})
1807
 
        return True
1808
 
 
1809
 
    def need_counterpart(self, cr, uid, ids, context=None):
1810
 
        res = False
1811
 
        for po in self.browse(cr, uid, ids, context=context):
1812
 
            if po.order_type == 'loan' and not po.loan_id and not po.is_a_counterpart and po.partner_id.partner_type not in ('internal', 'intermission'):
1813
 
                res = True
1814
 
 
1815
 
        return res
1816
 
 
1817
 
    def go_to_loan_done(self, cr, uid, ids, context=None):
1818
 
        res = False
1819
 
        for po in self.browse(cr, uid, ids, context=context):
1820
 
            if po.order_type not in ('loan', 'direct') or po.loan_id or (po.order_type == 'loan' and po.partner_id.partner_type in ('internal', 'intermission')):
1821
 
                res = True
1822
 
 
1823
 
        return res
1824
 
 
 
276
                    if line.procurement_id: todo.append(line.procurement_id.id)
 
277
                    
 
278
        if todo:
 
279
            todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
 
280
        
 
281
        if todo2:
 
282
            sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
 
283
            move_obj.write(cr, uid, sm_ids, {'state': 'done'}, context=context)
 
284
            for move in move_obj.browse(cr, uid, sm_ids, context=context):
 
285
                if move.picking_id: todo3.append(move.picking_id.id)
 
286
                
 
287
        if todo3:
 
288
            for pick_id in todo3:
 
289
                wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
 
290
            
 
291
        return super(purchase_order, self).wkf_approve_order(cr, uid, ids, context=context)
 
292
    
1825
293
    def action_sale_order_create(self, cr, uid, ids, context=None):
1826
294
        '''
1827
295
        Create a sale order as counterpart for the loan.
1830
298
            ids = [ids]
1831
299
        if context is None:
1832
300
            context = {}
1833
 
 
 
301
            
1834
302
        sale_obj = self.pool.get('sale.order')
1835
303
        sale_line_obj = self.pool.get('sale.order.line')
1836
304
        sale_shop = self.pool.get('sale.shop')
1837
305
        partner_obj = self.pool.get('res.partner')
1838
 
 
 
306
            
1839
307
        for order in self.browse(cr, uid, ids):
1840
 
            if order.is_a_counterpart or (order.order_type == 'loan' and order.partner_id.partner_type in ('internal', 'intermission')):
1841
 
                # UTP-392: This PO is created by the synchro from a Loan FO of internal/intermission partner, so do not generate the counterpart FO
1842
 
                return
1843
 
 
1844
308
            loan_duration = Parser.DateFromString(order.minimum_planned_date) + RelativeDateTime(months=+order.loan_duration)
1845
309
            # from yml test is updated according to order value
1846
310
            values = {'shop_id': sale_shop.search(cr, uid, [])[0],
1857
321
                      'categ': order.categ,
1858
322
                      'priority': order.priority,
1859
323
                      'from_yml_test': order.from_yml_test,
1860
 
                      'is_a_counterpart': True,
1861
324
                      }
1862
325
            order_id = sale_obj.create(cr, uid, values, context=context)
1863
326
            for line in order.order_line:
1871
334
                                               'name': line.name,
1872
335
                                               'type': line.product_id.procure_method})
1873
336
            self.write(cr, uid, [order.id], {'loan_id': order_id})
1874
 
 
 
337
            
1875
338
            sale = sale_obj.browse(cr, uid, order_id)
1876
 
            message = _("Loan counterpart '%s' has been created and validated. Please confirm it.") % (sale.name,)
1877
 
 
 
339
            
 
340
            message = _("Loan counterpart '%s' was created.") % (sale.name,)
 
341
            
1878
342
            sale_obj.log(cr, uid, order_id, message)
1879
 
 
 
343
        
1880
344
        return order_id
1881
 
 
 
345
    
1882
346
    def has_stockable_product(self,cr, uid, ids, *args):
1883
347
        '''
1884
348
        Override the has_stockable_product to return False
1889
353
        for order in self.browse(cr, uid, ids):
1890
354
            if order.order_type != 'direct':
1891
355
                return super(purchase_order, self).has_stockable_product(cr, uid, ids, args)
 
356
        
1892
357
        return False
1893
 
 
 
358
    
1894
359
    def action_invoice_create(self, cr, uid, ids, *args):
1895
360
        '''
1896
361
        Override this method to check the purchase_list box on invoice
1897
 
        when the invoice comes from a purchase list.
1898
 
        Change journal to an inkind journal if we comes from a In-kind Donation PO
 
362
        when the invoice comes from a purchase list
1899
363
        '''
1900
364
        invoice_id = super(purchase_order, self).action_invoice_create(cr, uid, ids, args)
1901
365
        invoice_obj = self.pool.get('account.invoice')
1902
 
        inkind_journal_ids = self.pool.get('account.journal').search(cr, uid, [
1903
 
                    ("type", "=", "inkind"),
1904
 
                    ('is_current_instance', '=', True)
1905
 
                ])
1906
 
 
 
366
        
1907
367
        for order in self.browse(cr, uid, ids):
1908
368
            if order.order_type == 'purchase_list':
1909
369
                invoice_obj.write(cr, uid, [invoice_id], {'purchase_list': 1})
1910
 
            elif order.order_type == 'in_kind':
1911
 
                if not inkind_journal_ids:
1912
 
                    raise osv.except_osv(_('Error'), _('No In-kind Donation journal found!'))
1913
 
                invoice_obj.write(cr, uid, [invoice_id], {'journal_id': inkind_journal_ids[0], 'is_inkind_donation': True})
1914
 
 
 
370
        
1915
371
        return invoice_id
1916
 
 
 
372
    
1917
373
    def _hook_action_picking_create_modify_out_source_loc_check(self, cr, uid, ids, context=None, *args, **kwargs):
1918
374
        '''
1919
375
        Please copy this to your module's method also.
1920
376
        This hook belongs to the action_picking_create method from purchase>purchase.py>purchase_order class
1921
 
 
 
377
        
1922
378
        - allow to choose whether or not the source location of the corresponding outgoing stock move should
1923
379
        match the destination location of incoming stock move
1924
380
        '''
1925
381
        order_line = kwargs['order_line']
1926
382
        # by default, we change the destination stock move if the destination stock move exists
1927
383
        return order_line.move_dest_id
1928
 
 
 
384
    
 
385
    def _hook_action_picking_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
 
386
        '''
 
387
        modify data for stock move creation
 
388
        '''
 
389
        move_values = kwargs['move_values']
 
390
        return move_values
 
391
    
1929
392
    # @@@override@purchase.purchase.order.action_picking_create
1930
393
    def action_picking_create(self,cr, uid, ids, context=None, *args):
1931
 
        if context is None:
1932
 
            context = {}
1933
 
        move_obj = self.pool.get('stock.move')
1934
 
        line_obj = self.pool.get('purchase.order.line')
1935
 
        sol_obj = self.pool.get('sale.order.line')
1936
 
        data_obj = self.pool.get('ir.model.data')
1937
 
 
1938
 
        input_loc = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_input')[1]
1939
394
        picking_id = False
1940
395
        for order in self.browse(cr, uid, ids):
1941
 
            moves_to_update = []
1942
396
            loc_id = order.partner_id.property_stock_supplier.id
1943
397
            istate = 'none'
1944
398
            reason_type_id = False
1945
399
            if order.invoice_method=='picking':
1946
400
                istate = '2binvoiced'
1947
 
 
 
401
                
1948
402
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in')
1949
403
            picking_values = {
1950
404
                'name': pick_name,
1957
411
                'company_id': order.company_id.id,
1958
412
                'move_lines' : [],
1959
413
            }
1960
 
 
1961
 
            if order.order_type in ('regular', 'purchase_list', 'direct') and order.partner_id.partner_type in ('internal', 'intermission', 'section', 'esc'):
1962
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_supply')[1]
1963
 
            elif order.order_type in ('regular', 'purchase_list', 'direct'):
 
414
            
 
415
            if order.order_type in ('regular', 'purchase_list', 'direct'):
1964
416
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
1965
417
            if order.order_type == 'loan':
1966
418
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loan')[1]
1970
422
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation_expiry')[1]
1971
423
            if order.order_type == 'in_kind':
1972
424
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_in_kind_donation')[1]
1973
 
 
 
425
                
1974
426
            if reason_type_id:
1975
427
                picking_values.update({'reason_type_id': reason_type_id})
1976
 
 
 
428
            
1977
429
            picking_id = self.pool.get('stock.picking').create(cr, uid, picking_values, context=context)
1978
430
            todo_moves = []
1979
431
            for order_line in order.order_line:
1980
 
                # Reload the data of the line because if the line comes from an ISR and it's a duplicate line,
1981
 
                # the move_dest_id field has been changed by the _hook_action_picking_create_modify_out_source_loc_check method
1982
 
                order_line = self.pool.get('purchase.order.line').browse(cr, uid, order_line.id, context=context)
1983
432
                if not order_line.product_id:
1984
433
                    continue
1985
 
                dest = order.location_id.id
1986
 
                # service with reception are directed to Service Location
1987
 
                if order_line.product_id.type == 'service_recep' and not order.cross_docking_ok:
1988
 
                    dest = self.pool.get('stock.location').get_service_location(cr, uid)
1989
 
                else:
1990
 
                    sol_ids = line_obj.get_sol_ids_from_pol_ids(cr, uid, [order_line.id], context=context)
1991
 
                    for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
1992
 
                        if sol.order_id and sol.order_id.procurement_request:
1993
 
                            if order_line.product_id.type == 'service_recep':
1994
 
                                dest = self.pool.get('stock.location').get_service_location(cr, uid)
1995
 
                                break
1996
 
                            elif order_line.product_id.type == 'consu':
1997
 
                                dest = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1998
 
                                break
1999
 
                            elif sol.order_id.location_requestor_id.usage != 'customer':
2000
 
                                dest = input_loc
2001
 
                                break
2002
 
 
2003
 
                move_values = {
2004
 
                    'name': order.name + ': ' +(order_line.name or ''),
2005
 
                    'product_id': order_line.product_id.id,
2006
 
                    'product_qty': order_line.product_qty,
2007
 
                    'product_uos_qty': order_line.product_qty,
2008
 
                    'product_uom': order_line.product_uom.id,
2009
 
                    'product_uos': order_line.product_uom.id,
2010
 
                    'date': order_line.date_planned,
2011
 
                    'date_expected': order_line.date_planned,
2012
 
                    'location_id': loc_id,
2013
 
                    'location_dest_id': dest,
2014
 
                    'picking_id': picking_id,
2015
 
                    'move_dest_id': order_line.move_dest_id.id,
2016
 
                    'state': 'draft',
2017
 
                    'purchase_line_id': order_line.id,
2018
 
                    'company_id': order.company_id.id,
2019
 
                    'price_currency_id': order.pricelist_id.currency_id.id,
2020
 
                    'price_unit': order_line.price_unit,
2021
 
                    'date': order_line.confirmed_delivery_date,
2022
 
                    'date_expected': order_line.confirmed_delivery_date,
2023
 
                    'line_number': order_line.line_number,
2024
 
                }
2025
 
 
2026
 
                if reason_type_id:
2027
 
                    move_values.update({'reason_type_id': reason_type_id})
2028
 
 
2029
 
                ctx = context.copy()
2030
 
                ctx['bypass_store_function'] = [('stock.picking', ['dpo_incoming', 'dpo_out', 'overall_qty', 'line_state'])]
2031
 
                move = self.pool.get('stock.move').create(cr, uid, move_values, context=ctx)
2032
 
                if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
2033
 
                    moves_to_update.append(order_line.move_dest_id.id)
2034
 
                todo_moves.append(move)
2035
 
            # compute function fields
2036
 
            if todo_moves:
2037
 
                compute_store = self.pool.get('stock.move')._store_get_values(cr, uid, todo_moves, None, context)
2038
 
                compute_store.sort()
2039
 
                done = []
2040
 
                for _, store_object, store_ids, store_fields2 in compute_store:
2041
 
                    if store_fields2 in ('dpo_incoming', 'dpo_out', 'overall_qty', 'line_state') and not (store_object, store_ids, store_fields2) in done:
2042
 
                        self.pool.get(store_object)._store_set_values(cr, uid, store_ids, store_fields2, context)
2043
 
                        done.append((store_object, store_ids, store_fields2))
2044
 
            move_obj.write(cr, uid, moves_to_update, {'location_id':order.location_id.id})
2045
 
            move_obj.action_confirm(cr, uid, todo_moves)
2046
 
            move_obj.force_assign(cr, uid, todo_moves)
 
434
                if order_line.product_id.product_tmpl_id.type in ('product', 'consu', 'service_recep',):
 
435
                    dest = order.location_id.id
 
436
                    # service with reception are directed to Service Location
 
437
                    if order_line.product_id.product_tmpl_id.type == 'service_recep':
 
438
                        service_loc = self.pool.get('stock.location').search(cr, uid, [('service_location', '=', True)], context=context)
 
439
                        if service_loc:
 
440
                            dest = service_loc[0]
 
441
                            
 
442
                    move_values = {
 
443
                        'name': order.name + ': ' +(order_line.name or ''),
 
444
                        'product_id': order_line.product_id.id,
 
445
                        'product_qty': order_line.product_qty,
 
446
                        'product_uos_qty': order_line.product_qty,
 
447
                        'product_uom': order_line.product_uom.id,
 
448
                        'product_uos': order_line.product_uom.id,
 
449
                        'date': order_line.date_planned,
 
450
                        'date_expected': order_line.date_planned,
 
451
                        'location_id': loc_id,
 
452
                        'location_dest_id': dest,
 
453
                        'picking_id': picking_id,
 
454
                        'move_dest_id': order_line.move_dest_id.id,
 
455
                        'state': 'draft',
 
456
                        'purchase_line_id': order_line.id,
 
457
                        'company_id': order.company_id.id,
 
458
                        'price_unit': order_line.price_unit
 
459
                    }
 
460
                    # hook for stock move values modification
 
461
                    move_values = self._hook_action_picking_create_stock_picking(cr, uid, ids, context=context, move_values=move_values, order_line=order_line,)
 
462
                    
 
463
                    if reason_type_id:
 
464
                        move_values.update({'reason_type_id': reason_type_id})
 
465
                    
 
466
                    move = self.pool.get('stock.move').create(cr, uid, move_values, context=context)
 
467
                    if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
 
468
                        self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
 
469
                    todo_moves.append(move)
 
470
            self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
 
471
            self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
2047
472
            wf_service = netsvc.LocalService("workflow")
2048
473
            wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
2049
474
        return picking_id
2050
475
        # @@@end
2051
476
 
2052
 
    def _get_location_id(self, cr, uid, vals, warehouse_id=False, context=None):
2053
 
        """
2054
 
        Get the location_id according to the cross_docking_ok option
2055
 
        Return vals
2056
 
        """
2057
 
        if 'cross_docking_ok' not in vals:
2058
 
            return vals
2059
 
 
2060
 
        if not warehouse_id:
2061
 
            warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)[0]
2062
 
 
2063
 
        if isinstance(warehouse_id, str):
2064
 
            try:
2065
 
                warehouse_id = int(warehouse_id)
2066
 
            except ValueError:
2067
 
                raise osv.except_osv(
2068
 
                        _('Error'),
2069
 
                        _('The field \'warehouse_id\' is a float field but value is a string - Please contact your administrator'),
2070
 
                )
2071
 
 
2072
 
        if not vals.get('cross_docking_ok', False):
2073
 
            vals.update({'location_id': self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_input_id.id})
2074
 
        elif vals.get('cross_docking_ok', False):
2075
 
            vals.update({'location_id': self.pool.get('stock.location').get_cross_docking_location(cr, uid)})
2076
 
 
2077
 
        return vals
2078
 
 
2079
 
 
2080
477
    def create(self, cr, uid, vals, context=None):
2081
478
        """
2082
479
        Filled in 'from_yml_test' to True if we come from tests
2083
 
        # UTP-114 demands purchase_list PO to be 'from picking'.
2084
480
        """
2085
481
        if not context:
2086
482
            context = {}
2087
 
 
 
483
        if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
 
484
            logging.getLogger('init').info('PO: set from yml test to True')
 
485
            vals['from_yml_test'] = True
 
486
            
2088
487
        if vals.get('order_type'):
2089
 
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
 
488
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan', 'in_kind']:
2090
489
                vals.update({'invoice_method': 'manual'})
2091
 
            elif vals.get('order_type') in ['direct']:
 
490
            elif vals.get('order_type') in ['direct', 'purchase_list']:
2092
491
                vals.update({'invoice_method': 'order'})
2093
 
                if vals.get('partner_id'):
2094
 
                    if self.pool.get('res.partner').browse(cr, uid, vals.get('partner_id'), context=context).partner_type == 'esc':
2095
 
                        vals.update({'invoice_method': 'manual'})
2096
492
            else:
2097
493
                vals.update({'invoice_method': 'picking'})
2098
 
 
 
494
            
2099
495
        if 'partner_id' in vals:
2100
496
            self._check_user_company(cr, uid, vals['partner_id'], context=context)
2101
 
        # we need to update the location_id because it is readonly and so does not pass in the vals of create and write
2102
 
        vals = self._get_location_id(cr, uid, vals, warehouse_id=vals.get('warehouse_id', False), context=context)
2103
 
 
2104
 
        res = super(purchase_order, self).create(cr, uid, vals, context=context)
2105
 
        self._check_service(cr, uid, [res], vals, context=context)
2106
 
 
2107
 
        return res
2108
 
 
2109
 
    def wkf_action_cancel_po(self, cr, uid, ids, context=None):
 
497
    
 
498
        return super(purchase_order, self).create(cr, uid, vals, context=context)
 
499
 
 
500
    def action_cancel(self, cr, uid, ids, context=None):
2110
501
        """
2111
502
        Cancel activity in workflow.
2112
503
        """
2115
506
            context = {}
2116
507
        if isinstance(ids, (int, long)):
2117
508
            ids = [ids]
2118
 
 
2119
 
        wf_service = netsvc.LocalService("workflow")
2120
 
 
2121
 
        line_ids = []
2122
 
        for order in self.browse(cr, uid, ids, context=context):
2123
 
            for line in order.order_line:
2124
 
                line_ids.append(line.id)
2125
 
                if line.procurement_id and line.procurement_id.move_id:
2126
 
                    self.pool.get('stock.move').write(cr, uid, line.procurement_id.move_id.id, {'state': 'cancel'}, context=context)
2127
 
                    if line.procurement_id.move_id.picking_id:
2128
 
                        wf_service.trg_write(uid, 'stock.picking', line.procurement_id.move_id.picking_id.id, cr)
2129
 
 
2130
 
        self.pool.get('purchase.order.line').cancel_sol(cr, uid, line_ids, context=context)
2131
509
        return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
2132
510
 
2133
 
    def wkf_confirm_cancel(self, cr, uid, ids, context=None):
2134
 
        """
2135
 
        Continue the workflow if all other POs are confirmed
2136
 
        """
2137
 
        wf_service = netsvc.LocalService("workflow")
2138
 
        so_obj = self.pool.get('sale.order')
2139
 
 
2140
 
        # corresponding sale order
2141
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
2142
 
        # from so, list corresponding po first level
2143
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
2144
 
        # from listed po, list corresponding so
2145
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
2146
 
        # from all so, list all corresponding po second level
2147
 
        all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
2148
 
 
2149
 
        not_confirmed_po = self.search(cr, uid, [
2150
 
            ('id', 'not in', all_po_for_all_so_ids),
2151
 
            ('state', '=', 'confirmed_wait'),
2152
 
        ], context=context)
2153
 
 
2154
 
        # we trigger all the corresponding sale order -> test_lines is called on these so
2155
 
        for so_id in all_so_ids:
2156
 
            wf_service.trg_write(uid, 'sale.order', so_id, cr)
2157
 
 
2158
 
        # we trigger pos of all sale orders -> all_po_confirm is called on these po
2159
 
        for po_id in all_po_for_all_so_ids:
2160
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
2161
 
 
2162
 
        for po_id in not_confirmed_po:
2163
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
2164
 
 
2165
 
        return True
2166
 
 
2167
 
 
2168
511
    def action_done(self, cr, uid, ids, context=None):
2169
512
        """
2170
513
        Done activity in workflow.
2174
517
            context = {}
2175
518
        if isinstance(ids, (int, long)):
2176
519
            ids = [ids]
2177
 
        for order in self.browse(cr, uid, ids, context=context):
2178
 
            vals = {'state': 'done'}
2179
 
            if order.order_type == 'direct':
2180
 
                vals.update({'shipped': 1})
2181
 
            self.write(cr, uid, order.id, vals, context=context)
2182
 
        return True
 
520
        return self.write(cr, uid, ids, {'state':'done'}, context=context)
2183
521
 
2184
522
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
2185
523
        '''
2208
546
                loan_context.update({'loan_id': order.id})
2209
547
                self.pool.get('sale.order').set_manually_done(cr, uid, order.loan_id.id, all_doc=all_doc, context=loan_context)
2210
548
 
 
549
            # Done invoices
 
550
            invoice_error_ids = []
 
551
            for invoice in order.invoice_ids:
 
552
                if invoice.state == 'draft':
 
553
                    wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_cancel', cr)
 
554
                elif invoice.state not in ('cancel', 'done'):
 
555
                    invoice_error_ids.append(invoice.id)
 
556
 
 
557
            if invoice_error_ids:
 
558
                invoices_ref = ' / '.join(x.number for x in self.pool.get('account.invoice').browse(cr, uid, invoice_error_ids, context=context))
 
559
                raise osv.except_osv(_('Error'), _('The state of the following invoices cannot be updated automatically. Please cancel them manually or discuss with the accounting team to solve the problem.' \
 
560
                                'Invoices references : %s') % invoices_ref)
 
561
 
2211
562
        # Done stock moves
2212
563
        move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id', 'in', order_lines), ('state', 'not in', ('cancel', 'done'))], context=context)
2213
564
        self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
2215
566
        # Cancel all procurement ordes which have generated one of these PO
2216
567
        proc_ids = self.pool.get('procurement.order').search(cr, uid, [('purchase_id', 'in', ids)], context=context)
2217
568
        for proc in self.pool.get('procurement.order').browse(cr, uid, proc_ids, context=context):
2218
 
            if proc.move_id and proc.move_id.id:
2219
 
                self.pool.get('stock.move').write(cr, uid, [proc.move_id.id], {'state': 'cancel'}, context=context)
 
569
            self.pool.get('stock.move').write(cr, uid, [proc.move_id.id], {'state': 'cancel'}, context=context)
2220
570
            wf_service.trg_validate(uid, 'procurement.order', proc.id, 'subflow.cancel', cr)
2221
571
 
2222
572
        if all_doc:
2231
581
                    # Search the method called when the workflow enter in last activity
2232
582
                    wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'act_done')[1]
2233
583
                    activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
2234
 
                    _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
 
584
                    res = _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
2235
585
 
2236
586
        return True
2237
 
 
2238
 
    def _hook_order_infos(self, cr, uid, *args, **kwargs):
2239
 
        '''
2240
 
        Hook to change the values of the PO
2241
 
        '''
2242
 
        order_infos = super(purchase_order, self)._hook_order_infos(cr, uid, *args, **kwargs)
2243
 
        order_id = kwargs['order_id']
2244
 
 
2245
 
        fields = ['invoice_method', 'minimum_planned_date', 'order_type',
2246
 
                  'categ', 'priority', 'internal_type', 'arrival_date',
2247
 
                  'transport_type', 'shipment_date', 'ready_to_ship_date',
2248
 
                  'cross_docking_ok', 'delivery_confirmed_date',
2249
 
                  'est_transport_lead_time', 'transport_mode', 'location_id',
2250
 
                  'dest_address_id', 'incoterm_id']
2251
 
 
2252
 
 
2253
 
        delivery_requested_date = getattr(order_id, 'delivery_requested_date')
2254
 
        if not order_infos.get('delivery_requested_date') or delivery_requested_date < order_infos['delivery_requested_date']:
2255
 
            order_infos['delivery_requested_date'] = delivery_requested_date
2256
 
 
2257
 
 
2258
 
        for field in fields:
2259
 
            field_val = getattr(order_id, field)
2260
 
            if isinstance(field_val, browse_record):
2261
 
                field_val = field_val.id
2262
 
            elif isinstance(field_val, browse_null):
2263
 
                field_val = False
2264
 
            elif isinstance(field_val, list):
2265
 
                field_val = ((6, 0, tuple([v.id for v in field_val])),)
2266
 
            order_infos[field] = field_val
2267
 
 
2268
 
        return order_infos
2269
 
 
2270
 
    def _hook_o_line_value(self, cr, uid, *args, **kwargs):
2271
 
        o_line = super(purchase_order, self)._hook_o_line_value(cr, uid, *args, **kwargs)
2272
 
        order_line = kwargs['order_line']
2273
 
 
2274
 
        # Copy all fields except order_id and analytic_distribution_id
2275
 
        fields = ['product_uom', 'price_unit', 'move_dest_id', 'product_qty', 'partner_id',
2276
 
                  'confirmed_delivery_date', 'nomenclature_description', 'default_code',
2277
 
                  'nomen_manda_0', 'nomen_manda_1', 'nomen_manda_2', 'nomen_manda_3',
2278
 
                  'nomenclature_code', 'name', 'default_name', 'comment', 'date_planned',
2279
 
                  'to_correct_ok', 'text_error',
2280
 
                  'nomen_sub_0', 'nomen_sub_1', 'nomen_sub_2', 'nomen_sub_3', 'nomen_sub_4',
2281
 
                  'nomen_sub_5', 'procurement_id', 'change_price_manually', 'old_price_unit',
2282
 
                  'origin', 'account_analytic_id', 'product_id', 'company_id', 'notes', 'taxes_id']
2283
 
 
2284
 
        for field in fields:
2285
 
            field_val = getattr(order_line, field)
2286
 
            if isinstance(field_val, browse_record):
2287
 
                field_val = field_val.id
2288
 
            elif isinstance(field_val, browse_null):
2289
 
                field_val = False
2290
 
            elif isinstance(field_val, list):
2291
 
                field_val = ((6, 0, tuple([v.id for v in field_val])),)
2292
 
            o_line[field] = field_val
2293
 
 
2294
 
 
2295
 
        # Set the analytic distribution
2296
 
        distrib_id = False
2297
 
        if order_line.analytic_distribution_id:
2298
 
            distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.analytic_distribution_id.id)
2299
 
        elif order_line.order_id.analytic_distribution_id:
2300
 
            distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.order_id.analytic_distribution_id.id)
2301
 
 
2302
 
        o_line['analytic_distribution_id'] = distrib_id
2303
 
 
2304
 
        return o_line
2305
 
 
2306
 
    def check_empty_po(self, cr, uid, ids, context=None):
2307
 
        """
2308
 
        If the PO is empty, return a wizard to ask user if he wants
2309
 
        cancel the whole PO
2310
 
        """
2311
 
        order_wiz_obj = self.pool.get('purchase.order.cancel.wizard')
2312
 
        data_obj = self.pool.get('ir.model.data')
2313
 
 
2314
 
        for po in self.browse(cr, uid, ids, context=context):
2315
 
            if all(x.state in ('cancel', 'done') for x in po.order_line):
2316
 
                wiz_id = order_wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
2317
 
                if po.rfq_ok:
2318
 
                    view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_rfq_cancel_wizard_form_view')[1]
2319
 
                else:
2320
 
                    view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
2321
 
                context['view_id'] = False
2322
 
                return {'type': 'ir.actions.act_window',
2323
 
                        'res_model': 'purchase.order.cancel.wizard',
2324
 
                        'view_type': 'form',
2325
 
                        'view_mode': 'form',
2326
 
                        'view_id': [view_id],
2327
 
                        'res_id': wiz_id,
2328
 
                        'target': 'new',
2329
 
                        'context': context}
2330
 
 
2331
 
        return {'type': 'ir.actions.act_window_close'}
2332
 
 
 
587
    
2333
588
purchase_order()
2334
589
 
2335
590
 
2336
 
class purchase_order_line1(osv.osv):
2337
 
    '''
2338
 
    this modification is placed before merged, because unit price of merged should be Computation as well
2339
 
    '''
2340
 
    _name = 'purchase.order.line'
2341
 
    _inherit = 'purchase.order.line'
2342
 
 
2343
 
    def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
2344
 
        '''
2345
 
        Return True if the system configuration VAT management is set to True
2346
 
        '''
2347
 
        vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
2348
 
        res = {}
2349
 
        for id in ids:
2350
 
            res[id] = vat_ok
2351
 
 
2352
 
        return res
2353
 
 
2354
 
    _columns = {
2355
 
        'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price Computation')),
2356
 
        'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
2357
 
    }
2358
 
 
2359
 
    _defaults = {
2360
 
        'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
2361
 
    }
2362
 
 
2363
 
purchase_order_line1()
2364
 
 
2365
 
 
2366
591
class purchase_order_merged_line(osv.osv):
2367
592
    '''
2368
593
    A purchase order merged line is a special PO line.
2378
603
    _description = 'Purchase Order Merged Lines'
2379
604
    _table = 'purchase_order_merged_line'
2380
605
 
2381
 
    def _get_name(self, cr, uid, ids, field_name, args, context=None):
2382
 
        res = {}
2383
 
        for line in self.browse(cr, uid, ids, context=context):
2384
 
            if line.order_line_ids:
2385
 
                res[line.id] = line.product_id and line.product_id.name or line.order_line_ids[0].comment
2386
 
        return res
2387
 
 
2388
606
    _columns = {
2389
607
        'order_line_ids': fields.one2many('purchase.order.line', 'merged_id', string='Purchase Lines'),
2390
608
        'date_planned': fields.date(string='Delivery Requested Date', required=False, select=True,
2391
609
                                            help='Header level dates has to be populated by default with the possibility of manual updates'),
2392
 
        'name': fields.function(_get_name, method=True, type='char', string='Name', store=False),
2393
610
    }
2394
611
 
2395
612
    def create(self, cr, uid, vals, context=None):
2416
633
                    self.pool.get('purchase.order.line').write(cr, uid, [po_line.id], {'price_unit': vals['price_unit'],
2417
634
                                                                                       'old_price_unit': vals['price_unit']}, context=new_context)
2418
635
 
2419
 
        res = super(purchase_order_merged_line, self).write(cr, uid, ids, vals, context=context)
2420
 
 
2421
 
        return res
2422
 
 
2423
 
    def _update(self, cr, uid, p_id, po_line_id, product_qty, price=0.00, context=None, no_update=False):
 
636
        return super(purchase_order_merged_line, self).write(cr, uid, ids, vals, context=context)
 
637
 
 
638
    def _update(self, cr, uid, id, po_line_id, product_qty, price=0.00, context=None, no_update=False):
2424
639
        '''
2425
640
        Update the quantity and the unit price according to the new qty
2426
641
        '''
2427
 
        line = self.browse(cr, uid, p_id, context=context)
 
642
        line = self.browse(cr, uid, id, context=context)
2428
643
        change_price_ok = True
 
644
        delete = False
2429
645
        if not po_line_id:
2430
646
            change_price_ok = context.get('change_price_ok', True)
2431
647
        else:
2432
648
            po_line = self.pool.get('purchase.order.line').browse(cr, uid, po_line_id, context=context)
2433
 
            change_price_ok = po_line.change_price_ok
2434
 
            if 'change_price_ok' in context:
2435
 
                change_price_ok = context.get('change_price_ok')
2436
649
 
2437
650
        # If no PO line attached to this merged line, remove the merged line
2438
651
        if not line.order_line_ids:
2439
 
            self.unlink(cr, uid, [p_id], context=context)
 
652
            self.unlink(cr, uid, [id], context=context)
2440
653
            return False, False
2441
654
 
2442
655
        new_price = False
2443
 
        new_qty = line.product_qty + float(product_qty)
2444
 
 
2445
 
        if (po_line_id and not change_price_ok and not po_line.order_id.rfq_ok) or (not po_line_id and not change_price_ok):
 
656
        new_qty = line.product_qty + product_qty
 
657
        if (po_line_id and not po_line.change_price_ok and not po_line.order_id.rfq_ok) or (not po_line_id and not change_price_ok):    
2446
658
            # Get the catalogue unit price according to the total qty
2447
 
            new_price = self.pool.get('product.pricelist').price_get(cr, uid,
 
659
            new_price = self.pool.get('product.pricelist').price_get(cr, uid, 
2448
660
                                                              [line.order_id.pricelist_id.id],
2449
661
                                                              line.product_id.id,
2450
662
                                                              new_qty,
2451
663
                                                              line.order_id.partner_id.id,
2452
664
                                                              {'uom': line.product_uom.id,
2453
 
                                                               'date': line.order_id.date_order})[line.order_id.pricelist_id.id]
2454
 
 
2455
 
        # Update the quantity of the merged line
 
665
                                                               'date': line.order_id.date_order})[line.order_id.pricelist_id.id]                                      
 
666
        
 
667
        # Update the quantity of the merged line                  
2456
668
        values = {'product_qty': new_qty}
2457
669
        # If a catalogue unit price exist and the unit price is not manually changed
2458
670
        if new_price:
2464
676
 
2465
677
        # Update the unit price and the quantity of the merged line
2466
678
        if not no_update:
2467
 
            self.write(cr, uid, [p_id], values, context=context)
 
679
            self.write(cr, uid, [id], values, context=context)
2468
680
 
2469
 
        return p_id, new_price or False
 
681
        return id, new_price or False
2470
682
 
2471
683
 
2472
684
purchase_order_merged_line()
2476
688
    _name = 'purchase.order.line'
2477
689
    _inherit = 'purchase.order.line'
2478
690
 
2479
 
    def init(self, cr):
2480
 
        self.pool.get('fields.tools').remove_sql_constraint(cr,
2481
 
            'purchase_order_line', 'product_qty')
2482
 
        if hasattr(super(purchase_order_line, self), 'init'):
2483
 
            super(purchase_order_line, self).init(cr)
2484
 
 
2485
691
    def link_merged_line(self, cr, uid, vals, product_id, order_id, product_qty, uom_id, price_unit=0.00, context=None):
2486
692
        '''
2487
693
        Check if a merged line exist. If not, create a new one and attach them to the Po line
2493
699
            merged_ids = line_obj.search(cr, uid, domain, context=context)
2494
700
        else:
2495
701
            merged_ids = []
2496
 
 
 
702
        
2497
703
        new_vals = vals.copy()
2498
 
        # Don't include taxes on merged lines
2499
 
        if 'taxes_id' in new_vals:
2500
 
            new_vals.pop('taxes_id')
2501
704
 
2502
705
        if not merged_ids:
2503
706
            new_vals['order_id'] = order_id
2506
709
            # Create a new merged line which is the same than the normal PO line except for price unit
2507
710
            vals['merged_id'] = line_obj.create(cr, uid, new_vals, context=context)
2508
711
        else:
2509
 
            c = context.copy()
2510
 
            order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
2511
 
            stages = self._get_stages_price(cr, uid, product_id, uom_id, order, context=context)
2512
 
            if order.state != 'confirmed' and stages and not order.rfq_ok:
2513
 
                c.update({'change_price_ok': False})
2514
712
            # Update the associated merged line
2515
 
            res_merged = line_obj._update(cr, uid, merged_ids[0], False, product_qty, price_unit, context=c, no_update=False)
 
713
            res_merged = line_obj._update(cr, uid, merged_ids[0], False, product_qty, price_unit, context=context, no_update=False)
2516
714
            vals['merged_id'] = res_merged[0]
2517
715
            # Update unit price
2518
716
            vals['price_unit'] = res_merged[1]
2524
722
        Update the merged line
2525
723
        '''
2526
724
        merged_line_obj = self.pool.get('purchase.order.merged.line')
2527
 
 
 
725
        
2528
726
        if not vals:
2529
727
            vals = {}
2530
728
        tmp_vals = vals.copy()
2532
730
        # If it's an update of a line
2533
731
        if vals and line_id:
2534
732
            line = self.browse(cr, uid, line_id, context=context)
2535
 
 
 
733
            
2536
734
            # Set default values if not pass in values
2537
 
            if not 'product_uom' in vals:
 
735
            if not 'product_uom' in vals: 
2538
736
                tmp_vals.update({'product_uom': line.product_uom.id})
2539
 
            if not 'product_qty' in vals:
 
737
            if not 'product_qty' in vals: 
2540
738
                tmp_vals.update({'product_qty': line.product_qty})
2541
 
 
 
739
            
2542
740
            # If the user changed the product or the UoM or both on the PO line
2543
741
            if ('product_id' in vals and line.product_id.id != vals['product_id']) or ('product_uom' in vals and line.product_uom.id != vals['product_uom']):
2544
742
                # Need removing the merged_id link before update the merged line because the merged line
2545
743
                # will be removed if it hasn't attached PO line
2546
 
                merged_id = line.merged_id.id
2547
 
                change_price_ok = line.change_price_ok
2548
 
                c = context.copy()
2549
 
                c.update({'change_price_ok': change_price_ok})
2550
744
                self.write(cr, uid, line_id, {'merged_id': False}, context=context)
2551
 
                res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
2552
 
 
 
745
                res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, -line.product_qty, line.price_unit, context=context)
 
746
                
2553
747
                # Create or update an existing merged line with the new product
2554
748
                vals = self.link_merged_line(cr, uid, tmp_vals, tmp_vals.get('product_id', line.product_id.id), line.order_id.id, tmp_vals.get('product_qty', line.product_qty), tmp_vals.get('product_uom', line.product_uom.id), tmp_vals.get('price_unit', line.price_unit), context=context)
2555
 
 
 
749
            
2556
750
            # If the quantity is changed
2557
 
            elif 'product_qty' in vals and line.product_qty != vals['product_qty']:
 
751
            if 'product_qty' in vals and line.product_qty != vals['product_qty']:
2558
752
                res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, vals['product_qty']-line.product_qty, line.price_unit, context=context)
2559
753
                # Update the unit price
2560
754
                if res_merged and res_merged[1]:
2561
755
                    vals.update({'price_unit': res_merged[1]})
2562
 
 
 
756
                    
2563
757
            # If the price unit is changed and the product and the UoM is not modified
2564
758
            if 'price_unit' in tmp_vals and (line.price_unit != tmp_vals['price_unit'] or vals['price_unit'] != tmp_vals['price_unit']) and not (line.product_id.id != vals.get('product_id', False) or line.product_uom.id != vals.get('product_uom', False)):
2565
759
                # Give 0.00 to quantity because the _update should recompute the price unit with the same quantity
2576
770
            line = self.browse(cr, uid, line_id, context=context)
2577
771
            # Remove the qty from the merged line
2578
772
            if line.merged_id:
2579
 
                merged_id = line.merged_id.id
2580
 
                change_price_ok = line.change_price_ok
2581
 
                c = context.copy()
2582
 
                c.update({'change_price_ok': change_price_ok})
2583
 
                noraise_ctx = context.copy()
2584
 
                noraise_ctx.update({'noraise': True})
2585
773
                # Need removing the merged_id link before update the merged line because the merged line
2586
774
                # will be removed if it hasn't attached PO line
2587
 
                self.write(cr, uid, [line.id], {'merged_id': False}, context=noraise_ctx)
2588
 
                res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
 
775
                self.write(cr, uid, [line.id], {'merged_id': False}, context=context)
 
776
                res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, -line.product_qty, line.price_unit, context=context)
2589
777
 
2590
778
        return vals
2591
779
 
2592
 
    def _check_restriction_line(self, cr, uid, ids, context=None):
2593
 
        '''
2594
 
        Check if there is restriction on lines
2595
 
        '''
2596
 
        if isinstance(ids, (int, long)):
2597
 
            ids = [ids]
2598
 
 
2599
 
        if not context:
2600
 
            context = {}
2601
 
 
2602
 
        for line in self.browse(cr, uid, ids, context=context):
2603
 
            if line.order_id and line.order_id.partner_id and line.order_id.state != 'done' and line.product_id:
2604
 
                if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'partner_id': line.order_id.partner_id.id}, context=context):
2605
 
                    return False
2606
 
 
2607
 
        return True
2608
 
 
2609
 
    def _relatedFields(self, cr, uid, vals, context=None):
2610
 
        '''
2611
 
        related fields for create and write
2612
 
        '''
2613
 
        # recreate description because in readonly
2614
 
        if ('product_id' in vals) and (vals['product_id']):
2615
 
            # no nomenclature description
2616
 
            vals.update({'nomenclature_description':False})
2617
 
            # update the name (comment) of order line
2618
 
            # the 'name' is no more the get_name from product, but instead
2619
 
            # the name of product
2620
 
            productObj = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context)
2621
 
            vals.update({'name':productObj.name})
2622
 
            vals.update({'default_code':productObj.default_code})
2623
 
            vals.update({'default_name':productObj.name})
2624
 
            # erase the nomenclature - readonly
2625
 
            self.pool.get('product.product')._resetNomenclatureFields(vals)
2626
 
        elif ('product_id' in vals) and (not vals['product_id']):
2627
 
            sale = self.pool.get('sale.order.line')
2628
 
            sale._setNomenclatureInfo(cr, uid, vals, context)
2629
 
            # erase default code
2630
 
            vals.update({'default_code':False})
2631
 
            vals.update({'default_name':False})
2632
 
 
2633
 
            if 'comment' in vals:
2634
 
                vals.update({'name':vals['comment']})
2635
 
        # clear nomenclature filter values
2636
 
        #self.pool.get('product.product')._resetNomenclatureFields(vals)
2637
 
 
2638
 
    def _update_name_attr(self, cr, uid, vals, context=None):
2639
 
        """Update the name attribute in `vals` if a product is selected."""
2640
 
        if context is None:
2641
 
            context = {}
2642
 
        prod_obj = self.pool.get('product.product')
2643
 
        if vals.get('product_id'):
2644
 
            product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
2645
 
            vals['name'] = product.name
2646
 
        elif vals.get('comment'):
2647
 
            vals['name'] = vals.get('comment', False)
2648
 
 
2649
 
    def _check_product_uom(self, cr, uid, product_id, uom_id, context=None):
2650
 
        """Check the product UoM."""
2651
 
        if context is None:
2652
 
            context = {}
2653
 
        uom_tools_obj = self.pool.get('uom.tools')
2654
 
        if not uom_tools_obj.check_uom(cr, uid, product_id, uom_id, context=context):
2655
 
            raise osv.except_osv(
2656
 
                _('Error'),
2657
 
                _('You have to select a product UOM in the same '
2658
 
                  'category than the purchase UOM of the product !'))
2659
 
 
2660
780
    def create(self, cr, uid, vals, context=None):
2661
781
        '''
2662
782
        Create or update a merged line
2663
783
        '''
2664
 
        if context is None:
 
784
        if not context:
2665
785
            context = {}
2666
 
 
2667
 
        po_obj = self.pool.get('purchase.order')
2668
 
        seq_pool = self.pool.get('ir.sequence')
2669
 
        so_obj = self.pool.get('sale.order')
2670
 
        sol_obj = self.pool.get('sale.order.line')
2671
 
 
 
786
            
 
787
        vals.update({'old_price_unit': vals.get('price_unit', False)})
 
788
            
 
789
        order_id = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context)
 
790
        if order_id.from_yml_test:
 
791
            vals.update({'change_price_manually': True})
 
792
            if not vals.get('product_qty', False):
 
793
                vals['product_qty'] = 1.00
 
794
                
 
795
        # If we are on a RfQ, use the last entered unit price and update other lines with this price
 
796
        if order_id.rfq_ok:
 
797
            vals.update({'change_price_manually': True})
 
798
        else:
 
799
            if vals.get('product_qty', 0.00) == 0.00:
 
800
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
 
801
            
 
802
            if vals.get('price_unit', 0.00) == 0.00:
 
803
                raise osv.except_osv(_('Error'), _('You cannot save a line with no unit price !'))
 
804
        
2672
805
        order_id = vals.get('order_id')
2673
806
        product_id = vals.get('product_id')
2674
807
        product_uom = vals.get('product_uom')
2675
 
        order = po_obj.browse(cr, uid, order_id, context=context)
2676
 
 
2677
 
        if order.from_yml_test:
2678
 
            vals.update({'change_price_manually': True})
2679
 
            if not vals.get('product_qty', False):
2680
 
                vals['product_qty'] = 1.00
2681
 
            # [imported and adapted from 'analytic_distribution_supply']
2682
 
            if not vals.get('price_unit', False):
2683
 
                vals['price_unit'] = 1.00
2684
 
            # [/]
2685
 
 
2686
 
        # Update the name attribute if a product is selected
2687
 
        self._update_name_attr(cr, uid, vals, context=context)
2688
 
 
2689
 
        # If we are on a RfQ, use the last entered unit price and update other lines with this price
2690
 
        if order.rfq_ok:
2691
 
            vals.update({'change_price_manually': True})
2692
 
        else:
2693
 
            if order.po_from_fo or order.po_from_ir:
2694
 
                vals['from_fo'] = True
2695
 
            if vals.get('product_qty', 0.00) == 0.00 and not context.get('noraise'):
2696
 
                raise osv.except_osv(
2697
 
                    _('Error'),
2698
 
                    _('You can not have an order line with a negative or zero quantity')
2699
 
                )
2700
 
 
 
808
        order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
2701
809
        other_lines = self.search(cr, uid, [('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
2702
810
        stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
2703
 
 
2704
 
        if vals.get('origin'):
2705
 
            proc = False
2706
 
            if vals.get('procurement_id'):
2707
 
                proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id'))
2708
 
            if not proc or not proc.sale_id:
2709
 
                vals.update(self.update_origin_link(cr, uid, vals.get('origin'), context=context))
2710
 
 
 
811
        
2711
812
        if (other_lines and stages and order.state != 'confirmed'):
2712
813
            context.update({'change_price_ok': False})
2713
 
 
2714
 
        if not context.get('offline_synchronization'):
2715
 
            vals = self._update_merged_line(cr, uid, False, vals, context=dict(context, skipResequencing=True))
2716
 
 
2717
 
        vals.update({'old_price_unit': vals.get('price_unit', False)})
2718
 
 
2719
 
        # [imported from 'order_nomenclature']
2720
 
        # Don't save filtering data
2721
 
        self._relatedFields(cr, uid, vals, context)
2722
 
        # [/]
2723
 
 
2724
 
        # [imported from 'order_line_number']
2725
 
        # Add the corresponding line number
2726
 
        #   I leave this line from QT related to purchase.order.merged.line for compatibility and safety reasons
2727
 
        #   merged lines, set the line_number to 0 when calling create function
2728
 
        #   the following line should *logically* be removed safely
2729
 
        #   copy method should work as well, as merged line do *not* need to keep original line number with copy function (QT confirmed)
2730
 
        if self._name != 'purchase.order.merged.line':
2731
 
            if order_id:
2732
 
                # gather the line number from the sale order sequence if not specified in vals
2733
 
                # either line_number is not specified or set to False from copy, we need a new value
2734
 
                if not vals.get('line_number', False):
2735
 
                    # new number needed - gather the line number from the sequence
2736
 
                    sequence_id = order.sequence_id.id
2737
 
                    line = seq_pool.get_id(cr, uid, sequence_id, code_or_id='id', context=context)
2738
 
                    vals.update({'line_number': line})
2739
 
        # [/]
2740
 
 
2741
 
        # Check the selected product UoM
2742
 
        if not context.get('import_in_progress', False):
2743
 
            if vals.get('product_id') and vals.get('product_uom'):
2744
 
                self._check_product_uom(
2745
 
                    cr, uid, vals['product_id'], vals['product_uom'], context=context)
2746
 
 
2747
 
        # utp-518:we write the comment from the sale.order.line on the PO line through the procurement (only for the create!!)
2748
 
        po_procurement_id = vals.get('procurement_id', False)
2749
 
        if po_procurement_id:
2750
 
            sale_id = sol_obj.search(cr, uid, [('procurement_id', '=', po_procurement_id)], context=context)
2751
 
            if sale_id:
2752
 
                comment_so = sol_obj.read(cr, uid, sale_id, ['comment'], context=context)[0]['comment']
2753
 
                vals.update(comment=comment_so)
2754
 
 
2755
 
        # add the database Id to the sync_order_line_db_id
2756
 
        po_line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context)
2757
 
        if not vals.get('sync_order_line_db_id', False): #'sync_order_line_db_id' not in vals or vals:
2758
 
            name = order.name
2759
 
            super(purchase_order_line, self).write(cr, uid, [po_line_id], {'sync_order_line_db_id': name + "_" + str(po_line_id),}, context=context)
2760
 
 
2761
 
        if self._name != 'purchase.order.merged.line' and vals.get('origin') and not vals.get('procurement_id'):
2762
 
            so_ids = so_obj.search(cr, uid, [('name', '=', vals.get('origin'))], context=context)
2763
 
            for so_id in so_ids:
2764
 
                self.pool.get('expected.sale.order.line').create(cr, uid, {
2765
 
                    'order_id': so_id,
2766
 
                    'po_line_id': po_line_id,
2767
 
                }, context=context)
2768
 
 
2769
 
        return po_line_id
2770
 
 
2771
 
    def default_get(self, cr, uid, fields, context=None):
2772
 
        if not context:
2773
 
            context = {}
2774
 
 
2775
 
        if context.get('purchase_id'):
2776
 
            # Check validity of the purchase order. We write the order to avoid
2777
 
            # the creation of a new line if one line of the order is not valid
2778
 
            # according to the order category
2779
 
            # Example :
2780
 
            #    1/ Create a new PO with 'Other' as Order Category
2781
 
            #    2/ Add a new line with a Stockable product
2782
 
            #    3/ Change the Order Category of the PO to 'Service' -> A warning message is displayed
2783
 
            #    4/ Try to create a new line -> The system displays a message to avoid you to create a new line
2784
 
            #       while the not valid line is not modified/deleted
2785
 
            #
2786
 
            #   Without the write of the order, the message displayed by the system at 4/ is displayed at the saving
2787
 
            #   of the new line that is not very understandable for the user
2788
 
            data = {}
2789
 
            if context.get('partner_id'):
2790
 
                data.update({'partner_id': context.get('partner_id')})
2791
 
            if context.get('categ'):
2792
 
                data.update({'categ': context.get('categ')})
2793
 
            self.pool.get('purchase.order').write(cr, uid, [context.get('purchase_id')], data, context=context)
2794
 
 
2795
 
        return super(purchase_order_line, self).default_get(cr, uid, fields, context=context)
2796
 
 
2797
 
    def copy(self, cr, uid, line_id, defaults={}, context=None):
2798
 
        '''
2799
 
        Remove link to merged line
2800
 
        '''
2801
 
        defaults.update({'merged_id': False, 'sync_order_line_db_id': False})
2802
 
 
2803
 
        return super(purchase_order_line, self).copy(cr, uid, line_id, defaults, context=context)
2804
 
 
2805
 
    def copy_data(self, cr, uid, p_id, default=None, context=None):
2806
 
        """
2807
 
        """
2808
 
        # Some verifications
2809
 
        if not context:
2810
 
            context = {}
2811
 
        if not default:
2812
 
            default = {}
2813
 
 
2814
 
        if not 'move_dest_id' in default:
2815
 
            default.update({'move_dest_id': False})
2816
 
 
2817
 
        if not 'procurement_id' in default:
2818
 
            default.update({'procurement_id': False})
2819
 
 
2820
 
        default.update({'sync_order_line_db_id': False})
2821
 
        return super(purchase_order_line, self).copy_data(cr, uid, p_id, default=default, context=context)
 
814
            
 
815
        vals = self._update_merged_line(cr, uid, False, vals, context=context)
 
816
 
 
817
        return super(purchase_order_line, self).create(cr, uid, vals, context=context)
2822
818
 
2823
819
    def write(self, cr, uid, ids, vals, context=None):
2824
820
        '''
2825
821
        Update merged line
2826
822
        '''
2827
 
        so_obj = self.pool.get('sale.order')
2828
 
 
2829
 
        if context is None:
 
823
        if not context:
2830
824
            context = {}
2831
825
 
2832
826
        if isinstance(ids, (int, long)):
2833
827
            ids = [ids]
2834
 
 
2835
 
        res = False
2836
 
 
2837
 
        # [imported from the 'analytic_distribution_supply']
2838
 
        # Don't save filtering data
2839
 
        self._relatedFields(cr, uid, vals, context)
2840
 
        # [/]
2841
 
 
2842
 
        # Update the name attribute if a product is selected
2843
 
        self._update_name_attr(cr, uid, vals, context=context)
2844
 
 
 
828
            
2845
829
        if 'price_unit' in vals:
2846
830
            vals.update({'old_price_unit': vals.get('price_unit')})
2847
 
 
2848
 
        if ('state' in vals and vals.get('state') != 'draft') or ('procurement_id' in vals and vals.get('procurement_id')):
2849
 
            exp_sol_ids = self.pool.get('expected.sale.order.line').search(cr, uid, [('po_line_id', 'in', ids)], context=context)
2850
 
            self.pool.get('expected.sale.order.line').unlink(cr, uid, exp_sol_ids, context=context)
2851
 
 
2852
 
        for line in self.browse(cr, uid, ids, context=context):
2853
 
            new_vals = vals.copy()
2854
 
            # check qty
2855
 
            if vals.get('product_qty', line.product_qty) <= 0.0 and \
2856
 
                not line.order_id.rfq_ok and \
2857
 
                'noraise' not in context and line.state != 'cancel':
2858
 
                raise osv.except_osv(
2859
 
                    _('Error'),
2860
 
                    _('You can not have an order line with a negative or zero quantity')
2861
 
                )
2862
 
 
2863
 
            if vals.get('origin', line.origin):
2864
 
                proc = False
2865
 
                if vals.get('procurement_id', line.procurement_id.id):
2866
 
                    proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id', line.procurement_id.id))
2867
 
                if not proc or not proc.sale_id:
2868
 
                    link_so_dict = self.update_origin_link(cr, uid, vals.get('origin', line.origin), context=context)
2869
 
                    new_vals.update(link_so_dict)
2870
 
 
2871
 
            if line.order_id and not line.order_id.rfq_ok and (line.order_id.po_from_fo or line.order_id.po_from_ir):
2872
 
                new_vals['from_fo'] = True
2873
 
 
2874
 
            if not context.get('update_merge'):
2875
 
                new_vals.update(self._update_merged_line(cr, uid, line.id, vals, context=dict(context, skipResequencing=True, noraise=True)))
2876
 
 
2877
 
            res = super(purchase_order_line, self).write(cr, uid, [line.id], new_vals, context=context)
2878
 
 
2879
 
            if self._name != 'purchase.order.merged.line' and vals.get('origin') and not vals.get('procurement_id', line.procurement_id):
2880
 
                so_ids = so_obj.search(cr, uid, [('name', '=', vals.get('origin'))], context=context)
2881
 
                for so_id in so_ids:
2882
 
                    self.pool.get('expected.sale.order.line').create(cr, uid, {
2883
 
                        'order_id': so_id,
2884
 
                        'po_line_id': line.id,
2885
 
                    }, context=context)
2886
 
 
2887
 
        # Check the selected product UoM
2888
 
        if not context.get('import_in_progress', False):
2889
 
            for pol_read in self.read(cr, uid, ids, ['product_id', 'product_uom']):
2890
 
                if pol_read.get('product_id'):
2891
 
                    product_id = pol_read['product_id'][0]
2892
 
                    uom_id = pol_read['product_uom'][0]
2893
 
                    self._check_product_uom(cr, uid, product_id, uom_id, context=context)
2894
 
 
2895
 
        return res
2896
 
 
2897
 
    def update_origin_link(self, cr, uid, origin, context=None):
2898
 
        '''
2899
 
        Return the FO/IR that matches with the origin value
2900
 
        '''
2901
 
        so_obj = self.pool.get('sale.order')
2902
 
 
2903
 
        tmp_proc_context = context.get('procurement_request')
2904
 
        context['procurement_request'] = True
2905
 
        so_ids = so_obj.search(cr, uid, [('name', '=', origin), ('state', 'in', ('sourced', 'progress', 'manual'))], context=context)
2906
 
        context['procurement_request'] = tmp_proc_context
2907
 
        if so_ids:
2908
 
            return {'link_so_id': so_ids[0]}
2909
 
 
2910
 
        return {}
2911
 
 
2912
 
    def ask_unlink(self, cr, uid, ids, context=None):
2913
 
        '''
2914
 
        Call the unlink method for lines and if the PO becomes empty
2915
 
        ask the user if he wants to cancel the PO
2916
 
        '''
2917
 
        # Objects
2918
 
        wiz_obj = self.pool.get('purchase.order.line.unlink.wizard')
2919
 
        proc_obj = self.pool.get('procurement.order')
2920
 
        data_obj = self.pool.get('ir.model.data')
2921
 
        wkf_act_obj = self.pool.get('workflow.activity')
2922
 
        opl_obj = self.pool.get('stock.warehouse.orderpoint.line')
2923
 
 
2924
 
        # Variables initialization
2925
 
        if context is None:
2926
 
            context = {}
2927
 
 
2928
 
        if isinstance(ids, (int, long)):
2929
 
            ids = [ids]
2930
 
 
2931
 
        # Check if the line is not already removed
2932
 
        ids = self.search(cr, uid, [('id', 'in', ids)], context=context)
2933
 
        if not ids:
2934
 
            raise osv.except_osv(
2935
 
                _('Error'),
2936
 
                _('The line has been already deleted - Please refresh the page'),
2937
 
            )
2938
 
 
2939
 
        if context.get('rfq_ok', False):
2940
 
            view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'rfq_line_unlink_wizard_form_view')[1]
2941
 
        else:
2942
 
            view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'purchase_order_line_unlink_wizard_form_view')[1]
2943
 
 
2944
 
        for line in self.browse(cr, uid, ids, context=context):
2945
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
2946
 
            exp_sol_ids = self.get_exp_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
2947
 
            if (sol_ids or exp_sol_ids) and not context.get('from_del_wizard'):
2948
 
                wiz_id = wiz_obj.create(cr, uid, {'line_id': line.id, 'only_exp': (not sol_ids and exp_sol_ids) and True or False}, context=context)
2949
 
                if sol_ids or wiz_obj.read(cr, uid, wiz_id, ['last_line'], context=context)['last_line']:
2950
 
                    return {'type': 'ir.actions.act_window',
2951
 
                            'res_model': 'purchase.order.line.unlink.wizard',
2952
 
                            'view_type': 'form',
2953
 
                            'view_mode': 'form',
2954
 
                            'view_id': [view_id],
2955
 
                            'res_id': wiz_id,
2956
 
                            'target': 'new',
2957
 
                            'context': context}
2958
 
 
2959
 
            # In case of a PO line is created to source a FO/IR but the corresponding
2960
 
            # FO/IR line will be created when the PO will be confirmed
2961
 
            if not line.procurement_id and line.origin:
2962
 
                wiz_id = wiz_obj.create(cr, uid, {'line_id': line.id}, context=context)
2963
 
                return wiz_obj.just_cancel(cr, uid, [wiz_id], context=context)
2964
 
 
2965
 
            # In case of a PO line is not created to source a FO/IR but from a
2966
 
            # replenishment rule, cancel the stock move and the procurement order
2967
 
            if line.move_dest_id:
2968
 
                self.pool.get('stock.move').action_cancel(cr, uid, [line.move_dest_id.id], context=context)
2969
 
                proc_ids = proc_obj.search(cr, uid, [('move_id', '=', line.move_dest_id.id)], context=context)
2970
 
                if proc_ids:
2971
 
                    # Delete link between proc. order and min/max rule lines
2972
 
                    opl_ids = opl_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
2973
 
                    opl_obj.write(cr, uid, opl_ids, {'procurement_id': False}, context=context)
2974
 
                    wf_service = netsvc.LocalService("workflow")
2975
 
                    for proc_id in proc_ids:
2976
 
                        wf_service.trg_delete(uid, 'procurement.order', proc_id, cr)
2977
 
                        wkf_id = data_obj.get_object_reference(cr, uid, 'procurement', 'act_cancel')[1]
2978
 
                        activity = wkf_act_obj.browse(cr, uid, wkf_id, context=context)
2979
 
                        _eval_expr(cr, [uid, 'procurement.order', proc_id], False, activity.action)
2980
 
 
2981
 
        context['from_del_wizard'] = False
2982
 
        return self.unlink(cr, uid, ids, context=context)
2983
 
 
2984
 
    def cancel_sol(self, cr, uid, ids, context=None):
2985
 
        '''
2986
 
        Re-source the FO line
2987
 
        '''
2988
 
        context = context or {}
2989
 
        sol_obj = self.pool.get('sale.order.line')
2990
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
2991
 
        so_obj = self.pool.get('sale.order')
2992
 
        uom_obj = self.pool.get('product.uom')
2993
 
 
2994
 
        if isinstance(ids, (int, long)):
2995
 
            ids = [ids]
2996
 
 
2997
 
        sol_to_update = {}
2998
 
        sol_not_to_delete_ids = []
2999
 
        ir_to_potentialy_cancel_ids = []
3000
 
        sol_of_po_line_resourced_ids = []
3001
 
 
3002
 
        so_to_cancel_ids = []
3003
 
        for line in self.browse(cr, uid, ids, context=context):
3004
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
3005
 
 
3006
 
            if not sol_ids and line.origin:
3007
 
                origin_ids = so_obj.search(cr, uid, [('name', '=', line.origin)], context=context)
3008
 
                for origin in so_obj.read(cr, uid, origin_ids, ['order_line'], context=context):
3009
 
                    exp_sol_ids = exp_sol_obj.search(cr, uid, [('order_id', '=', origin['id']), ('po_line_id', '!=', line.id)], context=context)
3010
 
                    if not origin['order_line'] and not exp_sol_ids:
3011
 
                        so_to_cancel_ids.extend(origin_ids)
3012
 
 
3013
 
            line_qty = line.product_qty
3014
 
            if 'pol_qty' in context and line.id in context['pol_qty']:
3015
 
                line_qty = context['pol_qty'].get(line.id, 0.00)
3016
 
 
3017
 
            for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
3018
 
                diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line_qty, sol.product_uom.id)
3019
 
                sol_to_update.setdefault(sol.id, 0.00)
3020
 
                sol_to_update[sol.id] += diff_qty
3021
 
                if line.has_to_be_resourced:
3022
 
                    sol_obj.add_resource_line(cr, uid, sol, False, diff_qty, context=context)
3023
 
                    sol_of_po_line_resourced_ids.append(sol.id)
3024
 
                if sol.order_id.procurement_request:
3025
 
                    # UFTP-82: do not delete IR line, cancel it
3026
 
                    sol_not_to_delete_ids.append(sol.id)
3027
 
                    if sol.order_id.id not in ir_to_potentialy_cancel_ids:
3028
 
                        ir_to_potentialy_cancel_ids.append(sol.order_id.id)
3029
 
 
3030
 
        context['pol_ids'] = ids
3031
 
        # In case of cancelation and resourcing from IN cancelation
3032
 
        for sol in sol_to_update:
3033
 
            context['update_or_cancel_line_not_delete'] = sol in sol_not_to_delete_ids
3034
 
            if context.get('update_or_cancel_line_not_delete', False) or not context.get('from_in_cancel', False):
3035
 
                so_to_cancel_id = sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
3036
 
                if so_to_cancel_id:
3037
 
                    so_to_cancel_ids.append(so_to_cancel_id)
3038
 
 
3039
 
        del context['pol_ids']
3040
 
 
3041
 
        if context.get('update_or_cancel_line_not_delete', False):
3042
 
            del context['update_or_cancel_line_not_delete']
3043
 
 
3044
 
        # UFTP-82: IR and its PO is cancelled
3045
 
        # IR cancel all lines that have to be cancelled
3046
 
        # and cancel IR if all its lines cancelled
3047
 
        if ir_to_potentialy_cancel_ids:
3048
 
            for ir in self.pool.get('sale.order').browse(cr, uid, ir_to_potentialy_cancel_ids, context=context):
3049
 
                # new IR state:
3050
 
                # we change his state to 'cancel' if at least one line cancelled
3051
 
                # we change his state to 'done' if all lines cancelled and resourced
3052
 
                # else NO CHANGE
3053
 
                ir_new_state = 'cancel'
3054
 
                lines_to_cancel_ids = []
3055
 
                all_lines_resourced = True
3056
 
                one_line_not_cancelled = False
3057
 
 
3058
 
                # check if at least one line is cancelled
3059
 
                # or all lines cancel and resourced
3060
 
                for irl in ir.order_line:
3061
 
                    line_cancelled = False
3062
 
                    if ir.is_ir_from_po_cancel and \
3063
 
                        (irl.state == 'cancel' or irl.state == 'exception'):
3064
 
                        # note PO sourced from IR, IR cancelled line can be in 'exception' as a 'cancelled' one
3065
 
                        line_cancelled = True
3066
 
                        if irl.id not in sol_of_po_line_resourced_ids:
3067
 
                            all_lines_resourced = False  # one cancelled line not resourced
3068
 
                        if irl.state == 'exception':
3069
 
                            lines_to_cancel_ids.append(irl.id)  # to be set to cancel
3070
 
                    if not line_cancelled:
3071
 
                        ir_new_state = False  # no cancelled line left, then no change
3072
 
                if ir_new_state and all_lines_resourced:
3073
 
                    # 'state change' flaged and all line resourced, state to done
3074
 
                    ir_new_state = 'done'
3075
 
 
3076
 
                if lines_to_cancel_ids:
3077
 
                    self.pool.get('sale.order.line').write(cr, uid, lines_to_cancel_ids,
3078
 
                        {'state': ir_new_state if ir_new_state else 'cancel'},
3079
 
                        context=context)
3080
 
                if ir_new_state:
3081
 
                    self.pool.get('sale.order').write(cr, uid, ir.id,
3082
 
                        {'state':  ir_new_state}, context=context)
3083
 
 
3084
 
        return so_to_cancel_ids
3085
 
 
3086
 
    def fake_unlink(self, cr, uid, ids, context=None):
3087
 
        '''
3088
 
        Add an entry to cancel (and resource if needed) the line when the
3089
 
        PO will be confirmed
3090
 
        '''
3091
 
        proc_obj = self.pool.get('procurement.order')
3092
 
 
3093
 
        if context is None:
3094
 
            context = {}
3095
 
 
3096
 
        if isinstance(ids, (int, long)):
3097
 
            ids = [ids]
3098
 
 
3099
 
        so_to_cancel = []
3100
 
 
3101
 
        proc_ids = []
3102
 
        purchase_ids = []
3103
 
        line_to_cancel = []
3104
 
 
3105
 
        for line in self.browse(cr, uid, ids, context=context):
3106
 
            # Set the procurement orders to delete
3107
 
            # Set the list of linked purchase orders
3108
 
            if line.procurement_id:
3109
 
                proc_ids.append(line.procurement_id.id)
3110
 
            if line.order_id.id not in purchase_ids:
3111
 
                purchase_ids.append(line.order_id.id)
3112
 
 
3113
 
            if not self.pool.get('sale.order.line.cancel').search(cr, uid, [
3114
 
                ('sync_order_line_db_id', '=', line.sync_order_line_db_id),
3115
 
            ], context=context):
3116
 
                so_to_cancel = self.cancel_sol(cr, uid, [line.id], context=context)
3117
 
 
3118
 
            # we want to skip resequencing because unlink is performed on merged purchase order lines
3119
 
            tmp_Resequencing = context.get('skipResequencing', False)
3120
 
            context['skipResequencing'] = True
3121
 
            self._update_merged_line(cr, uid, line.id, False, context=context)
3122
 
            context['skipResequencing'] = tmp_Resequencing
3123
 
 
3124
 
            line_to_cancel.append(line.id)
3125
 
 
3126
 
        # Cancel the listed procurement orders
3127
 
        for proc_id in proc_ids:
3128
 
            if not self.search(cr, uid, [
3129
 
                ('order_id.state', '!=', 'split'),
3130
 
                ('id', 'not in', ids),
3131
 
                ('procurement_id', '=', proc_id)], context=context):
3132
 
                proc_obj.action_cancel(cr, uid, [proc_id])
3133
 
 
3134
 
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
3135
 
        self.unlink(cr, uid, line_to_cancel, context=context)
3136
 
 
3137
 
        return so_to_cancel
 
831
        
 
832
        for line in self.browse(cr, uid, ids, context=context):
 
833
            if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok:
 
834
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
 
835
            
 
836
            if vals.get('price_unit', line.price_unit) == 0.00 and not line.order_id.rfq_ok:
 
837
                raise osv.except_osv(_('Error'), _('You cannot save a line with no unit price !'))
 
838
        
 
839
        if not context.get('update_merge'):
 
840
            for line in ids:
 
841
                vals = self._update_merged_line(cr, uid, line, vals, context=context)
 
842
 
 
843
        return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
3138
844
 
3139
845
    def unlink(self, cr, uid, ids, context=None):
3140
846
        '''
3141
847
        Update the merged line
3142
848
        '''
3143
 
        po_obj = self.pool.get('purchase.order')
3144
 
        wf_service = netsvc.LocalService("workflow")
3145
 
 
3146
 
        if context is None:
 
849
        if not context:
3147
850
            context = {}
3148
851
 
3149
852
        if isinstance(ids, (int, long)):
3150
853
            ids = [ids]
3151
854
 
3152
 
        order_ids = []
3153
 
        for line in self.read(cr, uid, ids, ['id', 'order_id'], context=context):
3154
 
            # we want to skip resequencing because unlink is performed on merged purchase order lines
3155
 
            tmp_skip_resourcing = context.get('skipResourcing', False)
3156
 
            context['skipResourcing'] = True
3157
 
            self._update_merged_line(cr, uid, line['id'], False, context=context)
3158
 
            context['skipResourcing'] = tmp_skip_resourcing
3159
 
            if line['order_id'][0] not in order_ids:
3160
 
                order_ids.append(line['order_id'][0])
3161
 
 
3162
 
        if context.get('from_del_wizard'):
3163
 
            return self.ask_unlink(cr, uid, ids, context=context)
3164
 
 
3165
 
        res = super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
3166
 
 
3167
 
        po_obj.wkf_confirm_trigger(cr, uid, order_ids, context=context)
3168
 
 
3169
 
        return res
 
855
        for line_id in ids:
 
856
            self._update_merged_line(cr, uid, line_id, False, context=context)
 
857
 
 
858
        return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
3170
859
 
3171
860
    def _get_fake_state(self, cr, uid, ids, field_name, args, context=None):
3172
861
        if isinstance(ids, (int, long)):
3175
864
        for pol in self.read(cr, uid, ids, ['state']):
3176
865
            ret[pol['id']] = pol['state']
3177
866
        return ret
3178
 
 
 
867
    
3179
868
    def _get_fake_id(self, cr, uid, ids, field_name, args, context=None):
3180
869
        if isinstance(ids, (int, long)):
3181
870
            ids = [ids]
3183
872
        for pol in self.read(cr, uid, ids, ['id']):
3184
873
            ret[pol['id']] = pol['id']
3185
874
        return ret
3186
 
 
 
875
    
3187
876
    def _get_stages_price(self, cr, uid, product_id, uom_id, order, context=None):
3188
877
        '''
3189
878
        Returns True if the product/supplier couple has more than 1 line
3190
879
        '''
3191
 
        suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', order.partner_id.id),
 
880
        suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', order.partner_id.id), 
3192
881
                                                                              ('product_id', '=', product_id)], context=context)
3193
882
        if suppinfo_ids:
3194
883
            pricelist_ids = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('currency_id', '=', order.pricelist_id.currency_id.id),
3198
887
                                                                                    ('valid_till', '>=', order.date_order)], context=context)
3199
888
            if len(pricelist_ids) > 1:
3200
889
                return True
3201
 
 
 
890
        
3202
891
        return False
3203
 
 
 
892
        
3204
893
    def _get_price_change_ok(self, cr, uid, ids, field_name, args, context=None):
3205
894
        '''
3206
895
        Returns True if the price can be changed by the user
3207
896
        '''
3208
897
        res = {}
3209
 
 
 
898
        
3210
899
        for line in self.browse(cr, uid, ids, context=context):
3211
900
            res[line.id] = True
3212
901
            stages = self._get_stages_price(cr, uid, line.product_id.id, line.product_uom.id, line.order_id, context=context)
3213
902
            if line.merged_id and len(line.merged_id.order_line_ids) > 1 and line.order_id.state != 'confirmed' and stages and not line.order_id.rfq_ok:
3214
903
                res[line.id] = False
3215
 
 
3216
 
        return res
3217
 
 
3218
 
    def on_change_select_fo(self, cr, uid, ids, fo_id, context=None):
3219
 
        '''
3220
 
        Fill the origin field if a FO is selected
3221
 
        '''
3222
 
        so_obj = self.pool.get('sale.order')
3223
 
        if fo_id:
3224
 
            fo = so_obj.browse(cr, uid, fo_id, context=context)
3225
 
            res = {'value': {'origin': fo.name,
3226
 
                             'display_sync_ref': len(fo.sourced_references) and True or False,
3227
 
                             'select_fo': False}}
3228
 
            return res
3229
 
 
3230
 
        return {
3231
 
            'value': {
3232
 
                'display_sync_ref': False,
3233
 
            },
3234
 
        }
3235
 
 
3236
 
    def on_change_origin(self, cr, uid, ids, origin, procurement_id=False, partner_type='external', context=None):
3237
 
        '''
3238
 
        Check if the origin is a known FO/IR
3239
 
        '''
3240
 
        res = {}
3241
 
        if not procurement_id and origin:
3242
 
            domain = [('name', '=', origin), ('state', 'in', ('sourced', 'progres', 'manual'))]
3243
 
            o_type = 'a Non-ESC'
3244
 
            if partner_type == 'esc':
3245
 
                o_type = 'an ESC'
3246
 
                domain.append(('split_type_sale_order', '=', 'esc_split_sale_order'))
3247
 
            else:
3248
 
                domain.append(('split_type_sale_order', '=', 'local_purchase_split_sale_order'))
3249
 
            sale_id = self.pool.get('sale.order').search(cr, uid, domain, context=context)
3250
 
            if not sale_id:
3251
 
                res['warning'] = {'title': _('Warning'),
3252
 
                                  'message': _('The reference \'%s\' put in the Origin field doesn\'t match with a confirmed FO/IR sourced with %s supplier. No FO/IR line will be created for this PO line') % (origin, o_type)}
3253
 
                res['value'] = {
3254
 
                    'display_sync_ref': False,
3255
 
                    'instance_sync_order_ref': '',
3256
 
                }
3257
 
            else:
3258
 
                res['value'] = {
3259
 
                    'display_sync_ref': True,
3260
 
                }
3261
 
 
3262
 
        return res
3263
 
 
3264
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
3265
 
        '''
3266
 
        multi fields function method
3267
 
        '''
3268
 
        # Some verifications
3269
 
        if context is None:
3270
 
            context = {}
3271
 
        if isinstance(ids, (int, long)):
3272
 
            ids = [ids]
3273
 
 
3274
 
        result = {}
3275
 
        for obj in self.browse(cr, uid, ids, context=context):
3276
 
            # default values
3277
 
            result[obj.id] = {'order_state_purchase_order_line': False}
3278
 
            # order_state_purchase_order_line
3279
 
            if obj.order_id:
3280
 
                result[obj.id].update({'order_state_purchase_order_line': obj.order_id.state})
3281
 
 
3282
 
        return result
3283
 
 
3284
 
    def _get_project_po_ref(self, cr, uid, ids, field_name, args, context=None):
3285
 
        '''
3286
 
        Return the name of the PO at project side
3287
 
        '''
3288
 
        if isinstance(ids, (int, long)):
3289
 
            ids = [ids]
3290
 
 
3291
 
        res = {}
3292
 
        for line_id in ids:
3293
 
            res[line_id] = ''
3294
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, line_id, context=context)
3295
 
            for sol in self.pool.get('sale.order.line').browse(cr, uid, sol_ids, context=context):
3296
 
                if sol.order_id and sol.order_id.client_order_ref:
3297
 
                    if res[line_id]:
3298
 
                        res[line_id] += ' - '
3299
 
                    res[line_id] += sol.order_id.client_order_ref
3300
 
 
3301
 
        return res
3302
 
 
3303
 
    def _get_link_sol_id(self, cr, uid, ids, field_name, args, context=None):
3304
 
        """
3305
 
        Return the ID of the first FO line sourced by this PO line
3306
 
        """
3307
 
        if isinstance(ids, (int, long)):
3308
 
            ids = [ids]
3309
 
 
3310
 
        res = {}
3311
 
        for line_id in ids:
3312
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line_id], context=context)
3313
 
            if sol_ids:
3314
 
                res[line_id] = sol_ids[0]
3315
 
 
 
904
                        
3316
905
        return res
3317
906
 
3318
907
    _columns = {
3319
 
        'is_line_split': fields.boolean(string='This line is a split line?'), # UTP-972: Use boolean to indicate if the line is a split line
 
908
        'parent_line_id': fields.many2one('purchase.order.line', string='Parent line'),
3320
909
        'merged_id': fields.many2one('purchase.order.merged.line', string='Merged line'),
3321
 
        'origin': fields.char(size=512, string='Origin'),
3322
 
        'link_so_id': fields.many2one('sale.order', string='Linked FO/IR', readonly=True),
3323
 
        'dpo_received': fields.boolean(string='Is the IN has been received at Project side ?'),
 
910
        'origin': fields.char(size=64, string='Origin'),
3324
911
        'change_price_ok': fields.function(_get_price_change_ok, type='boolean', method=True, string='Price changing'),
3325
912
        'change_price_manually': fields.boolean(string='Update price manually'),
3326
913
        # openerp bug: eval invisible in p.o use the po line state and not the po state !
3327
914
        'fake_state': fields.function(_get_fake_state, type='char', method=True, string='State', help='for internal use only'),
3328
915
        # openerp bug: id is not given to onchanqge call if we are into one2many view
3329
916
        'fake_id':fields.function(_get_fake_id, type='integer', method=True, string='Id', help='for internal use only'),
3330
 
        'old_price_unit': fields.float(string='Old price', digits_compute=dp.get_precision('Purchase Price Computation')),
3331
 
        'order_state_purchase_order_line': fields.function(_vals_get, method=True, type='selection', selection=PURCHASE_ORDER_STATE_SELECTION, string='State of Po', multi='get_vals_purchase_override', store=False, readonly=True),
3332
 
 
3333
 
        # This field is used to identify the FO PO line between 2 instances of the sync
3334
 
        'sync_order_line_db_id': fields.text(string='Sync order line DB Id', required=False, readonly=True),
3335
 
        'external_ref': fields.char(size=256, string='Ext. Ref.'),
3336
 
        'project_ref': fields.char(size=256, string='Project Ref.'),
3337
 
        'has_to_be_resourced': fields.boolean(string='Has to be re-sourced'),
3338
 
        'select_fo': fields.many2one('sale.order', string='FO'),
3339
 
        'fnct_project_ref': fields.function(_get_project_po_ref, method=True, string='Project PO',
3340
 
                                            type='char', size=128, store=False),
3341
 
        'from_fo': fields.boolean(string='From FO', readonly=True),
3342
 
        'display_sync_ref': fields.boolean(string='Display sync. ref.'),
3343
 
        'instance_sync_order_ref': fields.many2one(
3344
 
            'sync.order.label',
3345
 
            string='Order in sync. instance',
3346
 
        ),
3347
 
        'link_sol_id': fields.function(
3348
 
            _get_link_sol_id,
3349
 
            method=True,
3350
 
            type='many2one',
3351
 
            relation='sale.order.line',
3352
 
            string='Linked FO line',
3353
 
            store=False,
3354
 
        ),
 
917
        'old_price_unit': fields.float(digits=(16,2), string='Old price'),
3355
918
    }
3356
919
 
3357
920
    _defaults = {
3359
922
        'product_qty': lambda *a: 0.00,
3360
923
        'price_unit': lambda *a: 0.00,
3361
924
        'change_price_ok': lambda *a: True,
3362
 
        'is_line_split': False, # UTP-972: by default not a split line
3363
 
        'from_fo': lambda self, cr, uid, c: not c.get('rfq_ok', False) and c.get('from_fo', False),
3364
925
    }
3365
 
 
 
926
    
3366
927
    def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
3367
928
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
3368
929
            name=False, price_unit=False, notes=False):
3370
931
        res = super(purchase_order_line, self).product_uom_change(cr, uid, ids, pricelist, product, qty, uom,
3371
932
                                                                  partner_id, date_order, fiscal_position, date_planned,
3372
933
                                                                  name, price_unit, notes)
3373
 
        if not product:
3374
 
            return res
3375
934
        res['value'].update({'product_qty': 0.00})
3376
935
        res.update({'warning': {}})
3377
 
 
 
936
        
3378
937
        return res
3379
 
 
 
938
    
3380
939
    def product_id_on_change(self, cr, uid, ids, pricelist, product, qty, uom,
3381
940
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
3382
 
            name=False, price_unit=False, notes=False, state=False, old_price_unit=False,
3383
 
            nomen_manda_0=False, comment=False, context=None):
3384
 
        all_qty = qty
3385
 
        partner_price = self.pool.get('pricelist.partnerinfo')
3386
 
        product_obj = self.pool.get('product.product')
3387
 
 
3388
 
        if not context:
3389
 
            context = {}
3390
 
 
3391
 
        # If the user modify a line, remove the old quantity for the total quantity
3392
 
        if ids:
3393
 
            for line_id in self.browse(cr, uid, ids, context=context):
3394
 
                all_qty -= line_id.product_qty
3395
 
 
3396
 
        if product and not uom:
3397
 
            uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
3398
 
 
3399
 
        if context and context.get('purchase_id') and state == 'draft' and product:
3400
 
            domain = [('product_id', '=', product),
3401
 
                      ('product_uom', '=', uom),
3402
 
                      ('order_id', '=', context.get('purchase_id'))]
3403
 
            other_lines = self.search(cr, uid, domain)
3404
 
            for l in self.browse(cr, uid, other_lines):
3405
 
                all_qty += l.product_qty
3406
 
 
3407
 
        res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, all_qty, uom,
3408
 
                                                                 partner_id, date_order, fiscal_position,
 
941
            name=False, price_unit=False, notes=False, state=False, old_price_unit=False):
 
942
        res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom,
 
943
                                                                 partner_id, date_order, fiscal_position, 
3409
944
                                                                 date_planned, name, price_unit, notes)
3410
 
 
 
945
        
 
946
        # Remove the warning message if the product has no staged pricelist
 
947
#        if res.get('warning'):
 
948
#            supplier_info = self.pool.get('product.supplierinfo').search(cr, uid, [('product_id', '=', product)])
 
949
#            product_pricelist = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('suppinfo_id', 'in', supplier_info)])
 
950
#            if not product_pricelist:
 
951
#                res['warning'] = {}
3411
952
        if res.get('warning', {}).get('title', '') == 'No valid pricelist line found !' or qty == 0.00:
3412
953
            res.update({'warning': {}})
3413
 
 
3414
 
        func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
3415
 
        if pricelist:
3416
 
            currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
3417
 
        else:
3418
 
            currency_id = func_curr_id
3419
 
 
3420
 
        if product and partner_id:
3421
 
            # Test the compatibility of the product with a the partner of the order
3422
 
            res, test = product_obj._on_change_restriction_error(cr, uid, product, field_name='product_id', values=res, vals={'partner_id': partner_id}, context=context)
3423
 
            if test:
3424
 
                return res
3425
 
 
3426
 
        # Update the old price value
 
954
        
 
955
        # Update the old price value        
3427
956
        res['value'].update({'product_qty': qty})
3428
 
        if product and not res.get('value', {}).get('price_unit', False) and all_qty != 0.00 and qty != 0.00:
 
957
        if not res.get('value', {}).get('price_unit', False) and qty != 0.00:
3429
958
            # Display a warning message if the quantity is under the minimal qty of the supplier
3430
 
            currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
3431
 
            tmpl_id = self.pool.get('product.product').read(cr, uid, product, ['product_tmpl_id'])['product_tmpl_id'][0]
3432
 
            info_prices = []
3433
 
            domain = [('uom_id', '=', uom),
3434
 
                      ('partner_id', '=', partner_id),
3435
 
                      ('product_id', '=', tmpl_id),
3436
 
                      '|', ('valid_from', '<=', date_order),
3437
 
                      ('valid_from', '=', False),
3438
 
                      '|', ('valid_till', '>=', date_order),
3439
 
                      ('valid_till', '=', False)]
3440
 
 
3441
 
            domain_cur = [('currency_id', '=', currency_id)]
3442
 
            domain_cur.extend(domain)
3443
 
 
3444
 
            info_prices = partner_price.search(cr, uid, domain_cur, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
3445
 
            if not info_prices:
3446
 
                info_prices = partner_price.search(cr, uid, domain, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
3447
 
 
3448
 
            if info_prices:
3449
 
                info_price = partner_price.browse(cr, uid, info_prices[0], context=context)
3450
 
                info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price, round=False, context=context)
3451
 
                res['value'].update({'old_price_unit': info_u_price, 'price_unit': info_u_price})
3452
 
                res.update({'warning': {'title': _('Warning'), 'message': _('The product unit price has been set ' \
3453
 
                                                                                'for a minimal quantity of %s (the min quantity of the price list), '\
3454
 
                                                                                'it might change at the supplier confirmation.') % info_price.min_quantity}})
3455
 
                if info_price.rounding and all_qty%info_price.rounding != 0:
3456
 
                    message = _('A rounding value of %s UoM has been set for ' \
3457
 
                            'this product, you should than modify ' \
3458
 
                            'the quantity ordered to match the supplier criteria.') % info_price.rounding
3459
 
                    message = '%s \n %s' % (res.get('warning', {}).get('message', ''), message)
3460
 
                    res['warning'].update({'message': message})
 
959
            suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', partner_id), 
 
960
                                                                              ('product_id', '=', product)])
 
961
            if suppinfo_ids:
 
962
                currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
 
963
                pricelist_ids = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('currency_id', '=', currency_id),
 
964
                                                                                        ('suppinfo_id', 'in', suppinfo_ids),
 
965
                                                                                        ('uom_id', '=', uom),
 
966
                                                                                        '|', ('valid_till', '=', False),
 
967
                                                                                        ('valid_till', '>=', date_order)], order='min_quantity')
 
968
                if pricelist_ids:
 
969
                    pricelist = self.pool.get('pricelist.partnerinfo').browse(cr, uid, pricelist_ids[0])
 
970
                    res['value'].update({'old_price_unit': pricelist.price, 'price_unit': pricelist.price})
 
971
                    res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier has a minimal ' \
 
972
                                                                                'quantity set to %s, you cannot purchase less.') % pricelist.min_quantity}})
 
973
                else:
 
974
                    res['value'].update({'old_price_unit': res['value']['price_unit']})
3461
975
            else:
3462
 
                old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'], round=False, context=context)
3463
 
                res['value'].update({'old_price_unit': old_price})
 
976
                res['value'].update({'old_price_unit': res['value']['price_unit']})
3464
977
        else:
3465
 
            old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'), round=False, context=context)
3466
 
            res['value'].update({'old_price_unit': old_price})
3467
 
 
 
978
            res['value'].update({'old_price_unit': res.get('value').get('price_unit')})
 
979
                
3468
980
        # Set the unit price with cost price if the product has no staged pricelist
3469
 
        if product and qty != 0.00:
3470
 
            res['value'].update({'comment': False, 'nomen_manda_0': False, 'nomen_manda_1': False,
3471
 
                                 'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False,
3472
 
                                 'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False,
3473
 
                                 'nomen_sub_4': False, 'nomen_sub_5': False})
3474
 
            st_uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
 
981
        if product and qty != 0.00: 
3475
982
            st_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
3476
 
            st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price, round=False, context=context)
3477
 
            st_price = self.pool.get('product.uom')._compute_price(cr, uid, st_uom, st_price, uom)
3478
 
 
 
983
        
3479
984
            if res.get('value', {}).get('price_unit', False) == False and (state and state == 'draft') or not state :
3480
985
                res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
3481
986
            elif state and state != 'draft' and old_price_unit:
3482
987
                res['value'].update({'price_unit': old_price_unit, 'old_price_unit': old_price_unit})
3483
 
 
3484
 
            if res['value']['price_unit'] == 0.00:
3485
 
                res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
3486
 
 
3487
988
        elif qty == 0.00:
3488
989
            res['value'].update({'price_unit': 0.00, 'old_price_unit': 0.00})
3489
 
        elif not product and not comment and not nomen_manda_0:
3490
 
            res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
3491
 
 
3492
 
 
3493
 
        if context and context.get('categ') and product:
3494
 
            # Check consistency of product
3495
 
            consistency_message = self.pool.get('product.product').check_consistency(cr, uid, product, context.get('categ'), context=context)
3496
 
            if consistency_message:
3497
 
                res.setdefault('warning', {})
3498
 
                res['warning'].setdefault('title', 'Warning')
3499
 
                res['warning'].setdefault('message', '')
3500
 
 
3501
 
                res['warning']['message'] = '%s \n %s' % (res.get('warning', {}).get('message', ''), consistency_message)
3502
 
 
 
990
        elif not product:
 
991
            res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})            
 
992
        
3503
993
        return res
3504
994
 
3505
 
    def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id,
3506
 
                          product_uom, product_qty, pricelist, partner_id, date_order,
3507
 
                          change_price_ok, state, old_price_unit,
3508
 
                          nomen_manda_0=False, comment=False, context=None):
 
995
    def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id, product_uom, product_qty, pricelist, partner_id, date_order, change_price_ok, state, old_price_unit, context=None):
3509
996
        '''
3510
997
        Display a warning message on change price unit if there are other lines with the same product and the same uom
3511
998
        '''
3513
1000
 
3514
1001
        if context is None:
3515
1002
            context = {}
3516
 
 
3517
1003
        if not product_id or not product_uom or not product_qty:
3518
1004
            return res
3519
 
 
 
1005
        
3520
1006
        order_id = context.get('purchase_id', False)
3521
1007
        if not order_id:
3522
1008
            return res
3524
1010
        order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
3525
1011
        other_lines = self.search(cr, uid, [('id', '!=', fake_id), ('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
3526
1012
        stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
3527
 
 
 
1013
        
3528
1014
        if not change_price_ok or (other_lines and stages and order.state != 'confirmed' and not context.get('rfq_ok')):
3529
1015
            res.update({'warning': {'title': 'Error',
3530
1016
                                    'message': 'This product get stages prices for this supplier, you cannot change the price manually in draft state '\
3535
1021
 
3536
1022
        return res
3537
1023
 
3538
 
    def get_exp_sol_ids_from_pol_ids(self, cr, uid, ids, context=None, po_line=False):
3539
 
        """
3540
 
        input: purchase order line ids
3541
 
        return: expected sale order line ids
3542
 
        """
3543
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
3544
 
        so_obj = self.pool.get('sale.order')
3545
 
 
3546
 
        if context is None:
3547
 
            context = {}
3548
 
 
3549
 
        if isinstance(ids, (int, long)):
3550
 
            ids = [ids]
3551
 
 
3552
 
        if po_line and isinstance(po_line, (int, long)):
3553
 
            po_line = [po_line]
3554
 
 
3555
 
        so_name = []
3556
 
        for line in self.read(cr, uid, ids, ['origin'], context=context):
3557
 
            if line['origin'] and line['origin'] not in so_name:
3558
 
                so_name.append(line['origin'])
3559
 
 
3560
 
        so_ids = so_obj.search(cr, uid, [('name', 'in', so_name)], context=context)
3561
 
        exp_sol_domain = [('order_id', 'in', so_ids)]
3562
 
        if po_line:
3563
 
            exp_sol_domain.append(('po_line_id', 'not in', po_line))
3564
 
 
3565
 
        return exp_sol_obj.search(cr, uid, exp_sol_domain, context=context)
3566
 
 
3567
 
    def get_sol_ids_from_pol_ids(self, cr, uid, ids, context=None):
3568
 
        '''
3569
 
        input: purchase order line ids
3570
 
        return: sale order line ids
3571
 
        '''
3572
 
        # Some verifications
3573
 
        if not context:
3574
 
            context = {}
3575
 
        if isinstance(ids, (int, long)):
3576
 
            ids = [ids]
3577
 
 
3578
 
        # objects
3579
 
        sol_obj = self.pool.get('sale.order.line')
3580
 
        # procurement ids list
3581
 
        proc_ids = []
3582
 
        # sale order lines list
3583
 
        sol_ids = []
3584
 
 
3585
 
        for line in self.browse(cr, uid, ids, context=context):
3586
 
            if line.procurement_id:
3587
 
                proc_ids.append(line.procurement_id.id)
3588
 
        # get the corresponding sale order line list
3589
 
        if proc_ids:
3590
 
            sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
3591
 
        return sol_ids
3592
1024
 
3593
1025
    def open_split_wizard(self, cr, uid, ids, context=None):
3594
1026
        '''
3596
1028
        '''
3597
1029
        if not context:
3598
1030
            context = {}
3599
 
 
 
1031
 
3600
1032
        if isinstance(ids, (int, long)):
3601
1033
            ids = [ids]
3602
1034
 
3614
1046
 
3615
1047
purchase_order_line()
3616
1048
 
3617
 
class purchase_order_group(osv.osv_memory):
3618
 
    _name = "purchase.order.group"
3619
 
    _inherit = "purchase.order.group"
3620
 
    _description = "Purchase Order Merge"
3621
 
 
3622
 
    _columns = {
3623
 
        'po_value_id': fields.many2one('purchase.order', string='Template PO', help='All values in this PO will be used as default values for the merged PO'),
3624
 
        'unmatched_categ': fields.boolean(string='Unmatched categories'),
3625
 
    }
3626
 
 
3627
 
    def default_get(self, cr, uid, fields, context=None):
3628
 
        res = super(purchase_order_group, self).default_get(cr, uid, fields, context=context)
3629
 
        if context.get('active_model','') == 'purchase.order' and len(context['active_ids']) < 2:
3630
 
            raise osv.except_osv(_('Warning'),
3631
 
            _('Please select multiple order to merge in the list view.'))
3632
 
 
3633
 
        res['po_value_id'] = context['active_ids'][-1]
3634
 
 
3635
 
        categories = set()
3636
 
        for po in self.pool.get('purchase.order').read(cr, uid, context['active_ids'], ['categ'], context=context):
3637
 
            categories.add(po['categ'])
3638
 
 
3639
 
        if len(categories) > 1:
3640
 
            res['unmatched_categ'] = True
3641
 
 
3642
 
        return res
3643
 
 
3644
 
    def merge_orders(self, cr, uid, ids, context=None):
3645
 
        res = super(purchase_order_group, self).merge_orders(cr, uid, ids, context=context)
3646
 
        res.update({'context': {'search_default_draft': 1, 'search_default_approved': 0,'search_default_create_uid':uid, 'purchase_order': True}})
3647
 
 
3648
 
        if 'domain' in res and eval(res['domain'])[0][2]:
3649
 
            return res
3650
 
 
3651
 
        raise osv.except_osv(_('Error'), _('No PO merged !'))
3652
 
        return {'type': 'ir.actions.act_window_close'}
3653
 
 
3654
 
purchase_order_group()
3655
 
 
3656
 
class product_product(osv.osv):
3657
 
    _name = 'product.product'
3658
 
    _inherit = 'product.product'
3659
 
 
3660
 
    def _product_price(self, cr, uid, ids, field_name, args, context=None):
3661
 
        res = super(product_product, self)._product_price(cr, uid, ids, field_name, args, context=context)
3662
 
 
3663
 
        for product in res:
3664
 
            if res[product] == 0.00:
3665
 
                try:
3666
 
                    res[product] = self.pool.get('product.product').read(cr, uid, [product], ['standard_price'], context=context)[0]['standard_price']
3667
 
                except:
3668
 
                    pass
3669
 
 
3670
 
        return res
3671
 
 
3672
 
    def _get_purchase_type(self, cr, uid, ids, field_name, args, context=None):
3673
 
        res = {}
3674
 
        for p_id in ids:
3675
 
            res[p_id] = True
3676
 
 
3677
 
        return res
3678
 
 
3679
 
    def _src_purchase_type(self, cr, uid, obj, name, args, context=None):
3680
 
        '''
3681
 
        Returns a domain according to the PO type
3682
 
        '''
3683
 
        res = []
3684
 
        for arg in args:
3685
 
            if arg[0] == 'purchase_type':
3686
 
                if arg[1] != '=':
3687
 
                    raise osv.except_osv(_('Error'), _('Only the \'=\' operator is allowed.'))
3688
 
                # Returns all service products
3689
 
                if arg[2] == 'service':
3690
 
                    res.append(('type', '=', 'service_recep'))
3691
 
                elif arg[2] == 'transport':
3692
 
                    res.append(('transport_ok', '=', True))
3693
 
 
3694
 
        return res
3695
 
 
3696
 
    _columns = {
3697
 
 
3698
 
        'purchase_type': fields.function(_get_purchase_type, fnct_search=_src_purchase_type, type='boolean', string='Purchase type', method=True, store=False),
3699
 
        'price': fields.function(_product_price, method=True, type='float', string='Pricelist', digits_compute=dp.get_precision('Sale Price')),
3700
 
    }
3701
 
 
3702
 
    def check_consistency(self, cr, uid, product_id, category, context=None):
3703
 
        '''
3704
 
        Check the consistency of product according to category
3705
 
        '''
3706
 
        context = context is None and {} or context
3707
 
        display_message = False
3708
 
 
3709
 
        # No check for Other
3710
 
        if category == 'other':
3711
 
            return False
3712
 
 
3713
 
        product = self.read(cr, uid, product_id, ['nomen_manda_0', 'type', 'transport_ok'], context=context)
3714
 
        transport_product = product['transport_ok']
3715
 
        product_type = product['type']
3716
 
        main_type = product['nomen_manda_0'][0]
3717
 
 
3718
 
        if category == 'medical':
3719
 
            try:
3720
 
                med_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'MED')], context=context)[0]
3721
 
            except IndexError:
3722
 
                raise osv.except_osv(_('Error'), _('MED nomenclature Main Type not found'))
3723
 
 
3724
 
            if main_type != med_nomen:
3725
 
                display_message = True
3726
 
 
3727
 
        if category == 'log':
3728
 
            try:
3729
 
                log_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'LOG')], context=context)[0]
3730
 
            except IndexError:
3731
 
                raise osv.except_osv(_('Error'), _('LOG nomenclature Main Type not found'))
3732
 
 
3733
 
            if main_type != log_nomen:
3734
 
                display_message = True
3735
 
 
3736
 
        if category == 'service' and product_type != 'service_recep':
3737
 
            display_message = True
3738
 
 
3739
 
        if category == 'transport' and (product_type != 'service_recep' or not transport_product):
3740
 
            display_message = True
3741
 
 
3742
 
        if display_message:
3743
 
            return 'Warning you are about to add a product which does not conform to this PO’s order category, do you wish to proceed ?'
3744
 
        else:
3745
 
            return False
3746
 
 
3747
 
product_product()
3748
 
 
3749
 
 
3750
 
class purchase_order_line_unlink_wizard(osv.osv_memory):
3751
 
    _name = 'purchase.order.line.unlink.wizard'
3752
 
 
3753
 
    def _get_last_line(self, cr, uid, ids, field_name, args, context=None):
3754
 
        """
3755
 
        Return True if the line is the last line to confirm before confirm
3756
 
        all other PO.
3757
 
        """
3758
 
        po_obj = self.pool.get('purchase.order')
3759
 
        pol_obj = self.pool.get('purchase.order.line')
3760
 
        sol_obj = self.pool.get('sale.order.line')
3761
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
3762
 
        so_obj = self.pool.get('sale.order')
3763
 
 
3764
 
        if context is None:
3765
 
            context = {}
3766
 
 
3767
 
        if isinstance(ids, (int, long)):
3768
 
            ids = [ids]
3769
 
 
3770
 
        res = {}
3771
 
        for wiz in self.browse(cr, uid, ids, context=context):
3772
 
            res[wiz.id] = False
3773
 
 
3774
 
            # Add a check to avoid error in server log
3775
 
            if not pol_obj.search(cr, uid, [('id', '=', wiz.line_id.id)], context=context):
3776
 
                continue
3777
 
 
3778
 
            exp_sol_ids = pol_obj.get_exp_sol_ids_from_pol_ids(cr, uid, [wiz.line_id.id], context=context, po_line=wiz.line_id.id)
3779
 
 
3780
 
            if wiz.line_id.procurement_id:
3781
 
                order_id = wiz.line_id.order_id.id
3782
 
                po_so_ids, po_ids, so_ids, sol_nc_ids = po_obj.sourcing_document_state(cr, uid, [order_id], context=context)
3783
 
                sol_ids = sol_obj.search(cr, uid, [('procurement_id', '=', wiz.line_id.procurement_id.id)], context=context)
3784
 
 
3785
 
                if order_id in po_ids:
3786
 
                    po_ids.remove(order_id)
3787
 
                for sol_id in sol_ids:
3788
 
                    if sol_id in sol_nc_ids:
3789
 
                        sol_nc_ids.remove(sol_id)
3790
 
 
3791
 
                if po_ids and not sol_nc_ids and not exp_sol_ids:
3792
 
                    res[wiz.id] = True
3793
 
            elif wiz.line_id.origin and not exp_sol_ids:
3794
 
                res[wiz.id] = True
3795
 
 
3796
 
        return res
3797
 
 
3798
 
    _columns = {
3799
 
        'line_id': fields.many2one('purchase.order.line', 'Line to delete'),
3800
 
        'last_line': fields.function(
3801
 
            _get_last_line,
3802
 
            method=True,
3803
 
            type='boolean',
3804
 
            string='Last line to confirm',
3805
 
            readonly=True,
3806
 
            store=False,
3807
 
        ),
3808
 
        'only_exp': fields.boolean(
3809
 
            string='Remains only expected FO/IR lines',
3810
 
            readonly=True,
3811
 
        ),
3812
 
    }
3813
 
 
3814
 
    def just_cancel(self, cr, uid, ids, context=None):
3815
 
        '''
3816
 
        Cancel the line
3817
 
        '''
3818
 
        # Objects
3819
 
        line_obj = self.pool.get('purchase.order.line')
3820
 
        order_wiz_obj = self.pool.get('purchase.order.cancel.wizard')
3821
 
        data_obj = self.pool.get('ir.model.data')
3822
 
        po_obj = self.pool.get('purchase.order')
3823
 
        so_obj = self.pool.get('sale.order')
3824
 
 
3825
 
        # Variables
3826
 
        if context is None:
3827
 
            context = {}
3828
 
 
3829
 
        if isinstance(ids, (int, long)):
3830
 
            ids = [ids]
3831
 
 
3832
 
        line_ids =[]
3833
 
        po_ids = set()
3834
 
        for wiz in self.browse(cr, uid, ids, context=context):
3835
 
            po_ids.add(wiz.line_id.order_id.id)
3836
 
            line_ids.append(wiz.line_id.id)
3837
 
 
3838
 
        if context.get('has_to_be_resourced'):
3839
 
            line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3840
 
 
3841
 
        so_to_cancel_ids = line_obj.fake_unlink(cr, uid, line_ids, context=context)
3842
 
 
3843
 
        if not so_to_cancel_ids:
3844
 
            return po_obj.check_empty_po(cr, uid, list(po_ids), context=context)
3845
 
        else:
3846
 
            context.update({
3847
 
                'from_po': True,
3848
 
                'po_ids': list(po_ids),
3849
 
            })
3850
 
            return so_obj.open_cancel_wizard(cr, uid, so_to_cancel_ids, context=context)
3851
 
 
3852
 
        return {'type': 'ir.actions.act_window_close'}
3853
 
 
3854
 
 
3855
 
    def cancel_and_resource(self, cr, uid, ids, context=None):
3856
 
        '''
3857
 
        Flag the line to be re-sourced and run cancel method
3858
 
        '''
3859
 
        # Objects
3860
 
        if context is None:
3861
 
            context = {}
3862
 
 
3863
 
        context['has_to_be_resourced'] = True
3864
 
 
3865
 
        return self.just_cancel(cr, uid, ids, context=context)
3866
 
 
3867
 
purchase_order_line_unlink_wizard()
3868
 
 
3869
 
 
3870
 
class purchase_order_cancel_wizard(osv.osv_memory):
3871
 
    _name = 'purchase.order.cancel.wizard'
3872
 
 
3873
 
    _columns = {
3874
 
        'order_id': fields.many2one(
3875
 
            'purchase.order',
3876
 
            string='Order to delete',
3877
 
        ),
3878
 
        'unlink_po': fields.boolean(
3879
 
            string='Unlink PO',
3880
 
        ),
3881
 
        'last_lines': fields.boolean(
3882
 
            string='Remove last lines of the FO',
3883
 
        ),
3884
 
    }
3885
 
 
3886
 
    def _get_last_lines(self, cr, uid, order_id, context=None):
3887
 
        """
3888
 
        Returns True if the deletion of the PO will delete the last lines
3889
 
        of the FO/IR.
3890
 
        """
3891
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
3892
 
        po_obj = self.pool.get('purchase.order')
3893
 
 
3894
 
        po_so_ids, po_ids, so_ids, sol_nc_ids = po_obj.sourcing_document_state(cr, uid, [order_id], context=context)
3895
 
        if order_id in po_ids:
3896
 
            po_ids.remove(order_id)
3897
 
 
3898
 
        exp_sol_ids = exp_sol_obj.search(cr, uid, [('order_id', 'in', po_so_ids), ('po_id', '!=', order_id)], context=context)
3899
 
 
3900
 
        if not exp_sol_ids and not po_ids:
3901
 
            return True
3902
 
 
3903
 
        return False
3904
 
 
3905
 
    def fields_view_get(self, cr, uid, view_id=False, view_type='form', context=None, toolbar=False, submenu=False):
3906
 
        return super(purchase_order_cancel_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
3907
 
 
3908
 
    def ask_unlink(self, cr, uid, order_id, context=None):
3909
 
        '''
3910
 
        Return the wizard
3911
 
        '''
3912
 
        data_obj = self.pool.get('ir.model.data')
3913
 
 
3914
 
        if context is None:
3915
 
            context = {}
3916
 
 
3917
 
        if self._name == 'rfq.cancel.wizard':
3918
 
            view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_rfq_cancel_wizard_form_view')[1]
3919
 
        else:
3920
 
            view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3921
 
        wiz_id = self.create(cr, uid, {'order_id': order_id}, context=context)
3922
 
 
3923
 
        return {'type': 'ir.actions.act_window',
3924
 
                'res_model': 'purchase.order.cancel.wizard',
3925
 
                'res_id': wiz_id,
3926
 
                'view_id': [view_id],
3927
 
                'view_type': 'form',
3928
 
                'view_mode': 'form',
3929
 
                'target': 'new',
3930
 
                'context': context}
3931
 
 
3932
 
    def close_window(self, cr, uid, ids, context=None):
3933
 
        '''
3934
 
        Close the pop-up and reload the PO
3935
 
        '''
3936
 
        return {'type': 'ir.actions.act_window_close'}
3937
 
 
3938
 
    def cancel_po(self, cr, uid, ids, context=None):
3939
 
        '''
3940
 
        Cancel the PO and display his form
3941
 
        '''
3942
 
        po_obj = self.pool.get('purchase.order')
3943
 
        so_obj = self.pool.get('sale.order')
3944
 
        line_obj = self.pool.get('purchase.order.line')
3945
 
        wf_service = netsvc.LocalService("workflow")
3946
 
 
3947
 
 
3948
 
        if context is None:
3949
 
            context = {}
3950
 
 
3951
 
        if isinstance(ids, (int, long)):
3952
 
            ids = [ids]
3953
 
 
3954
 
        line_ids = []
3955
 
        order_ids = []
3956
 
        order_to_check = []
3957
 
        for wiz in self.browse(cr, uid, ids, context=context):
3958
 
            order_ids.append(wiz.order_id.id)
3959
 
            if wiz.last_lines and wiz.order_id.id not in order_to_check:
3960
 
                order_to_check.append(wiz.order_id.id)
3961
 
            if context.get('has_to_be_resourced'):
3962
 
                line_ids.extend([l.id for l in wiz.order_id.order_line])
3963
 
 
3964
 
        po_so_ids, po_ids, so_ids, sol_nc_ids = po_obj.sourcing_document_state(cr, uid, order_to_check, context=context)
3965
 
 
3966
 
        # Mark lines as 'To be resourced'
3967
 
        line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3968
 
 
3969
 
        po_obj.write(cr, uid, order_ids, {'canceled_end': True}, context=context)
3970
 
        for order_id in order_ids:
3971
 
            wf_service.trg_validate(uid, 'purchase.order', order_id, 'purchase_cancel', cr)
3972
 
 
3973
 
        if po_so_ids:
3974
 
            order_to_cancel = []
3975
 
            for so_id in po_so_ids:
3976
 
                if so_obj._get_ready_to_cancel(cr, uid, so_id, context=context)[so_id]:
3977
 
                    order_to_cancel.append(so_id)
3978
 
 
3979
 
            if order_to_cancel:
3980
 
                context.update({
3981
 
                    'from_po': True,
3982
 
                    'po_ids': list(order_to_check),
3983
 
                })
3984
 
                return so_obj.open_cancel_wizard(cr, uid, po_so_ids, context=context)
3985
 
 
3986
 
        return {'type': 'ir.actions.act_window_close'}
3987
 
 
3988
 
    def cancel_and_resource(self, cr, uid, ids, context=None):
3989
 
        if context is None:
3990
 
            context = {}
3991
 
 
3992
 
        context['has_to_be_resourced'] = True
3993
 
 
3994
 
        return self.cancel_po(cr, uid, ids, context=context)
3995
 
 
3996
 
purchase_order_cancel_wizard()
3997
 
 
3998
 
 
3999
 
class res_partner(osv.osv):
4000
 
    _inherit = 'res.partner'
4001
 
 
4002
 
    def address_multiple_get(self, cr, uid, ids, adr_pref=['default']):
4003
 
        address_obj = self.pool.get('res.partner.address')
4004
 
        address_ids = address_obj.search(cr, uid, [('partner_id', '=', ids)])
4005
 
        address_rec = address_obj.read(cr, uid, address_ids, ['type'])
4006
 
        res= {}
4007
 
        for addr in address_rec:
4008
 
            res.setdefault(addr['type'], [])
4009
 
            res[addr['type']].append(addr['id'])
4010
 
        if res:
4011
 
            default_address = res.get('default', False)
4012
 
        else:
4013
 
            default_address = False
4014
 
        result = {}
4015
 
        for a in adr_pref:
4016
 
            result[a] = res.get(a, default_address)
4017
 
 
4018
 
        return result
4019
 
 
4020
 
res_partner()
4021
 
 
4022
 
 
4023
 
class res_partner_address(osv.osv):
4024
 
    _inherit = 'res.partner.address'
4025
 
 
4026
 
    def _get_dummy(self, cr, uid, ids, field_name, args, context=None):
4027
 
        res = {}
4028
 
        for a_id in ids:
4029
 
            res[a_id] = True
4030
 
 
4031
 
        return res
4032
 
 
4033
 
    def _src_address(self, cr, uid, obj, name, args, context=None):
4034
 
        '''
4035
 
        Returns all the destination addresses of a partner or all default
4036
 
        addresses if he hasn't destination addresses
4037
 
        '''
4038
 
        partner_obj = self.pool.get('res.partner')
4039
 
        user_obj = self.pool.get('res.users')
4040
 
        res = []
4041
 
 
4042
 
        for arg in args:
4043
 
            if arg[0] == 'dest_address':
4044
 
                addr_type = 'delivery'
4045
 
            elif arg[0] == 'inv_address':
4046
 
                addr_type = 'invoice'
4047
 
 
4048
 
            if arg[2]:
4049
 
                partner_id = arg[2]
4050
 
            else:
4051
 
                partner_id = user_obj.browse(cr, uid, uid, context=context).company_id.partner_id.id
4052
 
                if arg[1] == 'in':
4053
 
                    partner_id = [partner_id]
4054
 
 
4055
 
            addr_ids = []
4056
 
            if isinstance(partner_id, list):
4057
 
                for partner in partner_id:
4058
 
                    if not partner:
4059
 
                        continue
4060
 
                    addr_ids.extend(partner_obj.address_multiple_get(cr, uid, partner, [addr_type])[addr_type])
4061
 
 
4062
 
            else:
4063
 
                addr_ids = partner_obj.address_multiple_get(cr, uid, partner_id, [addr_type])[addr_type]
4064
 
 
4065
 
            res.append(('id', 'in', list(i for i in addr_ids if i)))
4066
 
 
4067
 
        return res
4068
 
 
4069
 
    _columns = {
4070
 
        'dest_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
4071
 
                                           type='boolean', string='Dest. Address', store=False),
4072
 
        'inv_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
4073
 
                                           type='boolean', string='Invoice Address', store=False),
4074
 
    }
4075
 
 
4076
 
 
4077
 
res_partner_address()
 
1049
class account_invoice(osv.osv):
 
1050
    _name = 'account.invoice'
 
1051
    _inherit = 'account.invoice'
 
1052
    
 
1053
    _columns = {
 
1054
        'purchase_list': fields.boolean(string='Purchase List ?', help='Check this box if the invoice comes from a purchase list', readonly=True, states={'draft':[('readonly',False)]}),
 
1055
    }
 
1056
    
 
1057
account_invoice()
4078
1058
 
4079
1059
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: