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

« back to all changes in this revision

Viewing changes to sale_override/sale.py

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
20
20
##############################################################################
21
21
 
22
22
from osv import osv, fields
 
23
from osv.orm import browse_record
23
24
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
25
import netsvc
25
26
from datetime import datetime, timedelta
26
27
from dateutil.relativedelta import relativedelta
27
28
from mx.DateTime import *
28
 
from tools.translate import _ 
 
29
import time
 
30
from tools.translate import _
29
31
import logging
 
32
from workflow.wkf_expr import _eval_expr
 
33
 
 
34
import decimal_precision as dp
 
35
 
 
36
from sale_override import SALE_ORDER_STATE_SELECTION
 
37
from sale_override import SALE_ORDER_SPLIT_SELECTION
 
38
from sale_override import SALE_ORDER_LINE_STATE_SELECTION
 
39
 
30
40
 
31
41
class sale_order(osv.osv):
32
42
    _name = 'sale.order'
33
43
    _inherit = 'sale.order'
34
44
 
35
 
    def copy(self, cr, uid, id, default, context={}):
36
 
        '''
37
 
        Delete the loan_id field on the new sale.order
38
 
        '''
39
 
        return super(sale_order, self).copy(cr, uid, id, default={'loan_id': False}, context=context)
40
 
    
 
45
    """
 
46
    Other methods
 
47
    """
 
48
    def _check_browse_param(self, param, method):
 
49
        """
 
50
        Returns an error message if the parameter is not a
 
51
        browse_record instance
 
52
 
 
53
        :param param: The parameter to test
 
54
        :param method: Name of the method that call the _check_browse_param()
 
55
 
 
56
        :return True
 
57
        """
 
58
        if not isinstance(param, browse_record):
 
59
            raise osv.except_osv(
 
60
                _('Bad parameter'),
 
61
                _("""Exception when call the method '%s' of the object '%s' :
 
62
The parameter '%s' should be an browse_record instance !""") % (method, self._name, param)
 
63
            )
 
64
 
 
65
        return True
 
66
 
 
67
    def copy(self, cr, uid, id, default=None, context=None):
 
68
        """
 
69
        Copy the sale.order. When copy the sale.order:
 
70
            * re-set the sourcing logs,
 
71
            * re-set the loan_id field
 
72
            * re-set split flag to original value (field order flow) if
 
73
              not in default
 
74
 
 
75
        :param cr: Cursor to the database
 
76
        :param uid: ID of the user that runs the method
 
77
        :param order_id: ID of the sale.order to copy
 
78
        :param default: Default values to put on the new sale.order
 
79
        :param context: Context of the call
 
80
 
 
81
        :return ID of the new sale.order
 
82
        :rtype integer
 
83
        """
 
84
        if context is None:
 
85
            context = {}
 
86
 
 
87
        if default is None:
 
88
            default = {}
 
89
 
 
90
        # if the copy comes from the button duplicate
 
91
        if context.get('from_button'):
 
92
            default.update({'is_a_counterpart': False})
 
93
 
 
94
        if 'loan_id' not in default:
 
95
            default.update({'loan_id': False})
 
96
 
 
97
        default.update({
 
98
            'order_policy': 'picking',
 
99
            'active': True,
 
100
            'sourcing_trace': '',
 
101
            'sourcing_trace_ok': False,
 
102
        })
 
103
 
 
104
        if not context.get('keepClientOrder', False):
 
105
            default.update({'client_order_ref': False})
 
106
 
 
107
        # if splitting related attributes are not set with default values, we reset their values
 
108
        if 'split_type_sale_order' not in default:
 
109
            default.update({'split_type_sale_order': 'original_sale_order'})
 
110
        if 'original_so_id_sale_order' not in default:
 
111
            default.update({'original_so_id_sale_order': False})
 
112
        if 'fo_to_resource' not in default:
 
113
            default.update({'fo_to_resource': False})
 
114
        if 'parent_order_name' not in default:
 
115
            default.update({'parent_order_name': False})
 
116
 
 
117
        return super(sale_order, self).copy(cr, uid, id, default=default, context=context)
 
118
 
 
119
    def unlink(self, cr, uid, ids, context=None):
 
120
        '''
 
121
        Check if the status of the unlinked FO is allowed for unlink.
 
122
        Statuses allowed : draft / cancel
 
123
        '''
 
124
        for order in self.read(cr, uid, ids, ['state', 'procurement_request'], context=context):
 
125
            if order['state'] not in ('draft', 'cancel'):
 
126
                type = order['procurement_request'] and _('Internal Request') or _('Field order')
 
127
                raise osv.except_osv(_('Error'), _('Only Draft and Canceled %s can be deleted.') % type)
 
128
        return super(sale_order, self).unlink(cr, uid, ids, context=context)
 
129
 
 
130
    def action_cancel(self, cr, uid, ids, context=None):
 
131
        if context is None:
 
132
            context = {}
 
133
 
 
134
        context.update({'no_check_line': True})
 
135
        self.write(cr, uid, ids, {'delivery_confirmed_date': time.strftime('%Y-%m-%d')}, context=context)
 
136
        return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
 
137
 
41
138
    #@@@override sale.sale_order._invoiced
42
 
    def _invoiced(self, cr, uid, ids, name, arg, context={}):
 
139
    def _invoiced(self, cr, uid, ids, name, arg, context=None):
43
140
        '''
44
141
        Return True is the sale order is an uninvoiced order
45
142
        '''
46
143
        partner_obj = self.pool.get('res.partner')
47
144
        partner = False
48
145
        res = {}
49
 
        
 
146
 
50
147
        for sale in self.browse(cr, uid, ids):
51
148
            if sale.partner_id:
52
149
                partner = partner_obj.browse(cr, uid, [sale.partner_id.id])[0]
62
159
                    res[sale.id] = False
63
160
        return res
64
161
    #@@@end
65
 
    
 
162
 
66
163
    #@@@override sale.sale_order._invoiced_search
67
 
    def _invoiced_search(self, cursor, user, obj, name, args, context={}):
 
164
    def _invoiced_search(self, cursor, user, obj, name, args, context=None):
68
165
        if not len(args):
69
166
            return []
70
167
        clause = ''
79
176
                    no_invoiced = True
80
177
 
81
178
        cursor.execute('SELECT rel.order_id ' \
82
 
                'FROM sale_order_invoice_rel AS rel, account_invoice AS inv, sale_order AS sale, res_partner AS part '+ sale_clause + \
 
179
                'FROM sale_order_invoice_rel AS rel, account_invoice AS inv, sale_order AS sale, res_partner AS part ' + sale_clause + \
83
180
                'WHERE rel.invoice_id = inv.id AND rel.order_id = sale.id AND sale.partner_id = part.id ' + clause)
84
181
        res = cursor.fetchall()
85
182
        if no_invoiced:
95
192
            return [('id', '=', 0)]
96
193
        return [('id', 'in', [x[0] for x in res])]
97
194
    #@@@end
98
 
    
 
195
 
99
196
    #@@@override sale.sale_order._invoiced_rate
100
197
    def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
101
198
        res = {}
104
201
                res[sale.id] = 100.0
105
202
                continue
106
203
            tot = 0.0
107
 
            for invoice in sale.invoice_ids:
108
 
                if invoice.state not in ('draft', 'cancel'):
109
 
                    tot += invoice.amount_untaxed
 
204
            for line in sale.order_line:
 
205
                if line.invoiced:
 
206
                    for invoice_line in line.invoice_lines:
 
207
                        if invoice_line.invoice_id.state not in ('draft', 'cancel'):
 
208
                            tot += invoice_line.price_subtotal
110
209
            if tot:
111
210
                res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
112
211
            else:
113
212
                res[sale.id] = 0.0
114
213
        return res
115
214
    #@@@end
116
 
    
117
 
    def _get_noinvoice(self, cr, uid, ids, name, arg, context={}):
 
215
 
 
216
    def _get_noinvoice(self, cr, uid, ids, name, arg, context=None):
118
217
        res = {}
119
218
        for sale in self.browse(cr, uid, ids):
120
219
            res[sale.id] = sale.order_type != 'regular' or sale.partner_id.partner_type == 'internal'
121
220
        return res
122
 
    
 
221
 
 
222
    def _vals_get_sale_override(self, cr, uid, ids, fields, arg, context=None):
 
223
        '''
 
224
        get function values
 
225
        '''
 
226
        result = {}
 
227
        for obj in self.browse(cr, uid, ids, context=context):
 
228
            result[obj.id] = {}
 
229
            for f in fields:
 
230
                result[obj.id].update({f:False})
 
231
 
 
232
            # state_hidden_sale_order
 
233
            result[obj.id]['state_hidden_sale_order'] = obj.state
 
234
            if obj.state == 'done' and obj.split_type_sale_order == 'original_sale_order':
 
235
                result[obj.id]['state_hidden_sale_order'] = 'split_so'
 
236
 
 
237
        return result
 
238
 
 
239
    def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
 
240
        res = {}
 
241
 
 
242
        for order in self.browse(cr, uid, ids, context=context):
 
243
            res[order.id] = True
 
244
            for line in order.order_line:
 
245
                res[order.id] = False
 
246
                break
 
247
            # better: if order.order_line: res[order.id] = False
 
248
 
 
249
        return res
 
250
 
 
251
    def _get_manually_corrected(self, cr, uid, ids, field_name, args, context=None):
 
252
        res = {}
 
253
 
 
254
        for order in self.browse(cr, uid, ids, context=context):
 
255
            res[order.id] = False
 
256
            for line in order.order_line:
 
257
                if line.manually_corrected:
 
258
                    res[order.id] = True
 
259
                    break
 
260
 
 
261
        return res
 
262
 
123
263
    _columns = {
 
264
        # we increase the size of client_order_ref field from 64 to 128
 
265
        'client_order_ref': fields.char('Customer Reference', size=128),
 
266
        'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
 
267
        'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
124
268
        'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
125
 
                                        ('donation_st', 'Standard donation (for help)'), ('loan', 'Loan'),], 
 
269
                                        ('donation_st', 'Standard donation'), ('loan', 'Loan'), ],
126
270
                                        string='Order Type', required=True, readonly=True, states={'draft': [('readonly', False)]}),
127
271
        'loan_id': fields.many2one('purchase.order', string='Linked loan', readonly=True),
128
 
        'priority': fields.selection(ORDER_PRIORITY, string='Priority', readonly=True, states={'draft': [('readonly', False)]}),
 
272
        'priority': fields.selection(ORDER_PRIORITY, string='Priority', readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
129
273
        'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, readonly=True, states={'draft': [('readonly', False)]}),
130
 
        'details': fields.char(size=30, string='Details', readonly=True, states={'draft': [('readonly', False)]}),
 
274
        # we increase the size of the 'details' field from 30 to 86
 
275
        'details': fields.char(size=86, string='Details', readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
131
276
        'invoiced': fields.function(_invoiced, method=True, string='Paid',
132
277
            fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid."),
133
278
        'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
134
279
        'noinvoice': fields.function(_get_noinvoice, method=True, string="Don't create an invoice", type='boolean'),
135
 
        'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', readonly=True, states={'draft': [('readonly', False)]}),
 
280
        'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
136
281
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
 
282
        'yml_module_name': fields.char(size=1024, string='Name of the module which created the object in the yml tests', readonly=True),
 
283
        'company_id2': fields.many2one('res.company', 'Company', select=1),
 
284
        'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
 
285
        'partner_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}, help="Invoice address for current field order."),
 
286
        'partner_order_id': fields.many2one('res.partner.address', 'Ordering Contact', readonly=True, required=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}, help="The name and address of the contact who requested the order or quotation."),
 
287
        'partner_shipping_id': fields.many2one('res.partner.address', 'Shipping Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}, help="Shipping address for current field order."),
 
288
        'pricelist_id': fields.many2one('product.pricelist', 'Currency', required=True, readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}, help="Currency for current field order."),
 
289
        'validated_date': fields.date(string='Validated date', help='Date on which the FO was validated.'),
 
290
        'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you want your invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks.", required=True, readonly=True),
 
291
        'order_policy': fields.selection([
 
292
            ('prepaid', 'Payment Before Delivery'),
 
293
            ('manual', 'Shipping & Manual Invoice'),
 
294
            ('postpaid', 'Invoice On Order After Delivery'),
 
295
            ('picking', 'Invoice From The Picking'),
 
296
        ], 'Shipping Policy', required=True, readonly=True,
 
297
            help="""The Shipping Policy is used to synchronise invoice and delivery operations.
 
298
  - The 'Pay Before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
 
299
  - The 'Shipping & Manual Invoice' will create the picking order directly and wait for the user to manually click on the 'Invoice' button to generate the draft invoice.
 
300
  - The 'Invoice On Order After Delivery' choice will generate the draft invoice based on sales order after all picking lists have been finished.
 
301
  - The 'Invoice From The Picking' choice is used to create an invoice during the picking process."""),
 
302
        'split_type_sale_order': fields.selection(SALE_ORDER_SPLIT_SELECTION, required=True, readonly=True),
 
303
        'original_so_id_sale_order': fields.many2one('sale.order', 'Original Field Order', readonly=True),
 
304
        'active': fields.boolean('Active', readonly=True),
 
305
        'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
 
306
        'state_hidden_sale_order': fields.function(_vals_get_sale_override, method=True, type='selection', selection=SALE_ORDER_STATE_SELECTION, readonly=True, string='State', multi='get_vals_sale_override',
 
307
                                                   store={'sale.order': (lambda self, cr, uid, ids, c=None: ids, ['state', 'split_type_sale_order'], 10)}),
 
308
        'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
 
309
        'manually_corrected': fields.function(_get_manually_corrected, method=True, type='boolean', string='Manually corrected'),
 
310
        'is_a_counterpart': fields.boolean('Counterpart?', help="This field is only for indicating that the order is a counterpart"),
 
311
        'fo_created_by_po_sync': fields.boolean('FO created by PO after SYNC', readonly=True),
 
312
        'fo_to_resource': fields.boolean(string='FO created to resource FO in exception', readonly=True),
 
313
        'parent_order_name': fields.char(size=64, string='Parent order name', help='In case of this FO is created to re-source a need, this field contains the name of the initial FO (before split).'),
137
314
    }
138
 
    
 
315
 
139
316
    _defaults = {
140
317
        'order_type': lambda *a: 'regular',
 
318
        'invoice_quantity': lambda *a: 'procurement',
141
319
        'priority': lambda *a: 'normal',
142
 
        'categ': lambda *a: 'mixed',
 
320
        'categ': lambda *a: 'other',
143
321
        'loan_duration': lambda *a: 2,
144
322
        'from_yml_test': lambda *a: False,
 
323
        'company_id2': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id,
 
324
        'order_policy': lambda *a: 'picking',
 
325
        'split_type_sale_order': 'original_sale_order',
 
326
        'active': True,
 
327
        'no_line': lambda *a: True,
145
328
    }
146
 
    
147
 
    def create(self, cr, uid, vals, context={}):
148
 
        if not context:
149
 
            context = {}
150
 
        if context.get('update_mode') in ['init', 'update']:
151
 
            logging.getLogger('init').info('SO: set from yml test to True')
152
 
            vals['from_yml_test'] = True
153
 
        return super(sale_order, self).create(cr, uid, vals, context)
154
 
    
155
 
    def action_wait(self, cr, uid, ids, *args):
156
 
        '''
157
 
        Checks if the invoice should be create from the sale order
158
 
        or not
159
 
        '''
160
 
        line_obj = self.pool.get('sale.order.line')
161
 
        lines = []
162
 
        if isinstance(ids, (int, long)):
163
 
            ids = [ids]
164
 
            
165
 
        for order in self.browse(cr, uid, ids):
166
 
            if order.partner_id.partner_type == 'internal' and order.order_type == 'regular':
167
 
                self.write(cr, uid, [order.id], {'order_policy': 'manual'})
168
 
                for line in order.order_line:
169
 
                    lines.append(line.id)
170
 
            elif order.order_type in ['donation_exp', 'donation_st', 'loan']:
171
 
                self.write(cr, uid, [order.id], {'order_policy': 'manual'})
172
 
                for line in order.order_line:
173
 
                    lines.append(line.id)
174
 
            elif not order.from_yml_test:
175
 
                self.write(cr, uid, [order.id], {'order_policy': 'manual'})
176
 
    
177
 
        if lines:
178
 
            line_obj.write(cr, uid, lines, {'invoiced': 1})
179
 
            
180
 
        return super(sale_order, self).action_wait(cr, uid, ids, args)
181
 
 
182
 
    def action_purchase_order_create(self, cr, uid, ids, context={}):
 
329
 
 
330
    def _check_empty_line(self, cr, uid, ids, context=None):
 
331
        '''
 
332
        Check if all lines have a quantity larger than 0.00
 
333
        '''
 
334
        # Objects
 
335
        line_obj = self.pool.get('sale.order.line')
 
336
 
 
337
        line_ids = line_obj.search(cr, uid, [
 
338
            ('order_id', 'in', ids),
 
339
            ('order_id.state', 'not in', ['draft', 'cancel']),
 
340
            ('order_id.import_in_progress', '=', False),
 
341
            ('product_uom_qty', '<=', 0.00),
 
342
        ], count=True, context=context)
 
343
 
 
344
        if line_ids:
 
345
            return False
 
346
 
 
347
        return True
 
348
 
 
349
    _constraints = [
 
350
        (_check_empty_line, 'All lines must have a quantity larger than 0.00', ['order_line']),
 
351
    ]
 
352
 
 
353
    def _check_own_company(self, cr, uid, company_id, context=None):
 
354
        '''
 
355
        Remove the possibility to make a SO to user's company
 
356
        '''
 
357
        user_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
 
358
        if company_id == user_company_id:
 
359
            raise osv.except_osv(_('Error'), _('You cannot made a Field order to your own company !'))
 
360
 
 
361
        return True
 
362
 
 
363
    def _check_restriction_line(self, cr, uid, ids, context=None):
 
364
        '''
 
365
        Check restriction on products
 
366
        '''
 
367
        if isinstance(ids, (int, long)):
 
368
            ids = [ids]
 
369
 
 
370
        line_obj = self.pool.get('sale.order.line')
 
371
        res = True
 
372
 
 
373
        for order in self.browse(cr, uid, ids, context=context):
 
374
            res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in order.order_line], context=context)
 
375
 
 
376
        return res
 
377
 
 
378
    def onchange_partner_id(self, cr, uid, ids, part=False, order_type=False, *a, **b):
 
379
        '''
 
380
        Set the intl_customer_ok field if the partner is an ESC or an international partner
 
381
        '''
 
382
        res = super(sale_order, self).onchange_partner_id(cr, uid, ids, part)
 
383
 
 
384
        if part and order_type:
 
385
            res2 = self.onchange_order_type(cr, uid, ids, order_type, part)
 
386
            if res2.get('value'):
 
387
                if res.get('value'):
 
388
                    res['value'].update(res2['value'])
 
389
                else:
 
390
                    res.update({'value': res2['value']})
 
391
 
 
392
            # Check the restrction of product in lines
 
393
            if ids:
 
394
                product_obj = self.pool.get('product.product')
 
395
                for order in self.browse(cr, uid, ids):
 
396
                    for line in order.order_line:
 
397
                        if line.product_id:
 
398
                            res, test = product_obj._on_change_restriction_error(cr, uid, line.product_id.id, field_name='partner_id', values=res, vals={'partner_id': part, 'obj_type': 'sale.order'})
 
399
                            if test:
 
400
                                res.setdefault('value', {}).update({'partner_order_id': False, 'partner_shipping_id': False, 'partner_invoice_id': False})
 
401
                                return res
 
402
 
 
403
        return res
 
404
 
 
405
    def onchange_categ(self, cr, uid, ids, categ, context=None):
 
406
        '''
 
407
        Check if the list of products is valid for this new category
 
408
        '''
 
409
        if isinstance(ids, (int, long)):
 
410
            ids = [ids]
 
411
 
 
412
        message = {}
 
413
        if ids and categ in ['service', 'transport']:
 
414
            # Avoid selection of non-service producs on Service FO
 
415
            category = categ == 'service' and 'service_recep' or 'transport'
 
416
            transport_cat = ''
 
417
            if category == 'transport':
 
418
                transport_cat = 'OR p.transport_ok = False'
 
419
            cr.execute('''SELECT p.default_code AS default_code, t.name AS name
 
420
                          FROM sale_order_line l
 
421
                            LEFT JOIN product_product p ON l.product_id = p.id
 
422
                            LEFT JOIN product_template t ON p.product_tmpl_id = t.id
 
423
                            LEFT JOIN sale_order fo ON l.order_id = fo.id
 
424
                          WHERE (t.type != 'service_recep' %s) AND fo.id in (%s) LIMIT 1''' % (transport_cat, ','.join(str(x) for x in ids)))
 
425
            res = cr.fetchall()
 
426
            if res:
 
427
                cat_name = categ == 'service' and 'Service' or 'Transport'
 
428
                message.update({'title': _('Warning'),
 
429
                                'message': _('The product [%s] %s is not a \'%s\' product. You can sale only \'%s\' products on a \'%s\' field order. Please remove this line before saving.') % (res[0][0], res[0][1], cat_name, cat_name, cat_name)})
 
430
 
 
431
        return {'warning': message}
 
432
 
 
433
    def _check_service(self, cr, uid, ids, vals, context=None):
 
434
        '''
 
435
        Avoid the saving of a FO with a non service products on Service FO
 
436
        '''
 
437
        categ = {'transport': _('Transport'),
 
438
                 'service': _('Service')}
 
439
        if isinstance(ids, (int, long)):
 
440
            ids = [ids]
 
441
        if context is None:
 
442
            context = {}
 
443
        if context.get('import_in_progress'):
 
444
            return True
 
445
 
 
446
        for order in self.browse(cr, uid, ids, context=context):
 
447
            for line in order.order_line:
 
448
                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):
 
449
                    raise osv.except_osv(_('Error'), _('The product [%s] %s is not a \'Transport\' product. You can sale only \'Transport\' products on a \'Transport\' field order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
 
450
                    return False
 
451
                elif vals.get('categ', order.categ) == 'service' and line.product_id and line.product_id.type not in ('service', 'service_recep'):
 
452
                    raise osv.except_osv(_('Error'), _('The product [%s] %s is not a \'Service\' product. You can sale only \'Service\' products on a \'Service\' field order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
 
453
                    return False
 
454
 
 
455
        return True
 
456
 
 
457
    def create(self, cr, uid, vals, context=None):
 
458
        if context is None:
 
459
            context = {}
 
460
 
 
461
        # Don't allow the possibility to make a SO to my owm company
 
462
        if 'partner_id' in vals and not context.get('procurement_request') and not vals.get('procurement_request'):
 
463
            self._check_own_company(cr, uid, vals['partner_id'], context=context)
 
464
 
 
465
        if 'partner_id' in vals and vals.get('yml_module_name') != 'sale':
 
466
            partner = self.pool.get('res.partner').browse(cr, uid, vals['partner_id'])
 
467
            if vals.get('order_type', 'regular') != 'regular' or (vals.get('order_type', 'regular') == 'regular' and partner.partner_type == 'internal'):
 
468
                vals['order_policy'] = 'manual'
 
469
            else:
 
470
                vals['order_policy'] = 'picking'
 
471
        elif vals.get('yml_module_name') == 'vals':
 
472
            if not vals.get('order_policy'):
 
473
                vals['order_policy'] = 'picking'
 
474
            if not vals.get('invoice_quantity'):
 
475
                vals['invoice_quantity'] = 'order'
 
476
 
 
477
        res = super(sale_order, self).create(cr, uid, vals, context)
 
478
        self._check_service(cr, uid, [res], vals, context=context)
 
479
        return res
 
480
 
 
481
    def write(self, cr, uid, ids, vals, context=None):
 
482
        '''
 
483
        Remove the possibility to make a SO to user's company
 
484
        '''
 
485
        if isinstance(ids, (int, long)):
 
486
            ids = [ids]
 
487
        if context is None:
 
488
            context = {}
 
489
        # Don't allow the possibility to make a SO to my owm company
 
490
        if 'partner_id' in vals and not context.get('procurement_request'):
 
491
                for obj in self.read(cr, uid, ids, ['procurement_request']):
 
492
                    if not obj['procurement_request']:
 
493
                        self._check_own_company(cr, uid, vals['partner_id'], context=context)
 
494
 
 
495
        for order in self.browse(cr, uid, ids, context=context):
 
496
            if order.yml_module_name == 'sale':
 
497
                continue
 
498
            partner = self.pool.get('res.partner').browse(cr, uid, vals.get('partner_id', order.partner_id.id))
 
499
            if vals.get('order_type', order.order_type) != 'regular' or (vals.get('order_type', order.order_type) == 'regular' and partner.partner_type == 'internal'):
 
500
                vals['order_policy'] = 'manual'
 
501
            else:
 
502
                vals['order_policy'] = 'picking'
 
503
 
 
504
        self._check_service(cr, uid, ids, vals, context=context)
 
505
 
 
506
        res = super(sale_order, self).write(cr, uid, ids, vals, context=context)
 
507
 
 
508
        return res
 
509
 
 
510
    def ask_resource_lines(self, cr, uid, ids, context=None):
 
511
        '''
 
512
        Launch the wizard to re-source lines
 
513
        '''
 
514
        # Objects
 
515
        wiz_obj = self.pool.get('sale.order.cancelation.wizard')
 
516
 
 
517
        # Variables
 
518
        wf_service = netsvc.LocalService("workflow")
 
519
 
 
520
        if not context:
 
521
            context = {}
 
522
 
 
523
        if isinstance(ids, (int, long)):
 
524
            ids = [ids]
 
525
 
 
526
        for order in self.browse(cr, uid, ids, context=context):
 
527
            if order.state == 'validated' and len(order.order_line) > 0:
 
528
                wiz_id = wiz_obj.create(cr, uid, {'order_id': order.id}, context=context)
 
529
                return {'type': 'ir.actions.act_window',
 
530
                        'res_model': 'sale.order.cancelation.wizard',
 
531
                        'view_type': 'form',
 
532
                        'view_mode': 'form',
 
533
                        'target': 'new',
 
534
                        'res_id': wiz_id,
 
535
                        'context': context}
 
536
 
 
537
            wf_service.trg_validate(uid, 'sale.order', order.id, 'cancel', cr)
 
538
 
 
539
        return True
 
540
 
 
541
    def change_currency(self, cr, uid, ids, context=None):
 
542
        '''
 
543
        Launches the wizard to change the currency and update lines
 
544
        '''
 
545
        if not context:
 
546
            context = {}
 
547
 
 
548
        if isinstance(ids, (int, long)):
 
549
            ids = [ids]
 
550
 
 
551
        for order in self.browse(cr, uid, ids, context=context):
 
552
            data = {'order_id': order.id,
 
553
                    'partner_id': order.partner_id.id,
 
554
                    'partner_type': order.partner_id.partner_type,
 
555
                    'new_pricelist_id': order.pricelist_id.id,
 
556
                    'currency_rate': 1.00,
 
557
                    'old_pricelist_id': order.pricelist_id.id}
 
558
            wiz = self.pool.get('sale.order.change.currency').create(cr, uid, data, context=context)
 
559
            return {'type': 'ir.actions.act_window',
 
560
                    'res_model': 'sale.order.change.currency',
 
561
                    'view_type': 'form',
 
562
                    'view_mode': 'form',
 
563
                    'res_id': wiz,
 
564
                    'target': 'new'}
 
565
 
 
566
        return True
 
567
 
 
568
    def wkf_validated(self, cr, uid, ids, context=None):
 
569
        """
 
570
        Do some checks for SO validation :
 
571
            1/ Check of the analytic distribution
 
572
            2/ Check if there is lines in order
 
573
            3/ Check of line procurement method in case of loan FO
 
574
            4/ Check if the currency of the order is compatible with
 
575
               the currency of the partner
 
576
 
 
577
        :param cr: Cursor to the database
 
578
        :param uid: ID of the user that runs the method
 
579
        :param ids: List of IDs of the order to validate
 
580
        :param context: Context of the call
 
581
 
 
582
        :return True if all order have been written
 
583
        :rtype boolean
 
584
        """
 
585
        # Objects
 
586
        line_obj = self.pool.get('sale.order.line')
 
587
        pricelist_obj = self.pool.get('product.pricelist')
 
588
 
 
589
        if context is None:
 
590
            context = {}
 
591
 
 
592
        if isinstance(ids, (int, long)):
 
593
            ids = [ids]
 
594
 
 
595
        order_brw_list = self.browse(cr, uid, ids, context=context)
 
596
 
 
597
        # 1/ Check validity of analytic distribution
 
598
        self.analytic_distribution_checks(cr, uid, order_brw_list)
 
599
 
 
600
        for order in order_brw_list:
 
601
            # 2/ Check if there is lines in order
 
602
            if len(order.order_line) < 1:
 
603
                raise osv.except_osv(_('Error'), _('You cannot validate a Field order without line !'))
 
604
 
 
605
            # 3/ Check of line procurement method in case of loan PO
 
606
            if order.order_type == 'loan':
 
607
                non_mts_line = line_obj.search(cr, uid, [
 
608
                    ('order_id', '=', order.id),
 
609
                    ('type', '!=', 'make_to_stock'),
 
610
                ], context=context)
 
611
                line_obj.write(cr, uid, non_mts_line, {'type': 'make_to_stock'}, context=context)
 
612
 
 
613
            # 4/ Check if the currency of the order is compatible with the currency of the partner
 
614
            pricelist_ids = pricelist_obj.search(cr, uid, [('in_search', '=', order.partner_id.partner_type)], context=context)
 
615
            if order.pricelist_id.id not in pricelist_ids:
 
616
                raise osv.except_osv(
 
617
                    _('Error'),
 
618
                    _('The currency used on the order is not compatible with the supplier. '\
 
619
'Please change the currency to choose a compatible currency.'),
 
620
                )
 
621
 
 
622
        self.write(cr, uid, ids, {
 
623
            'state': 'validated',
 
624
            'validated_date': time.strftime('%Y-%m-%d'),
 
625
        }, context=context)
 
626
 
 
627
        # Display validation message to the user
 
628
        for order in order_brw_list:
 
629
            if not order.procurement_request:
 
630
                self.log(cr, uid, order.id, 'The Field order \'%s\' has been validated.' % order.name, context=context)
 
631
            else:
 
632
                self.log(cr, uid, order.id, 'The Internal Request \'%s\' has been validated.' % order.name, context=context)
 
633
 
 
634
        return True
 
635
 
 
636
    def wkf_split(self, cr, uid, ids, context=None):
 
637
        '''
 
638
        split function for sale order: original -> stock, esc, local purchase
 
639
        '''
 
640
        # Some verifications
 
641
        if context is None:
 
642
            context = {}
 
643
        if isinstance(ids, (int, long)):
 
644
            ids = [ids]
 
645
        # objects
 
646
        line_obj = self.pool.get('sale.order.line')
 
647
        fields_tools = self.pool.get('fields.tools')
 
648
        wf_service = netsvc.LocalService("workflow")
 
649
 
 
650
        # must be original-sale-order to reach this method
 
651
        for so in self.browse(cr, uid, ids, context=context):
 
652
            pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', so.partner_id.partner_type)], context=context)
 
653
            if so.pricelist_id.id not in pricelist_ids:
 
654
                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.'))
 
655
            # links to split Fo
 
656
            split_fo_dic = {'esc_split_sale_order': False,
 
657
                            'stock_split_sale_order': False,
 
658
                            'local_purchase_split_sale_order': False}
 
659
            # check we are allowed to be here
 
660
            if so.split_type_sale_order != 'original_sale_order':
 
661
                raise osv.except_osv(_('Error'), _('You cannot split a Fo which has already been split.'))
 
662
            # loop through lines
 
663
            created_line = []
 
664
            for line in so.order_line:
 
665
                # check that each line must have a supplier specified
 
666
                if  line.type == 'make_to_order':
 
667
                    if not line.product_id:
 
668
                        raise osv.except_osv(_('Warning'), _("""You can't confirm a Sale Order that contains
 
669
                        lines with procurement method 'On Order' and without product. Please check the line %s
 
670
                        """) % line.line_number)
 
671
                    if not line.supplier and line.po_cft in ('po', 'dpo'):
 
672
                        raise osv.except_osv(_('Error'), _("""Supplier is not defined for all Field Order lines.
 
673
                        Please check the line %s
 
674
                        """) % line.line_number)
 
675
                fo_type = False
 
676
                # get corresponding type
 
677
                if line.type == 'make_to_stock':
 
678
                    fo_type = 'stock_split_sale_order'
 
679
                elif line.supplier.partner_type == 'esc':
 
680
                    fo_type = 'esc_split_sale_order'
 
681
                else:
 
682
                    # default value is local purchase - same value if no supplier is defined (tender)
 
683
                    fo_type = 'local_purchase_split_sale_order'
 
684
                # do we have already a link to Fo
 
685
                if not split_fo_dic[fo_type]:
 
686
                    # try to find corresponding stock split sale order
 
687
                    so_ids = self.search(cr, uid, [('original_so_id_sale_order', '=', so.id),
 
688
                                                   ('split_type_sale_order', '=', fo_type)], context=context)
 
689
                    if so_ids:
 
690
                        # the fo already exists
 
691
                        split_fo_dic[fo_type] = so_ids[0]
 
692
                    else:
 
693
                        # we create a new Fo for the corresponding type -> COPY we empty the lines
 
694
                        # generate the name of new fo
 
695
                        selec_name = fields_tools.get_selection_name(cr, uid, self, 'split_type_sale_order', fo_type, context=context)
 
696
                        fo_name = so.name + '-' + selec_name
 
697
                        split_id = self.copy(cr, uid, so.id, {'name': fo_name,
 
698
                                                              'order_line': [],
 
699
                                                              'loan_id': so.loan_id and so.loan_id.id or False,
 
700
                                                              'delivery_requested_date': so.delivery_requested_date,
 
701
                                                              'split_type_sale_order': fo_type,
 
702
                                                              'ready_to_ship_date': line.order_id.ready_to_ship_date,
 
703
                                                              'original_so_id_sale_order': so.id}, context=dict(context, keepDateAndDistrib=True, keepClientOrder=True))
 
704
                        # log the action of split
 
705
                        self.log(cr, uid, split_id, _('The %s split %s has been created.') % (selec_name, fo_name))
 
706
                        split_fo_dic[fo_type] = split_id
 
707
                        # For loans, change the subflow
 
708
                        if fo_type == 'stock_split_sale_order':
 
709
                            po_ids = self.pool.get('purchase.order').search(cr, uid, [('loan_id', '=', so.id)], context=context)
 
710
                            netsvc.LocalService("workflow").trg_change_subflow(uid, 'purchase.order', po_ids, 'sale.order', [so.id], split_id, cr)
 
711
                # copy the line to the split Fo - the state is forced to 'draft' by default method in original add-ons
 
712
                # -> the line state is modified to sourced when the corresponding procurement is created in action_ship_proc_create
 
713
                new_context = dict(context, keepDateAndDistrib=True, keepLineNumber=True, no_store_function=['sale.order.line'])
 
714
                new_line_id = line_obj.copy(cr, uid, line.id, {'order_id': split_fo_dic[fo_type],
 
715
                                                 'original_line_id': line.id}, context=new_context)
 
716
                created_line.append(new_line_id)
 
717
 
 
718
            line_obj._call_store_function(cr, uid, created_line, keys=None, result=None, bypass=False, context=context)
 
719
            # the sale order is treated, we process the workflow of the new so
 
720
            for to_treat in [x for x in split_fo_dic.values() if x]:
 
721
                wf_service.trg_validate(uid, 'sale.order', to_treat, 'order_validated', cr)
 
722
                wf_service.trg_validate(uid, 'sale.order', to_treat, 'order_confirm', cr)
 
723
        return True
 
724
 
 
725
    def get_original_name(self, cr, uid, order, context=None):
 
726
        '''
 
727
        Returns the name of the first original FO
 
728
        '''
 
729
        if order.original_so_id_sale_order:
 
730
            return self.get_original_name(cr, uid, order.original_so_id_sale_order, context=context)
 
731
        elif order.parent_order_name:
 
732
            return order.parent_order_name
 
733
 
 
734
        return order.name
 
735
 
 
736
    def create_resource_order(self, cr, uid, order, context=None):
 
737
        '''
 
738
        Create a new FO to re-source the needs.
 
739
        '''
 
740
        context = context or {}
 
741
 
 
742
        # Get the name of the original FO
 
743
        old_order_name = order.name
 
744
 
 
745
        order_ids = self.search(cr, uid, [('active', 'in', ('t', 'f')), ('fo_to_resource', '=', True), ('parent_order_name', '=', old_order_name)], context=dict(context, procurement_request=True))
 
746
        for old_order in self.read(cr, uid, order_ids, ['name', 'state'], context=context):
 
747
            if old_order['state'] == 'draft':
 
748
                return old_order['id']
 
749
 
 
750
        order_id = self.copy(cr, uid, order.id, {'order_line': [],
 
751
                                                 'state': 'draft',
 
752
                                                 'parent_order_name': old_order_name,
 
753
                                                 'fo_to_resource': True}, context=context)
 
754
 
 
755
 
 
756
        order_name = self.read(cr, uid, order_id, ['name'], context=context)['name']
 
757
 
 
758
        self.log(cr, uid, order_id, _('The Field order %s has been created to re-source the canceled needs') % order_name, context=dict(context, procurement_request=order.procurement_request))
 
759
 
 
760
        return order_id
 
761
 
 
762
    def sale_except_correction(self, cr, uid, ids, context=None):
 
763
        '''
 
764
        Remove the link between a Field order and the canceled procurement orders
 
765
        '''
 
766
        for order in self.browse(cr, uid, ids, context=context):
 
767
            for line in order.order_line:
 
768
                if line.procurement_id and line.procurement_id.state == 'cancel':
 
769
                    if line.procurement_id.procure_method == 'make_to_stock' and line.procurement_id.move_id:
 
770
                        # TODO: Make a diff with UoM
 
771
                        diff = line.product_uom_qty - (line.product_uom_qty - line.procurement_id.move_id.product_qty)
 
772
                        resource_id = self.pool.get('sale.order').create_resource_order(cr, uid, line.order_id.original_so_id_sale_order, context=context)
 
773
                        self.pool.get('sale.order.line').add_resource_line(cr, uid, line, resource_id, diff, context=context)
 
774
                    self.pool.get('sale.order.line').write(cr, uid, [line.id], {'state': 'cancel',
 
775
                                                                                'manually_corrected': True,
 
776
                                                                                'procurement_id': False}, context=context)
 
777
            if (order.order_policy == 'manual'):
 
778
                self.write(cr, uid, [order.id], {'state': 'manual'})
 
779
            else:
 
780
                self.write(cr, uid, [order.id], {'state': 'progress'})
 
781
 
 
782
        return
 
783
 
 
784
    def wkf_split_done(self, cr, uid, ids, context=None):
 
785
        '''
 
786
        split done function for sale order
 
787
        '''
 
788
        # Some verifications
 
789
        if context is None:
 
790
            context = {}
 
791
        if isinstance(ids, (int, long)):
 
792
            ids = [ids]
 
793
 
 
794
        # objects
 
795
        sol_obj = self.pool.get('sale.order.line')
 
796
        # get all corresponding sale order lines
 
797
        sol_ids = sol_obj.search(cr, uid, [('order_id', 'in', ids)], context=context)
 
798
        # set lines state to done
 
799
        if sol_ids:
 
800
            sol_obj.write(cr, uid, sol_ids, {'state': 'done'}, context=context)
 
801
        self.write(cr, uid, ids, {'state': 'done',
 
802
                                  'active': False}, context=context)
 
803
        return True
 
804
 
 
805
    def get_po_ids_from_so_ids(self, cr, uid, ids, context=None):
 
806
        '''
 
807
        receive the list of sale order ids
 
808
 
 
809
        return the list of purchase order ids corresponding (through procurement process)
 
810
        '''
 
811
        # Some verifications
 
812
        if not context:
 
813
            context = {}
 
814
        if isinstance(ids, (int, long)):
 
815
            ids = [ids]
 
816
 
 
817
        # procurement ids list
 
818
        po_ids = []
 
819
 
 
820
        for so in self.browse(cr, uid, ids, context=context):
 
821
            for line in so.order_line:
 
822
                if line.procurement_id:
 
823
                    if line.procurement_id.purchase_id:
 
824
                        if line.procurement_id.purchase_id.id not in po_ids:
 
825
                            po_ids.append(line.procurement_id.purchase_id.id)
 
826
 
 
827
        # return the purchase order ids
 
828
        return po_ids
 
829
 
 
830
    def _hook_message_action_wait(self, cr, uid, *args, **kwargs):
 
831
        '''
 
832
        Hook the message displayed on sale order confirmation
 
833
        '''
 
834
        return _('The Field order \'%s\' has been confirmed.') % (kwargs['order'].name,)
 
835
 
 
836
    def action_purchase_order_create(self, cr, uid, ids, context=None):
183
837
        '''
184
838
        Create a purchase order as counterpart for the loan.
185
839
        '''
186
840
        if isinstance(ids, (int, long)):
187
841
            ids = [ids]
188
 
            
 
842
        if context is None:
 
843
            context = {}
 
844
 
189
845
        purchase_obj = self.pool.get('purchase.order')
190
846
        purchase_line_obj = self.pool.get('purchase.order.line')
191
847
        partner_obj = self.pool.get('res.partner')
192
 
            
 
848
 
193
849
        for order in self.browse(cr, uid, ids):
 
850
            # UTP-392: don't create a PO if it is created by sync ofr the loan
 
851
            if order.is_a_counterpart or (order.order_type == 'loan' and order.fo_created_by_po_sync):
 
852
                return
 
853
 
194
854
            two_months = today() + RelativeDateTime(months=+2)
195
 
            order_id = purchase_obj.create(cr, uid, {'partner_id': order.partner_id.id,
196
 
                                                 'partner_address_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['contact'])['contact'],
197
 
                                                 'pricelist_id': order.partner_id.property_product_pricelist_purchase.id,
198
 
                                                 'loan_id': order.id,
199
 
                                                 'loan_duration': order.loan_duration,
200
 
                                                 'origin': order.name,
201
 
                                                 'order_type': 'loan',
202
 
                                                 'delivery_requested_date': (today() + RelativeDateTime(months=+order.loan_duration)).strftime('%Y-%m-%d'),
203
 
                                                 'categ': order.categ,
204
 
                                                 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
205
 
                                                 'priority': order.priority,})
 
855
            # from yml test is updated according to order value
 
856
            values = {'partner_id': order.partner_id.id,
 
857
                      'partner_address_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['contact'])['contact'],
 
858
                      'pricelist_id': order.partner_id.property_product_pricelist_purchase.id,
 
859
                      'loan_id': order.id,
 
860
                      'loan_duration': order.loan_duration,
 
861
                      'origin': order.name,
 
862
                      'order_type': 'loan',
 
863
                      'delivery_requested_date': (today() + RelativeDateTime(months=+order.loan_duration)).strftime('%Y-%m-%d'),
 
864
                      'categ': order.categ,
 
865
                      'location_id': order.shop_id.warehouse_id.lot_input_id.id,
 
866
                      'priority': order.priority,
 
867
                      'from_yml_test': order.from_yml_test,
 
868
                      'is_a_counterpart': True,
 
869
                      }
 
870
            context['is_a_counterpart'] = True
 
871
            order_id = purchase_obj.create(cr, uid, values, context=context)
206
872
            for line in order.order_line:
207
873
                purchase_line_obj.create(cr, uid, {'product_id': line.product_id and line.product_id.id or False,
208
874
                                                   'product_uom': line.product_uom.id,
210
876
                                                   'price_unit': line.price_unit,
211
877
                                                   'product_qty': line.product_uom_qty,
212
878
                                                   'date_planned': (today() + RelativeDateTime(months=+order.loan_duration)).strftime('%Y-%m-%d'),
213
 
                                                   'name': line.name,})
 
879
                                                   'name': line.name, }, context)
214
880
            self.write(cr, uid, [order.id], {'loan_id': order_id})
215
 
            
 
881
 
216
882
            purchase = purchase_obj.browse(cr, uid, order_id)
217
 
            
218
 
            message = _("Loan counterpart '%s' is created.") % (purchase.name,)
219
 
            
 
883
 
 
884
            message = _("Loan counterpart '%s' has been created.") % (purchase.name,)
 
885
 
220
886
            purchase_obj.log(cr, uid, order_id, message)
221
 
        
 
887
 
222
888
        return order_id
223
 
    
 
889
 
224
890
    def has_stockable_products(self, cr, uid, ids, *args):
225
891
        '''
226
892
        Override the has_stockable_product to return False
229
895
        for order in self.browse(cr, uid, ids):
230
896
            if order.order_type != 'direct':
231
897
                return super(sale_order, self).has_stockable_product(cr, uid, ids, args)
232
 
        
 
898
 
233
899
        return False
234
 
    
 
900
 
235
901
    #@@@override sale.sale_order.action_invoice_end
236
902
    def action_invoice_end(self, cr, uid, ids, context=None):
237
 
        ''' 
 
903
        '''
238
904
        Modified to set lines invoiced when order_type is not regular
239
905
        '''
240
906
        for order in self.browse(cr, uid, ids, context=context):
269
935
        return True
270
936
        #@@@end
271
937
 
272
 
    def _get_reason_type(self, cr, uid, order, context={}):
 
938
    def _get_reason_type(self, cr, uid, order, context=None):
273
939
        r_types = {
274
 
            'regular': 'reason_type_deliver_partner', 
 
940
            'regular': 'reason_type_deliver_partner',
275
941
            'loan': 'reason_type_loan',
276
942
            'donation_st': 'reason_type_donation',
277
943
            'donation_exp': 'reason_type_donation_expiry',
278
944
        }
279
945
 
280
 
        if order.order_type in r_types:
281
 
            return self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves',r_types[order.order_type])[1]
 
946
        if not order.procurement_request and order.order_type in r_types:
 
947
            return self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', r_types[order.order_type])[1]
282
948
 
283
949
        return False
284
 
    
 
950
 
 
951
    def order_line_change(self, cr, uid, ids, order_line):
 
952
        res = {'no_line': True}
 
953
 
 
954
        if order_line:
 
955
            res = {'no_line': False}
 
956
 
 
957
        return {'value': res}
 
958
 
285
959
    def _hook_ship_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
286
960
        '''
287
961
        Please copy this to your module's method also.
288
962
        This hook belongs to the action_ship_create method from sale>sale.py
289
 
        
 
963
 
290
964
        - allow to modify the data for stock picking creation
291
965
        '''
292
966
        result = super(sale_order, self)._hook_ship_create_stock_picking(cr, uid, ids, context=context, *args, **kwargs)
293
967
        result['reason_type_id'] = self._get_reason_type(cr, uid, kwargs['order'], context)
294
 
        
295
 
        return result
296
 
    
297
 
    def _hook_ship_create_stock_move(self, cr, uid, ids, context=None, *args, **kwargs):
298
 
        '''
299
 
        Please copy this to your module's method also.
300
 
        This hook belongs to the action_ship_create method from sale>sale.py
301
 
        
302
 
        - allow to modify the data for stock move creation
303
 
        '''
304
 
        result = super(sale_order, self)._hook_ship_create_stock_move(cr, uid, ids, context=context, *args, **kwargs)
305
 
        result['reason_type_id'] = self._get_reason_type(cr, uid, kwargs['order'], context)
306
 
        
307
 
        return result
308
 
    
309
 
    def _hook_ship_create_execute_specific_code_01(self, cr, uid, ids, context=None, *args, **kwargs):
310
 
        '''
311
 
        Please copy this to your module's method also.
312
 
        This hook belongs to the action_ship_create method from sale>sale.py
313
 
        
314
 
        - allow to execute specific code at position 01
315
 
        '''
316
 
        super(sale_order, self)._hook_ship_create_execute_specific_code_01(cr, uid, ids, context=context, *args, **kwargs)
317
 
        wf_service = netsvc.LocalService("workflow")
318
 
        order = kwargs['order']
319
 
        proc_id = kwargs['proc_id']
320
 
        if order.procurement_request and order.state == 'progress':
321
 
            wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
322
 
        
323
 
        return True
324
 
    
325
 
    def _hook_ship_create_line_condition(self, cr, uid, ids, context=None, *args, **kwargs):
326
 
        '''
327
 
        Please copy this to your module's method also.
328
 
        This hook belongs to the action_ship_create method from sale>sale.py
329
 
        
330
 
        - allow to customize the execution condition
331
 
        '''
332
 
        line = kwargs['line']
333
 
        result = super(sale_order, self)._hook_ship_create_line_condition(cr, uid, ids, context=context, *args, **kwargs)
334
 
        result = result and not line.order_id.procurement_request
335
 
        return result
 
968
 
 
969
        return result
 
970
 
 
971
    def _get_date_planned(self, order, line):
 
972
        """
 
973
        Return the planned date for the FO/IR line according
 
974
        to the order and line values.
 
975
 
 
976
        :param order: browse_record of a sale.order
 
977
        :param line: browse_record of a sale.order.line
 
978
 
 
979
        :return The planned date
 
980
        :rtype datetime
 
981
        """
 
982
        # Check type of parameter
 
983
        self._check_browse_param(order, '_get_date_planned')
 
984
        self._check_browse_param(line, '_get_date_planned')
 
985
 
 
986
        date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
 
987
        date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
 
988
 
 
989
        return date_planned
 
990
 
 
991
    def _get_new_picking(self, line):
 
992
        """
 
993
        Return True if the line needs a new picking ticket.
 
994
        In case of IR to an internal location, the creation
 
995
        of a picking is not needed.
 
996
 
 
997
        :param line: The browse_record of the sale.order.line to check
 
998
 
 
999
        :return True if the line needs a new picking or False
 
1000
        :rtype boolean
 
1001
        """
 
1002
        # Check type of parameter
 
1003
        self._check_browse_param(line, '_get_new_picking')
 
1004
 
 
1005
        res = line.product_id and line.product_id.type in ['product', 'consu', 'service']
 
1006
 
 
1007
        if line.order_id.manually_corrected:
 
1008
            return False
 
1009
 
 
1010
        if line.order_id.procurement_request and line.type == 'make_to_order':
 
1011
            # Create OUT lines for MTO lines with an external CU as requestor location
 
1012
            if line.order_id.location_requestor_id.usage != 'customer':
 
1013
                res = False
 
1014
            elif line.order_id.location_requestor_id.usage == 'customer':
 
1015
                res = True
 
1016
 
 
1017
        return res
 
1018
 
 
1019
    def _get_picking_data(self, cr, uid, order, context=None):
 
1020
        """
 
1021
        Define the values for the picking ticket associated to the
 
1022
        FO/IR according to order values.
 
1023
 
 
1024
        :param cr: Cursor to the database
 
1025
        :param uid: ID of the user that runs the method
 
1026
        :param order: browse_record of a sale.order
 
1027
 
 
1028
        :return A dictionary with the values of the picking to be create
 
1029
        :rtype dict
 
1030
        """
 
1031
        # Objects
 
1032
        seq_obj = self.pool.get('ir.sequence')
 
1033
        config_obj = self.pool.get('unifield.setup.configuration')
 
1034
        data_obj = self.pool.get('ir.model.data')
 
1035
 
 
1036
        if context is None:
 
1037
            context = {}
 
1038
 
 
1039
        self._check_browse_param(order, '_get_picking_data')
 
1040
 
 
1041
        setup = config_obj.get_config(cr, uid)
 
1042
 
 
1043
        picking_data = {
 
1044
            'origin': order.name,
 
1045
            'type': 'out',
 
1046
            'state': 'draft',
 
1047
            'move_type': order.picking_policy,
 
1048
            'sale_id': order.id,
 
1049
            'address_id': order.partner_shipping_id.id,
 
1050
            'note': order.note,
 
1051
            'invoice_state': (order.order_policy == 'picking' and '2binvoiced') or 'none',
 
1052
            'company_id': order.company_id.id,
 
1053
        }
 
1054
 
 
1055
        if order.procurement_request:
 
1056
            location_dest_id = order.location_requestor_id
 
1057
            if order.procurement_request:
 
1058
                if location_dest_id and location_dest_id.usage in ('supplier', 'customer'):
 
1059
                    picking_data.update({
 
1060
                        'type': 'out',
 
1061
                        'subtype': 'standard',
 
1062
                        'reason_type_id': data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1],
 
1063
                    })
 
1064
                    pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
 
1065
                else:
 
1066
                    picking_data.update({
 
1067
                        'type': 'internal',
 
1068
                        'subtype': 'standard',
 
1069
                        'reason_type_id': data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_move')[1],
 
1070
                    })
 
1071
                    pick_name = seq_obj.get(cr, uid, 'stock.picking.internal')
 
1072
        else:
 
1073
            if setup.delivery_process == 'simple':
 
1074
                picking_data['subtype'] = 'standard'
 
1075
                # use the name according to picking ticket sequence
 
1076
                pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
 
1077
            else:
 
1078
                picking_data['subtype'] = 'picking'
 
1079
                # use the name according to picking ticket sequence
 
1080
                pick_name = seq_obj.get(cr, uid, 'picking.ticket')
 
1081
 
 
1082
        picking_data.update({
 
1083
            'name': pick_name,
 
1084
            'flow_type': 'full',
 
1085
            'backorder_id': False,
 
1086
            'warehouse_id': order.shop_id.warehouse_id.id,
 
1087
            'reason_type_id': self._get_reason_type(cr, uid, order, context=context) or picking_data.get('reason_type_id', False),
 
1088
        })
 
1089
 
 
1090
        return picking_data
 
1091
 
 
1092
    def _get_move_data(self, cr, uid, order, line, picking_id, context=None):
 
1093
        """
 
1094
        Define the values for the stock move associated to the
 
1095
        FO/IR line according to line and order values.
 
1096
 
 
1097
        :param cr: Cursor to the database
 
1098
        :param uid: ID of the user that runs the method
 
1099
        :param order: browse_record of a sale.order
 
1100
        :param line: browse_record of a sale.order.line
 
1101
 
 
1102
        :return A dictionary with the values of the move to be create
 
1103
        :rtype dict
 
1104
        """
 
1105
        # Objects
 
1106
        data_obj = self.pool.get('ir.model.data')
 
1107
        config_obj = self.pool.get('unifield.setup.configuration')
 
1108
        loc_obj = self.pool.get('stock.location')
 
1109
        pick_obj = self.pool.get('stock.picking')
 
1110
 
 
1111
        if context is None:
 
1112
            context = {}
 
1113
 
 
1114
 
 
1115
        self._check_browse_param(order, '_get_move_data')
 
1116
        self._check_browse_param(line, '_get_move_data')
 
1117
 
 
1118
        location_id = order.shop_id.warehouse_id.lot_stock_id.id
 
1119
        output_id = order.shop_id.warehouse_id.lot_output_id.id
 
1120
 
 
1121
        move_data = {
 
1122
            'name': line.name[:64],
 
1123
            'picking_id': picking_id,
 
1124
            'product_id': line.product_id.id,
 
1125
            'date': order.ready_to_ship_date,
 
1126
            'date_expected': order.ready_to_ship_date,
 
1127
            'product_qty': line.product_uom_qty,
 
1128
            'product_uom': line.product_uom.id,
 
1129
            'product_uos_qty': line.product_uos_qty,
 
1130
            'product_uos': (line.product_uos and line.product_uos.id)\
 
1131
                 or line.product_uom.id,
 
1132
            'product_packaging': line.product_packaging.id,
 
1133
            'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
 
1134
            'location_id': location_id,
 
1135
            'location_dest_id': output_id,
 
1136
            'sale_line_id': line.id,
 
1137
            'tracking_id': False,
 
1138
            'state': 'draft',
 
1139
                 # 'state': 'waiting',
 
1140
            'note': line.notes,
 
1141
            'company_id': order.company_id.id,
 
1142
            'reason_type_id': self._get_reason_type(cr, uid, order),
 
1143
            'price_currency_id': order.pricelist_id.currency_id.id,
 
1144
            'line_number': line.line_number,
 
1145
        }
 
1146
 
 
1147
        if line.order_id.procurement_request and line.order_id.location_requestor_id.usage == 'customer' and not line.product_id and line.comment:
 
1148
            move_data['product_id'] = data_obj.get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
 
1149
 
 
1150
        # For IR
 
1151
        if order.procurement_request and order.location_requestor_id:
 
1152
            move_data.update({
 
1153
                'type': 'internal',
 
1154
                'reason_type_id': data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_move')[1],
 
1155
                'location_dest_id': order.location_requestor_id.id,
 
1156
            })
 
1157
 
 
1158
            if order.location_requestor_id.usage in ('supplier', 'customer'):
 
1159
                move_data['type'] = 'out'
 
1160
        else:
 
1161
            # first go to packing location (PICK/PACK/SHIP) or output location (Simple OUT)
 
1162
            # according to the configuration
 
1163
            # first go to packing location
 
1164
            setup = config_obj.get_config(cr, uid)
 
1165
            if setup.delivery_process == 'simple':
 
1166
                move_data['location_dest_id'] = order.shop_id.warehouse_id.lot_output_id.id
 
1167
            else:
 
1168
                move_data['location_dest_id'] = order.shop_id.warehouse_id.lot_packing_id.id
 
1169
 
 
1170
            if line.product_id and line.product_id.type == 'service_recep':
 
1171
                move_data['location_id'] = loc_obj.get_cross_docking_location(cr, uid)
 
1172
 
 
1173
        if 'sale_line_id' in move_data and move_data['sale_line_id']:
 
1174
            if line.type == 'make_to_stock':
 
1175
                move_data['location_id'] = line.location_id and line.location_id.id or order.shop_id.warehouse_id.lot_stock_id.id
 
1176
            elif line.type == 'make_to_order':
 
1177
                move_data.update({
 
1178
                    'location_id': loc_obj.get_cross_docking_location(cr, uid),
 
1179
                    'move_cross_docking_ok': True,
 
1180
                })
 
1181
                # Update the stock.picking
 
1182
                pick_obj.write(cr, uid, move_data['picking_id'], {'cross_docking_ok': True}, context=context)
 
1183
 
 
1184
        move_data['state'] = 'confirmed'
 
1185
 
 
1186
        return move_data
 
1187
 
 
1188
    # @@@override sale>sale.py>sale_order>action_ship_create()
 
1189
    def action_ship_create(self, cr, uid, ids, context=None):
 
1190
        """
 
1191
        Create the picking ticket with the stock moves and the
 
1192
        procurement orders according to FO/IR values.
 
1193
 
 
1194
        :param cr: Cursor to the database
 
1195
        :param uid: ID of the user that runs the method
 
1196
        :param ids: List of ID of FO/IR that have been confirmed
 
1197
        :param context: Context of the call
 
1198
 
 
1199
        :return
 
1200
        :rtype
 
1201
        """
 
1202
        # Objects
 
1203
        wf_service = netsvc.LocalService("workflow")
 
1204
        move_obj = self.pool.get('stock.move')
 
1205
        proc_obj = self.pool.get('procurement.order')
 
1206
        picking_obj = self.pool.get('stock.picking')
 
1207
        pol_obj = self.pool.get('purchase.order.line')
 
1208
        data_obj = self.pool.get('ir.model.data')
 
1209
        sol_obj = self.pool.get('sale.order.line')
 
1210
        config_obj = self.pool.get('unifield.setup.configuration')
 
1211
 
 
1212
        if context is None:
 
1213
            context = {}
 
1214
 
 
1215
        if isinstance(ids, (int, long)):
 
1216
            ids = [ids]
 
1217
 
 
1218
        setup = config_obj.get_config(cr, uid)
 
1219
 
 
1220
        for order in self.browse(cr, uid, ids, context=context):
 
1221
            proc_ids = []
 
1222
            move_ids = []
 
1223
            picking_id = False
 
1224
 
 
1225
            for line in order.order_line:
 
1226
                proc_id = False
 
1227
 
 
1228
                # Don't take care of closed lines
 
1229
                if line.state == 'done':
 
1230
                    continue
 
1231
 
 
1232
                move_id = False
 
1233
 
 
1234
                # In case of IR to internal location, the creation of
 
1235
                # a picking is not need.
 
1236
                if self._get_new_picking(line):
 
1237
                    if not picking_id:
 
1238
                        picking_data = self._get_picking_data(cr, uid, order)
 
1239
                        picking_id = picking_obj.create(cr, uid, picking_data, context=context)
 
1240
 
 
1241
                    # Get move data and create the move
 
1242
                    move_data = self._get_move_data(cr, uid, order, line, picking_id, context=context)
 
1243
 
 
1244
                    # defer overall_qty computation at the end of this method
 
1245
                    context['bypass_store_function'] = [('stock.picking', ['overall_qty'])]
 
1246
                    move_id = self.pool.get('stock.move').create(cr, uid, move_data, context=context)
 
1247
                    move_ids.append(move_id)
 
1248
                    context['bypass_store_function'] = False
 
1249
 
 
1250
                    if order.procurement_request:
 
1251
                        move_obj.action_confirm(cr, uid, [move_id], context=context)
 
1252
 
 
1253
                    """
 
1254
                    We update the procurement and the purchase orders if we are treating o FO which is
 
1255
                    not shipping_exception.
 
1256
                    PO is only treated if line is make_to_order.
 
1257
                    IN nor OUT are yet (or just) created, we theoretically won't have problem with
 
1258
                    backorders and co
 
1259
                    """
 
1260
                    if order.state != 'shipping_except' and not order.procurement_request and line.procurement_id:
 
1261
                        cancel_move_id = False
 
1262
                        """
 
1263
                        If the procurement has already a stock move linked to it (during action_confirm of procurement
 
1264
                        order), we cancel it.
 
1265
                        UF-1155: Divided the cancel of the move in two times to avaid the cancelation of the field order
 
1266
                        """
 
1267
                        if line.procurement_id.move_id:
 
1268
                            cancel_move_id = line.procurement_id.move_id.id
 
1269
 
 
1270
                        # Update corresponding procurement order with the new stock move
 
1271
                        proc_obj.write(cr, uid, [line.procurement_id.id], {'move_id': move_id}, context=context)
 
1272
 
 
1273
                        if cancel_move_id:
 
1274
                            # Ase action_cancel actually, because there is not stock picking or related stock moves
 
1275
                            move_obj.action_cancel(cr, uid, [line.procurement_id.move_id.id], context=context)
 
1276
 
 
1277
                        if line.type == 'make_to_order':
 
1278
                            pol_update_ids = pol_obj.search(cr, uid, [('procurement_id', '=', line.procurement_id.id)], context=context)
 
1279
                            pol_obj.write(cr, uid, pol_update_ids, {'move_dest_id': move_id}, context=context)
 
1280
 
 
1281
                product_id = False
 
1282
                if line.product_id:
 
1283
                    product_id = line.product_id.id
 
1284
                elif order.procurement_request and not line.product_id and line.comment:
 
1285
                    product_id = data_obj.get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
 
1286
 
 
1287
                if not(line.type == 'make_to_stock' and order.procurement_request) and \
 
1288
                   order.procurement_request and product_id:
 
1289
                    # For IR with no product defined, put ToBeDefined UoM as UoM
 
1290
                    if line.product_id:
 
1291
                        product_uom = line.product_uom.id
 
1292
                    elif line.order_id.procurement_request and not line.product_id and line.comment:
 
1293
                        # do we need to have one product data per uom?
 
1294
                        product_uom = data_obj.get_object_reference(cr, uid, 'product', 'cat0')[1]
 
1295
 
 
1296
                    rts_date = self._get_date_planned(order, line)
 
1297
                    proc_data = self._get_procurement_order_data(line, order, rts_date, context)
 
1298
 
 
1299
                    # Just change some values because in case of IR, we need specific values
 
1300
                    proc_data.update({
 
1301
                        'product_id': product_id,
 
1302
                        'product_uom': product_uom,
 
1303
                        'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
 
1304
                        'move_id': move_id,
 
1305
                        'property_ids': [(6, 0, [x.id for x in line.property_ids])],
 
1306
                    })
 
1307
 
 
1308
                    proc_id = proc_obj.create(cr, uid, proc_data)
 
1309
                    proc_ids.append(proc_id)
 
1310
 
 
1311
                    sol_obj.write(cr, uid, [line.id], {'procurement_id': proc_id})
 
1312
                    if order.state == 'shipping_except':
 
1313
                        for pick in order.picking_ids:
 
1314
                            mov_ids = move_obj.search(cr, uid, [
 
1315
                                ('state', '=', 'cancel'),
 
1316
                                ('sale_line_id', '=', line.id),
 
1317
                                ('picking_id', '=', pick.id),
 
1318
                            ], limit=1, context=context)
 
1319
 
 
1320
                            for mov in move_obj.read(cr, uid, mov_ids, ['product_qty', 'product_uos_qty'], context=context):
 
1321
                                values = {
 
1322
                                    'product_qty': mov['product_qty'],
 
1323
                                    'product_uos_qty': mov['product_uos_qty'],
 
1324
                                }
 
1325
                                move_obj.write(cr, uid, [move_id], values, context=context)
 
1326
                                proc_obj.write(cr, uid, [proc_id], values, context=context)
 
1327
 
 
1328
                    wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
 
1329
 
 
1330
            # compute overall_qty
 
1331
            if move_ids:
 
1332
                compute_store = move_obj._store_get_values(cr, uid, move_ids, None, context)
 
1333
                compute_store.sort()
 
1334
                done = []
 
1335
                for store_order, store_object, store_ids, store_fields2 in compute_store:
 
1336
                    if store_fields2 == ['overall_qty'] and not (store_object, store_ids, store_fields2) in done:
 
1337
                        self.pool.get(store_object)._store_set_values(cr, uid, store_ids, store_fields2, context)
 
1338
                        done.append((store_object, store_ids, store_fields2))
 
1339
 
 
1340
            if picking_id and order.procurement_request:
 
1341
                picking_obj.draft_force_assign(cr, uid , [picking_id], context)
 
1342
                picking_obj.cancel_assign(cr, uid, [picking_id], context)
 
1343
                picking_obj.action_assign(cr, uid, [picking_id], context)
 
1344
 
 
1345
            # end for each line
 
1346
            val = {}
 
1347
 
 
1348
            # On Simple OUT configuration, the system should confirm the OUT and launch a first check availability
 
1349
            # On P/P/S configuration, the system should only launch a first check availability on Picking Ticket
 
1350
            if setup.delivery_process != 'simple' and picking_id:
 
1351
                picking_obj.log_picking(cr, uid, [picking_id], context=context)
 
1352
            elif picking_id:
 
1353
                wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
 
1354
 
 
1355
            if picking_id:
 
1356
                # Launch a first check availability
 
1357
                picking_obj.action_assign(cr, uid, [picking_id], context=context)
 
1358
 
 
1359
            picks_to_check = set()
 
1360
            for proc_id in proc_ids:
 
1361
                if order.procurement_request:
 
1362
                    proc = proc_obj.browse(cr, uid, [proc_id], context=context)
 
1363
                    pick_id = proc and proc[0] and proc[0].move_id and proc[0].move_id.picking_id and proc[0].move_id.picking_id.id or False
 
1364
                    if pick_id:
 
1365
                        picks_to_check.add(pick_id)
 
1366
 
 
1367
            for pick_id in picks_to_check:
 
1368
                wf_service.trg_validate(uid, 'stock.picking', pick_id, 'button_confirm', cr)
 
1369
                # We also do a first 'check availability': cancel then check
 
1370
                picking_obj.cancel_assign(cr, uid, [pick_id], context)
 
1371
                picking_obj.action_assign(cr, uid, [pick_id], context)
 
1372
 
 
1373
            if order.state == 'shipping_except':
 
1374
                manual_lines = False
 
1375
                if (order.order_policy == 'manual'):
 
1376
                    manual_lines = sol_obj.search(cr, uid, [
 
1377
                         ('order_id', '=', order.id),
 
1378
                         ('invoiced', '=', False),
 
1379
                         ('state', 'not in', ['cancel', 'draft']),
 
1380
                    ], context=context)
 
1381
 
 
1382
                val.update({
 
1383
                    'state': order.order_policy and manual_lines and 'manual' or 'progress',
 
1384
                    'shipped': False,
 
1385
                })
 
1386
 
 
1387
            self.write(cr, uid, [order.id], val)
 
1388
 
 
1389
        return True
 
1390
    # @@@END override sale>sale.py>sale_order>action_ship_create()
 
1391
 
 
1392
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
 
1393
        '''
 
1394
        Set the sale order and all related documents to done state
 
1395
        '''
 
1396
        wf_service = netsvc.LocalService("workflow")
 
1397
 
 
1398
        if isinstance(ids, (int, long)):
 
1399
            ids = [ids]
 
1400
 
 
1401
        if context is None:
 
1402
            context = {}
 
1403
        order_lines = []
 
1404
        procurement_ids = []
 
1405
        proc_move_ids = []
 
1406
        for order in self.browse(cr, uid, ids, context=context):
 
1407
            #  Done picking
 
1408
            for pick in order.picking_ids:
 
1409
                if pick.state not in ('cancel', 'done'):
 
1410
                    wf_service.trg_validate(uid, 'stock.picking', pick.id, 'manually_done', cr)
 
1411
 
 
1412
            for line in order.order_line:
 
1413
                order_lines.append(line.id)
 
1414
                if line.procurement_id:
 
1415
                    procurement_ids.append(line.procurement_id.id)
 
1416
                    if line.procurement_id.move_id:
 
1417
                        proc_move_ids.append(line.procurement_id.move_id.id)
 
1418
 
 
1419
            # Closed loan counterpart
 
1420
            if order.loan_id and order.loan_id.state not in ('cancel', 'done') and not context.get('loan_id', False) == order.id:
 
1421
                loan_context = context.copy()
 
1422
                loan_context.update({'loan_id': order.id})
 
1423
                self.pool.get('purchase.order').set_manually_done(cr, uid, order.loan_id.id, all_doc=all_doc, context=loan_context)
 
1424
 
 
1425
            # Closed invoices
 
1426
            # invoice_error_ids = []
 
1427
            # for invoice in order.invoice_ids:
 
1428
            #    if invoice.state == 'draft':
 
1429
            #        wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_cancel', cr)
 
1430
            #    elif invoice.state not in ('cancel', 'done'):
 
1431
            #        invoice_error_ids.append(invoice.id)
 
1432
 
 
1433
            # if invoice_error_ids:
 
1434
            #    invoices_ref = ' / '.join(x.number for x in self.pool.get('account.invoice').browse(cr, uid, invoice_error_ids, context=context))
 
1435
            #    raise osv.except_osv(_('Error'), _('The state of the following invoices cannot be updated automatically. Please cancel them manually or d    iscuss with the accounting team to solve the problem. Invoices references : %s') % invoices_ref)
 
1436
 
 
1437
        # Closed stock moves
 
1438
        move_ids = self.pool.get('stock.move').search(cr, uid, [('sale_line_id', 'in', order_lines), ('state', 'not in', ('cancel', 'done'))], context=context)
 
1439
        self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
 
1440
        self.pool.get('stock.move').set_manually_done(cr, uid, proc_move_ids, all_doc=all_doc, context=context)
 
1441
 
 
1442
        for procurement in procurement_ids:
 
1443
            # Closed procurement
 
1444
            wf_service.trg_validate(uid, 'procurement.order', procurement, 'subflow.cancel', cr)
 
1445
            wf_service.trg_validate(uid, 'procurement.order', procurement, 'button_check', cr)
 
1446
 
 
1447
 
 
1448
        if all_doc:
 
1449
            # Detach the PO from his workflow and set the state to done
 
1450
            for order_id in ids:
 
1451
                wf_service.trg_delete(uid, 'sale.order', order_id, cr)
 
1452
                # Search the method called when the workflow enter in last activity
 
1453
                wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'sale', 'act_done')[1]
 
1454
                activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
 
1455
                res = _eval_expr(cr, [uid, 'sale.order', order_id], False, activity.action)
 
1456
 
 
1457
        return True
 
1458
 
 
1459
    def _get_procurement_order_data(self, line, order, rts_date, context=None):
 
1460
        """
 
1461
        Get data for the  procurement order creation according to
 
1462
        sale.order.line and sale.order values.
 
1463
 
 
1464
        :param line: browse_record of a sale.order.line
 
1465
        :param order: browse_record of a sale.order
 
1466
        :param rts_date: Ready to ship date of the procurement order to create
 
1467
        :param context: Context of the call
 
1468
        """
 
1469
        if context is None:
 
1470
            context = {}
 
1471
 
 
1472
        # Check type of parameters
 
1473
        for param in [line, order]:
 
1474
            self._check_browse_param(param, '_create_procurement_order')
 
1475
 
 
1476
        if line.type == 'make_to_order':
 
1477
            location_id = order.shop_id.warehouse_id.lot_input_id.id
 
1478
        else:
 
1479
            location_id = order.shop_id.warehouse_id.lot_stock_id.id
 
1480
 
 
1481
        proc_data = {
 
1482
            'name': line.name,
 
1483
            'origin': order.name,
 
1484
            'product_qty': line.product_uom_qty,
 
1485
            'product_uom': line.product_uom.id,
 
1486
            'product_uos_qty': (line.product_uos and line.product_uos_qty)\
 
1487
                or line.product_uom_qty,
 
1488
            'product_uos': (line.product_uos and line.product_uos.id)\
 
1489
                or line.product_uom.id,
 
1490
            'location_id': location_id,
 
1491
            'procure_method': line.type,
 
1492
            'move_id': False,  # will be completed at ship state in action_ship_create method
 
1493
            'property_ids': [(6, 0, [x.id for x in line.property_ids])],
 
1494
            'company_id': order.company_id.id,
 
1495
            'supplier': line.supplier and line.supplier.id or False,
 
1496
            'po_cft': line.po_cft or False,
 
1497
            'date_planned': rts_date,
 
1498
            'from_yml_test': order.from_yml_test,
 
1499
            'so_back_update_dest_po_id_procurement_order': line.so_back_update_dest_po_id_sale_order_line.id,
 
1500
            'so_back_update_dest_pol_id_procurement_order': line.so_back_update_dest_pol_id_sale_order_line.id,
 
1501
            'sale_id': line.order_id.id,
 
1502
        }
 
1503
 
 
1504
        if line.product_id:
 
1505
            proc_data['product_id'] = line.product_id.id
 
1506
 
 
1507
        return proc_data
 
1508
 
 
1509
    def action_ship_proc_create(self, cr, uid, ids, context=None):
 
1510
        """
 
1511
        1/ Check of the analytic distribution
 
1512
        2/ Check if there is lines in order
 
1513
        3/ Update the delivery confirmed date of sale order in case of STOCK sale order
 
1514
           (check split_type_sale_order == 'stock_split_sale_order')
 
1515
        4/ Update the delivery confirmed date on sale order line
 
1516
        5/ Update the order policy of the sale order according to partner and order type
 
1517
        6/ Create and confirm the procurement orders according to line values
 
1518
 
 
1519
        :param cr: Cursor to the database
 
1520
        :param uid: ID of the user that runs the method
 
1521
        :param ids: List of IDs of the order to validate
 
1522
        :param context: Context of the call
 
1523
 
 
1524
        :return True if all order have been written
 
1525
        :rtype boolean
 
1526
        """
 
1527
        # Objects
 
1528
        wf_service = netsvc.LocalService("workflow")
 
1529
        sol_obj = self.pool.get('sale.order.line')
 
1530
        fields_tools = self.pool.get('fields.tools')
 
1531
        date_tools = self.pool.get('date.tools')
 
1532
        proc_obj = self.pool.get('procurement.order')
 
1533
        pol_obj = self.pool.get('purchase.order.line')
 
1534
 
 
1535
        if context is None:
 
1536
            context = {}
 
1537
 
 
1538
        if isinstance(ids, (int, long)):
 
1539
            ids = [ids]
 
1540
 
 
1541
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
1542
        order_brw_list = self.browse(cr, uid, ids)
 
1543
 
 
1544
        lines = []
 
1545
 
 
1546
        # 1/ Check of the analytic distribution
 
1547
        self.analytic_distribution_checks(cr, uid, order_brw_list)
 
1548
 
 
1549
        for order in order_brw_list:
 
1550
            o_write_vals = {}
 
1551
            # 2/ Check if there is lines in order
 
1552
            if len(order.order_line) < 1:
 
1553
                raise osv.except_osv(_('Error'), _('You cannot confirm a Field order without line !'))
 
1554
 
 
1555
            # 3/ Update the delivery confirmed date of sale order in case of STOCK sale order
 
1556
            #    (check split_type_sale_order == 'stock_split_sale_order')
 
1557
            delivery_confirmed_date = order.delivery_confirmed_date
 
1558
 
 
1559
            prep_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='preparation_lead_time', context=context)
 
1560
            rts = datetime.strptime(order.ready_to_ship_date, db_date_format)
 
1561
            rts = rts - relativedelta(days=prep_lt or 0)
 
1562
            rts = rts.strftime(db_date_format)
 
1563
 
 
1564
            # If the order is stock So, we update the confirmed delivery date
 
1565
            if order.split_type_sale_order == 'stock_split_sale_order':
 
1566
                # date values
 
1567
                ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
 
1568
                # confirmed
 
1569
                days_to_add = (ship_lt or 0) + (order.est_transport_lead_time or 0)
 
1570
                delivery_confirmed_date = (datetime.today() + relativedelta(days=days_to_add)).strftime(db_date_format)
 
1571
                # rts
 
1572
                o_rts = (datetime.today() + relativedelta(days=ship_lt or 0)).strftime(db_date_format)
 
1573
 
 
1574
                o_write_vals.update({
 
1575
                    'delivery_confirmed_date': delivery_confirmed_date,
 
1576
                    'ready_to_ship_date': o_rts,
 
1577
                })
 
1578
 
 
1579
            # Put a default delivery confirmed date
 
1580
            if not delivery_confirmed_date:
 
1581
                o_write_vals['delivery_confirmed_date'] = time.strftime('%Y-%m-%d')
 
1582
 
 
1583
            # For all lines, if the confirmed date is not filled, we copy the header value
 
1584
            line_to_write = sol_obj.search(cr, uid, [
 
1585
                ('order_id', '=', order.id),
 
1586
                ('confirmed_delivery_date', '=', False),
 
1587
            ], context=context)
 
1588
 
 
1589
            if line_to_write:
 
1590
                sol_obj.write(cr, uid, line_to_write, {
 
1591
                    'confirmed_delivery_date': o_write_vals.get('delivery_confirmed_date', order.delivery_confirmed_date),
 
1592
                }, context=context)
 
1593
 
 
1594
            if (order.partner_id.partner_type == 'internal' and order.order_type == 'regular') or \
 
1595
               order.order_type in ['donation_exp', 'donation_st', 'loan']:
 
1596
                o_write_vals['order_policy'] = 'manual'
 
1597
                for line in order.order_line:
 
1598
                    lines.append(line.id)
 
1599
 
 
1600
            # flag to prevent the display of the sale order log message
 
1601
            # if the method is called after po update, we do not display log message
 
1602
            display_log = True
 
1603
            for line in order.order_line:
 
1604
                # these lines are valid for all types (stock and order)
 
1605
                # when the line is sourced, we already get a procurement for the line
 
1606
                # when the line is confirmed, the corresponding procurement order has already been processed
 
1607
                # if the line is draft, either it is the first call, or we call the method again after having added a line in the procurement's po
 
1608
                if line.state not in ['sourced', 'confirmed', 'done'] and not line.created_by_po_line and not line.procurement_id and line.product_id:
 
1609
                    proc_data = self._get_procurement_order_data(line, order, rts, context=context)
 
1610
                    proc_id = proc_obj.create(cr, uid, proc_data, context=context)
 
1611
                    # set the flag for log message
 
1612
                    if line.so_back_update_dest_po_id_sale_order_line or line.created_by_po:
 
1613
                        display_log = False
 
1614
 
 
1615
                    if line.created_by_po_line:
 
1616
                        pol_obj.write(cr, uid, [line.created_by_po_line.id], {'procurement_id': proc_id}, context=context)
 
1617
 
 
1618
                    line_values = {
 
1619
                        'procurement_id': proc_id,
 
1620
                    }
 
1621
                    # if the line is draft (it should be the case), we set its state to 'sourced'
 
1622
                    if line.state == 'draft':
 
1623
                        line_values['state'] = 'sourced'
 
1624
 
 
1625
                    # Avoid a second write on the line if the line must be set as invoiced
 
1626
                    if line.id in lines:
 
1627
                        line_values['invoiced'] = 1
 
1628
                        lines.remove(line.id)
 
1629
 
 
1630
                    sol_obj.write(cr, uid, [line.id], line_values, context=context)
 
1631
 
 
1632
                    wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
 
1633
 
 
1634
                    if line.created_by_po:
 
1635
                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
 
1636
                        proc_obj.write(cr, uid, [proc_id], {'state': 'running'}, context=context)
 
1637
 
 
1638
            # the Fo is sourced we set the state
 
1639
            o_write_vals['state'] = 'sourced'
 
1640
            self.write(cr, uid, [order.id], o_write_vals, context=context)
 
1641
            # display message for sourced
 
1642
            if display_log:
 
1643
                self.log(cr, uid, order.id, _('The split \'%s\' is sourced.') % (order.name))
 
1644
 
 
1645
        if lines:
 
1646
            sol_obj.write(cr, uid, lines, {'invoiced': 1}, context=context)
 
1647
 
 
1648
        return True
 
1649
 
 
1650
    def test_lines(self, cr, uid, ids, context=None):
 
1651
        '''
 
1652
        return True if all lines of type 'make_to_order' are 'confirmed'
 
1653
 
 
1654
        only if a product is selected
 
1655
        internal requests are not taken into account (should not be the case anyway because of separate workflow)
 
1656
        '''
 
1657
        line_obj = self.pool.get('sale.order.line')
 
1658
 
 
1659
        if context is None:
 
1660
            context = {}
 
1661
 
 
1662
        if isinstance(ids, (int, long)):
 
1663
            ids = [ids]
 
1664
 
 
1665
        # Update the context to get IR lines
 
1666
        context['procurement_request'] = True
 
1667
 
 
1668
        for order in self.read(cr, uid, ids, ['from_yml_test'], context=context):
 
1669
            # backward compatibility for yml tests, if test we do not wait
 
1670
            if order['from_yml_test']:
 
1671
                continue
 
1672
 
 
1673
            line_error = line_obj.search(cr, uid, [
 
1674
                ('order_id', '=', order['id']),
 
1675
                ('product_id', '!=', False),
 
1676
                ('type', '=', 'make_to_order',),
 
1677
                ('state', '!=', 'confirmed'),
 
1678
                '|',
 
1679
                ('procurement_id', '=', 'False'),
 
1680
                ('procurement_id.state', '!=', 'cancel'),
 
1681
            ], count=True, context=context)
 
1682
 
 
1683
            if line_error:
 
1684
                return False
 
1685
 
 
1686
        return True
336
1687
 
337
1688
sale_order()
338
1689
 
 
1690
 
 
1691
class sale_order_line(osv.osv):
 
1692
    _name = 'sale.order.line'
 
1693
    _inherit = 'sale.order.line'
 
1694
 
 
1695
    _columns = {'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Sale Price Computation'), readonly=True, states={'draft': [('readonly', False)]}),
 
1696
                '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
 
1697
                'partner_id': fields.related('order_id', 'partner_id', relation="res.partner", readonly=True, type="many2one", string="Customer"),
 
1698
                # this field is used when the po is modified during on order process, and the so must be modified accordingly
 
1699
                # the resulting new purchase order line will be merged in specified po_id
 
1700
                'so_back_update_dest_po_id_sale_order_line': fields.many2one('purchase.order', string='Destination of new purchase order line', readonly=True),
 
1701
                'so_back_update_dest_pol_id_sale_order_line': fields.many2one('purchase.order.line', string='Original purchase order line', readonly=True),
 
1702
                'state': fields.selection(SALE_ORDER_LINE_STATE_SELECTION, 'State', required=True, readonly=True,
 
1703
                help='* The \'Draft\' state is set when the related sales order in draft state. \
 
1704
                    \n* The \'Confirmed\' state is set when the related sales order is confirmed. \
 
1705
                    \n* The \'Exception\' state is set when the related sales order is set as exception. \
 
1706
                    \n* The \'Done\' state is set when the sales order line has been picked. \
 
1707
                    \n* The \'Cancelled\' state is set when a user cancel the sales order related.'),
 
1708
 
 
1709
                # This field is used to identify the FO PO line between 2 instances of the sync
 
1710
                'sync_order_line_db_id': fields.text(string='Sync order line DB Id', required=False, readonly=True),
 
1711
                'original_line_id': fields.many2one('sale.order.line', string='Original line', help='ID of the original line before the split'),
 
1712
                'manually_corrected': fields.boolean(string='FO line is manually corrected by user'),
 
1713
                'created_by_po': fields.many2one('purchase.order', string='Created by PO'),
 
1714
                'created_by_po_line': fields.many2one('purchase.order.line', string='Created by PO line'),
 
1715
                'dpo_line_id': fields.many2one('purchase.order.line', string='DPO line'),
 
1716
                }
 
1717
 
 
1718
    _defaults = {
 
1719
        'is_line_split': False,  # UTP-972: By default set False, not split
 
1720
    }
 
1721
 
 
1722
    def ask_unlink(self, cr, uid, ids, context=None):
 
1723
        '''
 
1724
        Call the user to know if the line must be re-sourced
 
1725
        '''
 
1726
        if context is None:
 
1727
            context = {}
 
1728
 
 
1729
        if isinstance(ids, (int, long)):
 
1730
            ids = [ids]
 
1731
 
 
1732
        for line in self.browse(cr, uid, ids, context=context):
 
1733
            if line.order_id and line.order_id.state != 'draft':
 
1734
                return self.pool.get('sale.order.line.unlink.wizard').ask_unlink(cr, uid, ids, context=context)
 
1735
 
 
1736
        return self.ask_order_unlink(cr, uid, ids, context=context)
 
1737
 
 
1738
    def ask_order_unlink(self, cr, uid, ids, context=None):
 
1739
        '''
 
1740
        Call the unlink method for lines and if the FO becomes empty,
 
1741
        ask the user if he wants to cancel the FO
 
1742
        '''
 
1743
        sale_ids = []
 
1744
        res = False
 
1745
        for line in self.read(cr, uid, ids, ['order_id'], context=context):
 
1746
            if line['order_id'][0] not in sale_ids:
 
1747
                sale_ids.append(line['order_id'][0])
 
1748
 
 
1749
        self.unlink(cr, uid, ids, context=context)
 
1750
 
 
1751
        for order in self.pool.get('sale.order').read(cr, uid, sale_ids, ['order_line'], context=context):
 
1752
            if len(order['order_line']) == 0:
 
1753
                res = self.pool.get('sale.order.unlink.wizard').ask_unlink(cr, uid, order['id'], context=context)
 
1754
 
 
1755
        return res
 
1756
 
 
1757
    def _check_restriction_line(self, cr, uid, ids, context=None):
 
1758
        '''
 
1759
        Check if there is restriction on lines
 
1760
        '''
 
1761
        if isinstance(ids, (int, long)):
 
1762
            ids = [ids]
 
1763
 
 
1764
        if context is None:
 
1765
            context = {}
 
1766
 
 
1767
        for line in self.browse(cr, uid, ids, context=context):
 
1768
            if line.order_id and line.order_id.partner_id and line.product_id:
 
1769
                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, 'obj_type': 'sale.order'}, context=context):
 
1770
                    return False
 
1771
 
 
1772
        return True
 
1773
 
 
1774
    def update_or_cancel_line(self, cr, uid, line, qty_diff, context=None):
 
1775
        '''
 
1776
        Update the quantity of the IR/FO line with the qty_diff - Update also
 
1777
        the quantity in procurement attached to the IR/Fo line.
 
1778
 
 
1779
        If the qty_diff is equal or larger than the line quantity, delete the
 
1780
        line and its procurement.
 
1781
        '''
 
1782
        # Documents
 
1783
        proc_obj = self.pool.get('procurement.order')
 
1784
 
 
1785
        wf_service = netsvc.LocalService("workflow")
 
1786
 
 
1787
        if context is None:
 
1788
            context = {}
 
1789
 
 
1790
        if isinstance(line, (int, long)):
 
1791
            line = self.browse(cr, uid, line, context=context)
 
1792
 
 
1793
        order = line.order_id and line.order_id.id
 
1794
 
 
1795
        if qty_diff >= line.product_uom_qty:
 
1796
            proc = line.procurement_id and line.procurement_id.id
 
1797
            # Delete the line and the procurement
 
1798
            self.write(cr, uid, [line.id], {'state': 'cancel'}, context=context)
 
1799
            self.unlink(cr, uid, [line.id], context=context)
 
1800
 
 
1801
            if proc:
 
1802
                proc_obj.write(cr, uid, [proc], {'product_qty': 0.00}, context=context)
 
1803
                proc_obj.action_cancel(cr, uid, [proc])
 
1804
        else:
 
1805
            minus_qty = line.product_uom_qty - qty_diff
 
1806
            proc = line.procurement_id and line.procurement_id.id
 
1807
            # Update the line and the procurement
 
1808
            self.write(cr, uid, [line.id], {'product_uom_qty': minus_qty,
 
1809
                                            'product_uos_qty': minus_qty}, context=context)
 
1810
            if proc:
 
1811
                proc_obj.write(cr, uid, [proc], {'product_qty': minus_qty}, context=context)
 
1812
 
 
1813
        if order:
 
1814
            wf_service.trg_write(uid, 'sale.order', order, cr)
 
1815
 
 
1816
        return True
 
1817
 
 
1818
    def add_resource_line(self, cr, uid, line, order_id, qty_diff, context=None):
 
1819
        '''
 
1820
        Add a copy of the original line (line) into the new order (order_id)
 
1821
        created to resource needs.
 
1822
        Update the product qty with the qty_diff in case of split or backorder moves
 
1823
        before cancelation
 
1824
        '''
 
1825
        # Documents
 
1826
        order_obj = self.pool.get('sale.order')
 
1827
        ad_obj = self.pool.get('analytic.distribution')
 
1828
        data_obj = self.pool.get('ir.model.data')
 
1829
 
 
1830
        if context is None:
 
1831
            context = {}
 
1832
 
 
1833
        if isinstance(line, (int, long)):
 
1834
            line = self.browse(cr, uid, line, context=context)
 
1835
 
 
1836
#        if not order_id and not line.order_id.procurement_request and line.order_id.original_so_id_sale_order:
 
1837
#            order_id = order_obj.create_resource_order(cr, uid, line.order_id.original_so_id_sale_order, context=context)
 
1838
#        elif not order_id and (line.order_id.procurement_request or not line.order_id.original_so_id_sale_order):
 
1839
        order_id = order_obj.create_resource_order(cr, uid, line.order_id, context=context)
 
1840
 
 
1841
        if not qty_diff:
 
1842
            qty_diff = line.product_uom_qty
 
1843
 
 
1844
        values = {
 
1845
            'order_id': order_id,
 
1846
            'product_uom_qty': qty_diff,
 
1847
            'product_uos_qty': qty_diff,
 
1848
            'procurement_id': False
 
1849
        }
 
1850
        context['keepDateAndDistrib'] = True
 
1851
        if not line.analytic_distribution_id and line.order_id and line.order_id.analytic_distribution_id:
 
1852
            new_distrib = ad_obj.copy(cr, uid, line.order_id.analytic_distribution_id.id, {}, context=context)
 
1853
            values['analytic_distribution_id'] = new_distrib
 
1854
 
 
1855
        line_id = self.copy(cr, uid, line.id, values, context=context)
 
1856
 
 
1857
        order_name = self.pool.get('sale.order').read(cr, uid, [order_id], ['name'], context=context)[0]['name']
 
1858
 
 
1859
        if line.order_id and line.order_id.procurement_request:
 
1860
            view_id = data_obj.get_object_reference(cr, uid, 'procurement_request', 'procurement_request_form_view')[1]
 
1861
        else:
 
1862
            view_id = data_obj.get_object_reference(cr, uid, 'sale', 'view_order_form')[1]
 
1863
        context.update({'view_id': view_id})
 
1864
        self.pool.get('sale.order').log(cr, uid, order_id, _('A line was added to the Field Order %s to re-source the canceled line.') % (order_name), context=context)
 
1865
 
 
1866
        return line_id
 
1867
 
 
1868
    def open_split_wizard(self, cr, uid, ids, context=None):
 
1869
        '''
 
1870
        Open the wizard to split the line
 
1871
        '''
 
1872
        if not context:
 
1873
            context = {}
 
1874
 
 
1875
        if isinstance(ids, (int, long)):
 
1876
            ids = [ids]
 
1877
 
 
1878
        for line in self.browse(cr, uid, ids, context=context):
 
1879
            data = {'sale_line_id': line.id, 'original_qty': line.product_uom_qty, 'old_line_qty': line.product_uom_qty}
 
1880
            wiz_id = self.pool.get('split.sale.order.line.wizard').create(cr, uid, data, context=context)
 
1881
            return {'type': 'ir.actions.act_window',
 
1882
                    'res_model': 'split.sale.order.line.wizard',
 
1883
                    'view_type': 'form',
 
1884
                    'view_mode': 'form',
 
1885
                    'target': 'new',
 
1886
                    'res_id': wiz_id,
 
1887
                    'context': context}
 
1888
 
 
1889
    def copy_data(self, cr, uid, id, default=None, context=None):
 
1890
        '''
 
1891
        reset link to purchase order from update of on order purchase order
 
1892
        '''
 
1893
        if not default:
 
1894
            default = {}
 
1895
        # if the po link is not in default, we set both to False (both values are closely related)
 
1896
        if 'so_back_update_dest_po_id_sale_order_line' not in default:
 
1897
            default.update({'so_back_update_dest_po_id_sale_order_line': False,
 
1898
                            'so_back_update_dest_pol_id_sale_order_line': False, })
 
1899
        default.update({'sync_order_line_db_id': False, 'manually_corrected': False})
 
1900
 
 
1901
        return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
 
1902
 
 
1903
    def open_order_line_to_correct(self, cr, uid, ids, context=None):
 
1904
        '''
 
1905
        Open Order Line in form view
 
1906
        '''
 
1907
        if context is None:
 
1908
            context = {}
 
1909
        if isinstance(ids, (int, long)):
 
1910
            ids = [ids]
 
1911
        obj_data = self.pool.get('ir.model.data')
 
1912
        view_id = obj_data.get_object_reference(cr, uid, 'sale_override', 'view_order_line_to_correct_form')[1]
 
1913
        view_to_return = {
 
1914
            'view_type': 'form',
 
1915
            'view_mode': 'form',
 
1916
            'res_model': 'sale.order.line',
 
1917
            'type': 'ir.actions.act_window',
 
1918
            'res_id': ids[0],
 
1919
            'target': 'new',
 
1920
            'context': context,
 
1921
            'view_id': [view_id],
 
1922
        }
 
1923
        return view_to_return
 
1924
 
 
1925
    def save_and_close(self, cr, uid, ids, context=None):
 
1926
        '''
 
1927
        Save and close the configuration window
 
1928
        '''
 
1929
        uom_obj = self.pool.get('product.uom')
 
1930
        obj_data = self.pool.get('ir.model.data')
 
1931
        tbd_uom = obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'uom_tbd')[1]
 
1932
        obj_browse = self.browse(cr, uid, ids, context=context)
 
1933
        vals = {}
 
1934
        message = ''
 
1935
        for var in obj_browse:
 
1936
            if var.product_uom.id == tbd_uom:
 
1937
                message += 'You have to define a valid UOM, i.e. not "To be define".'
 
1938
            if var.nomen_manda_0.id == obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'nomen_tbd0')[1]:
 
1939
                message += 'You have to define a valid Main Type (in tab "Nomenclature Selection"), i.e. not "To be define".'
 
1940
            if var.nomen_manda_1.id == obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'nomen_tbd1')[1]:
 
1941
                message += 'You have to define a valid Group (in tab "Nomenclature Selection"), i.e. not "To be define".'
 
1942
            if var.nomen_manda_2.id == obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'nomen_tbd2')[1]:
 
1943
                message += 'You have to define a valid Family (in tab "Nomenclature Selection"), i.e. not "To be define".'
 
1944
        # the 3rd level is not mandatory
 
1945
        if message:
 
1946
            raise osv.except_osv(_('Warning !'), _(message))
 
1947
 
 
1948
        self.write(cr, uid, ids, vals, context=context)
 
1949
        view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'procurement_request', 'procurement_request_form_view')[1]
 
1950
        return {'type': 'ir.actions.act_window_close',
 
1951
                'res_model': 'sale.order',
 
1952
                'view_type': 'form',
 
1953
                'view_mode': 'form',
 
1954
                'target': 'new',
 
1955
                'view_id': [view_id],
 
1956
                }
 
1957
 
 
1958
    def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
 
1959
            uom=False, qty_uos=0, uos=False, name='', partner_id=False,
 
1960
            lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
 
1961
        """
 
1962
        If we select a product we change the procurement type to its own procurement method (procure_method).
 
1963
        If there isn't product, the default procurement method is 'From Order' (make_to_order).
 
1964
        Both remains changeable manually.
 
1965
        """
 
1966
        product_obj = self.pool.get('product.product')
 
1967
 
 
1968
        res = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty,
 
1969
            uom, qty_uos, uos, name, partner_id,
 
1970
            lang, update_tax, date_order, packaging, fiscal_position, flag)
 
1971
 
 
1972
        if 'domain' in res:
 
1973
            del res['domain']
 
1974
 
 
1975
        if product:
 
1976
            if partner_id:
 
1977
                # Test the compatibility of the product with the partner of the order
 
1978
                res, test = product_obj._on_change_restriction_error(cr, uid, product, field_name='product_id', values=res, vals={'partner_id': partner_id, 'obj_type': 'sale.order'})
 
1979
                if test:
 
1980
                    return res
 
1981
 
 
1982
            type = product_obj.read(cr, uid, [product], 'procure_method')[0]['procure_method']
 
1983
            if 'value' in res:
 
1984
                res['value'].update({'type': type})
 
1985
            else:
 
1986
                res.update({'value':{'type': type}})
 
1987
            res['value'].update({'product_uom_qty': qty, 'product_uos_qty': qty})
 
1988
        elif not product:
 
1989
            if 'value' in res:
 
1990
                res['value'].update({'type': 'make_to_order'})
 
1991
            else:
 
1992
                res.update({'value':{'type': 'make_to_order'}})
 
1993
            res['value'].update({'product_uom_qty': 0.00, 'product_uos_qty': 0.00})
 
1994
 
 
1995
        return res
 
1996
 
 
1997
    def default_get(self, cr, uid, fields, context=None):
 
1998
        """
 
1999
        Default procurement method is 'on order' if no product selected
 
2000
        """
 
2001
        if not context:
 
2002
            context = {}
 
2003
 
 
2004
        if context.get('sale_id'):
 
2005
            # Check validity of the field order. We write the order to avoid
 
2006
            # the creation of a new line if one line of the order is not valid
 
2007
            # according to the order category
 
2008
            # Example :
 
2009
            #    1/ Create a new FO with 'Other' as Order Category
 
2010
            #    2/ Add a new line with a Stockable product
 
2011
            #    3/ Change the Order Category of the FO to 'Service' -> A warning message is displayed
 
2012
            #    4/ Try to create a new line -> The system displays a message to avoid you to create a new line
 
2013
            #       while the not valid line is not modified/deleted
 
2014
            #
 
2015
            #   Without the write of the order, the message displayed by the system at 4/ is displayed at the saving
 
2016
            #   of the new line that is not very understandable for the user
 
2017
            data = {}
 
2018
            if context.get('partner_id'):
 
2019
                data.update({'partner_id': context.get('partner_id')})
 
2020
            if context.get('categ'):
 
2021
                data.update({'categ': context.get('categ')})
 
2022
            self.pool.get('sale.order').write(cr, uid, [context.get('sale_id')], data, context=context)
 
2023
 
 
2024
        default_data = super(sale_order_line, self).default_get(cr, uid, fields, context=context)
 
2025
        default_data.update({'product_uom_qty': 0.00, 'product_uos_qty': 0.00})
 
2026
        sale_id = context.get('sale_id', [])
 
2027
        if not sale_id:
 
2028
            return default_data
 
2029
        else:
 
2030
            default_data.update({'type': 'make_to_order'})
 
2031
        return default_data
 
2032
 
 
2033
    def copy(self, cr, uid, id, default=None, context=None):
 
2034
        '''
 
2035
        copy from sale order line
 
2036
        '''
 
2037
        if not context:
 
2038
            context = {}
 
2039
 
 
2040
        if not default:
 
2041
            default = {}
 
2042
 
 
2043
        default.update({'sync_order_line_db_id': False, 'manually_corrected': False})
 
2044
 
 
2045
        return super(sale_order_line, self).copy(cr, uid, id, default, context)
 
2046
 
 
2047
    def check_empty_line(self, cr, uid, ids, vals, context=None):
 
2048
        '''
 
2049
        Return an error if the line has no qty
 
2050
        '''
 
2051
        context = context is None and {} or context
 
2052
 
 
2053
        if not context.get('noraise') and not context.get('import_in_progress'):
 
2054
            if ids and not 'product_uom_qty' in vals:
 
2055
                empty_lines = self.search(cr, uid, [
 
2056
                    ('id', 'in', ids),
 
2057
                    ('order_id.state', 'not in', ['draft', 'cancel']),
 
2058
                    ('product_uom_qty', '<=', 0.00),
 
2059
                ], count=True, context=context)
 
2060
                if empty_lines:
 
2061
                        raise osv.except_osv(_('Error'), _('A line must a have a quantity larger than 0.00'))
 
2062
 
 
2063
        return True
 
2064
 
 
2065
    def create(self, cr, uid, vals, context=None):
 
2066
        """
 
2067
        Override create method so that the procurement method is on order if no product is selected
 
2068
        If it is a procurement request, we update the cost price.
 
2069
        """
 
2070
        if context is None:
 
2071
            context = {}
 
2072
        if not vals.get('product_id') and context.get('sale_id', []):
 
2073
            vals.update({'type': 'make_to_order'})
 
2074
 
 
2075
        self.check_empty_line(cr, uid, False, vals, context=context)
 
2076
 
 
2077
        # UF-1739: as we do not have product_uos_qty in PO (only in FO), we recompute here the product_uos_qty for the SYNCHRO
 
2078
        qty = vals.get('product_uom_qty')
 
2079
        product_id = vals.get('product_id')
 
2080
        product_obj = self.pool.get('product.product')
 
2081
        if product_id and qty:
 
2082
            if isinstance(qty, str):
 
2083
                qty = float(qty)
 
2084
            vals.update({'product_uos_qty' : qty * product_obj.read(cr, uid, product_id, ['uos_coeff'])['uos_coeff']})
 
2085
 
 
2086
        # Internal request
 
2087
        order_id = vals.get('order_id', False)
 
2088
        if order_id and self.pool.get('sale.order').read(cr, uid, order_id, ['procurement_request'], context)['procurement_request']:
 
2089
            vals.update({'cost_price': vals.get('cost_price', False)})
 
2090
 
 
2091
        '''
 
2092
        Add the database ID of the SO line to the value sync_order_line_db_id
 
2093
        '''
 
2094
 
 
2095
        so_line_ids = super(sale_order_line, self).create(cr, uid, vals, context=context)
 
2096
        if not vals.get('sync_order_line_db_id', False):  # 'sync_order_line_db_id' not in vals or vals:
 
2097
            if vals.get('order_id', False):
 
2098
                name = self.pool.get('sale.order').browse(cr, uid, vals.get('order_id'), context=context).name
 
2099
                super(sale_order_line, self).write(cr, uid, so_line_ids, {'sync_order_line_db_id': name + "_" + str(so_line_ids), } , context=context)
 
2100
 
 
2101
        return so_line_ids
 
2102
 
 
2103
    def write(self, cr, uid, ids, vals, context=None):
 
2104
        """
 
2105
        Override write method so that the procurement method is on order if no product is selected.
 
2106
        If it is a procurement request, we update the cost price.
 
2107
        """
 
2108
        if context is None:
 
2109
            context = {}
 
2110
 
 
2111
        # UTP-392: fixed from the previous code: check if the sale order line contains the product, and not only from vals!
 
2112
        product_id = vals.get('product_id')
 
2113
        if context.get('sale_id', False):
 
2114
            if not product_id:
 
2115
                product_id = self.browse(cr, uid, ids, context=context)[0].product_id
 
2116
 
 
2117
            if not product_id:
 
2118
                vals.update({'type': 'make_to_order'})
 
2119
        # Internal request
 
2120
        order_id = vals.get('order_id', False)
 
2121
        if order_id and self.pool.get('sale.order').read(cr, uid, order_id, ['procurement_request'], context)['procurement_request']:
 
2122
            vals.update({'cost_price': vals.get('cost_price', False)})
 
2123
 
 
2124
        self.check_empty_line(cr, uid, ids, vals, context=context)
 
2125
 
 
2126
        res = super(sale_order_line, self).write(cr, uid, ids, vals, context=context)
 
2127
 
 
2128
        return res
 
2129
 
 
2130
sale_order_line()
 
2131
 
 
2132
 
 
2133
class procurement_order(osv.osv):
 
2134
    _inherit = 'procurement.order'
 
2135
 
 
2136
    _columns = {
 
2137
        'sale_id': fields.many2one('sale.order', string='Sale'),
 
2138
    }
 
2139
 
 
2140
procurement_order()
 
2141
 
 
2142
 
 
2143
class sale_config_picking_policy(osv.osv_memory):
 
2144
    """
 
2145
    Set order_policy to picking
 
2146
    """
 
2147
    _name = 'sale.config.picking_policy'
 
2148
    _inherit = 'sale.config.picking_policy'
 
2149
 
 
2150
    _defaults = {
 
2151
        'order_policy': 'picking',
 
2152
    }
 
2153
 
 
2154
sale_config_picking_policy()
 
2155
 
 
2156
class sale_order_line_unlink_wizard(osv.osv_memory):
 
2157
    _name = 'sale.order.line.unlink.wizard'
 
2158
 
 
2159
    _columns = {
 
2160
            'order_line_id': fields.many2one('sale.order.line', 'Line to delete'),
 
2161
            }
 
2162
 
 
2163
    def ask_unlink(self, cr, uid, order_line_id, context=None):
 
2164
        '''
 
2165
        Return the wizard
 
2166
        '''
 
2167
        context = context or {}
 
2168
 
 
2169
        if isinstance(order_line_id, (int, long)):
 
2170
            order_line_id = [order_line_id]
 
2171
 
 
2172
        wiz_id = self.create(cr, uid, {'order_line_id': order_line_id[0]}, context=context)
 
2173
 
 
2174
        return {'type': 'ir.actions.act_window',
 
2175
                'res_model': self._name,
 
2176
                'res_id': wiz_id,
 
2177
                'view_type': 'form',
 
2178
                'view_mode': 'form',
 
2179
                'target': 'new',
 
2180
                'context': context}
 
2181
 
 
2182
    def close_window(self, cr, uid, ids, context=None):
 
2183
        '''
 
2184
        Close the pop-up and reload the FO
 
2185
        '''
 
2186
        return {'type': 'ir.actions.act_window_close'}
 
2187
 
 
2188
    def cancel_fo_line(self, cr, uid, ids, context=None):
 
2189
        '''
 
2190
        Cancel the FO line and display the FO form
 
2191
        '''
 
2192
        context = context or {}
 
2193
 
 
2194
        if isinstance(ids, (int, long)):
 
2195
            ids = [ids]
 
2196
 
 
2197
        res = False
 
2198
 
 
2199
        for wiz in self.browse(cr, uid, ids, context=context):
 
2200
            res = self.pool.get('sale.order.line').ask_order_unlink(cr, uid, [wiz.order_line_id.id], context=context)
 
2201
            break
 
2202
 
 
2203
        return res or {'type': 'ir.actions.act_window_close'}
 
2204
 
 
2205
    def resource_line(self, cr, uid, ids, context=None):
 
2206
        '''
 
2207
        Resource the FO line and display the FO form
 
2208
        '''
 
2209
        context = context or {}
 
2210
 
 
2211
        if isinstance(ids, (int, long)):
 
2212
            ids = [ids]
 
2213
 
 
2214
        for wiz in self.browse(cr, uid, ids, context=context):
 
2215
            self.pool.get('sale.order.line').add_resource_line(cr, uid, wiz.order_line_id.id, False, wiz.order_line_id.product_uom_qty, context=context)
 
2216
 
 
2217
        return self.cancel_fo_line(cr, uid, ids, context=context)
 
2218
 
 
2219
sale_order_line_unlink_wizard()
 
2220
 
 
2221
class sale_order_unlink_wizard(osv.osv_memory):
 
2222
    _name = 'sale.order.unlink.wizard'
 
2223
 
 
2224
    _columns = {
 
2225
        'order_id': fields.many2one('sale.order', 'Order to delete'),
 
2226
    }
 
2227
 
 
2228
    def ask_unlink(self, cr, uid, order_id, context=None):
 
2229
        '''
 
2230
        Return the wizard
 
2231
        '''
 
2232
        context = context or {}
 
2233
 
 
2234
        wiz_id = self.create(cr, uid, {'order_id': order_id}, context=context)
 
2235
        context['view_id'] = False
 
2236
 
 
2237
        return {'type': 'ir.actions.act_window',
 
2238
                'res_model': self._name,
 
2239
                'res_id': wiz_id,
 
2240
                'view_type': 'form',
 
2241
                'view_mode': 'form',
 
2242
                'target': 'new',
 
2243
                'context': context}
 
2244
 
 
2245
    def close_window(self, cr, uid, ids, context=None):
 
2246
        '''
 
2247
        Close the pop-up and reload the FO
 
2248
        '''
 
2249
        return {'type': 'ir.actions.act_window_close'}
 
2250
 
 
2251
    def cancel_fo(self, cr, uid, ids, context=None):
 
2252
        '''
 
2253
        Cancel the FO and display the FO form
 
2254
        '''
 
2255
        context = context or {}
 
2256
 
 
2257
        if isinstance(ids, (int, long)):
 
2258
            ids = [ids]
 
2259
 
 
2260
        for wiz in self.browse(cr, uid, ids, context=context):
 
2261
            self.pool.get('sale.order').action_cancel(cr, uid, [wiz.order_id.id], context=context)
 
2262
 
 
2263
        return {'type': 'ir.actions.act_window_close'}
 
2264
 
 
2265
sale_order_unlink_wizard()
 
2266
 
 
2267
 
 
2268
class sale_order_cancelation_wizard(osv.osv_memory):
 
2269
    _name = 'sale.order.cancelation.wizard'
 
2270
 
 
2271
    _columns = {
 
2272
        'order_id': fields.many2one('sale.order', 'Order to delete', required=True),
 
2273
    }
 
2274
 
 
2275
    def only_cancel(self, cr, uid, ids, context=None):
 
2276
        '''
 
2277
        Cancel the FO w/o re-sourcing lines
 
2278
        '''
 
2279
        # Objects
 
2280
        sale_obj = self.pool.get('sale.order')
 
2281
 
 
2282
        # Variables initialization
 
2283
        if not context:
 
2284
            context = {}
 
2285
 
 
2286
        if isinstance(ids, (int, long)):
 
2287
            ids = [id]
 
2288
 
 
2289
        for wiz in self.browse(cr, uid, ids, context=context):
 
2290
            sale_obj.action_cancel(cr, uid, [wiz.order_id.id], context=context)
 
2291
 
 
2292
        return {'type': 'ir.actions.act_window_close'}
 
2293
 
 
2294
    def resource_lines(self, cr, uid, ids, context=None):
 
2295
        '''
 
2296
        Cancel the FO and re-source all lines
 
2297
        '''
 
2298
        # Objects
 
2299
        sale_obj = self.pool.get('sale.order')
 
2300
        line_obj = self.pool.get('sale.order.line')
 
2301
 
 
2302
        # Variables initialization
 
2303
        if not context:
 
2304
            context = {}
 
2305
 
 
2306
        if isinstance(ids, (int, long)):
 
2307
            ids = [ids]
 
2308
 
 
2309
        wf_service = netsvc.LocalService("workflow")
 
2310
 
 
2311
        for wiz in self.browse(cr, uid, ids, context=context):
 
2312
            # Re-source lines
 
2313
            for line in wiz.order_id.order_line:
 
2314
                line_obj.add_resource_line(cr, uid, line.id, line.order_id.id, line.product_uom_qty, context=context)
 
2315
 
 
2316
            # Cancel FO
 
2317
            wf_service.trg_validate(uid, 'sale.order', wiz.order_id.id, 'cancel', cr)
 
2318
 
 
2319
        return {'type': 'ir.actions.act_window_close'}
 
2320
 
 
2321
sale_order_cancelation_wizard()
 
2322
 
339
2323
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: