1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 TeMPO Consulting, MSF
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
22
from osv import osv, fields
23
from osv.orm import browse_record
24
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
26
from datetime import datetime, timedelta
27
from dateutil.relativedelta import relativedelta
28
from mx.DateTime import *
30
from tools.translate import _
32
from workflow.wkf_expr import _eval_expr
34
import decimal_precision as dp
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
41
class sale_order(osv.osv):
43
_inherit = 'sale.order'
48
def _check_browse_param(self, param, method):
50
Returns an error message if the parameter is not a
51
browse_record instance
53
:param param: The parameter to test
54
:param method: Name of the method that call the _check_browse_param()
58
if not isinstance(param, browse_record):
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)
67
def copy(self, cr, uid, id, default=None, context=None):
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
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
81
:return ID of the new sale.order
90
# if the copy comes from the button duplicate
91
if context.get('from_button'):
92
default.update({'is_a_counterpart': False})
94
if 'loan_id' not in default:
95
default.update({'loan_id': False})
98
'order_policy': 'picking',
100
'sourcing_trace': '',
101
'sourcing_trace_ok': False,
104
if not context.get('keepClientOrder', False):
105
default.update({'client_order_ref': False})
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})
117
return super(sale_order, self).copy(cr, uid, id, default=default, context=context)
119
def unlink(self, cr, uid, ids, context=None):
121
Check if the status of the unlinked FO is allowed for unlink.
122
Statuses allowed : draft / cancel
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)
130
def action_cancel(self, cr, uid, ids, context=None):
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)
138
#@@@override sale.sale_order._invoiced
139
def _invoiced(self, cr, uid, ids, name, arg, context=None):
141
Return True is the sale order is an uninvoiced order
143
partner_obj = self.pool.get('res.partner')
147
for sale in self.browse(cr, uid, ids):
149
partner = partner_obj.browse(cr, uid, [sale.partner_id.id])[0]
150
if sale.state != 'draft' and (sale.order_type != 'regular' or (partner and partner.partner_type == 'internal')):
154
for invoice in sale.invoice_ids:
155
if invoice.state != 'paid':
158
if not sale.invoice_ids:
163
#@@@override sale.sale_order._invoiced_search
164
def _invoiced_search(self, cursor, user, obj, name, args, context=None):
173
clause += 'AND inv.state = \'paid\' OR (sale.state != \'draft\' AND (sale.order_type != \'regular\' OR part.partner_type = \'internal\'))'
175
clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND sale.order_type = \'regular\''
178
cursor.execute('SELECT rel.order_id ' \
179
'FROM sale_order_invoice_rel AS rel, account_invoice AS inv, sale_order AS sale, res_partner AS part ' + sale_clause + \
180
'WHERE rel.invoice_id = inv.id AND rel.order_id = sale.id AND sale.partner_id = part.id ' + clause)
181
res = cursor.fetchall()
183
cursor.execute('SELECT sale.id ' \
184
'FROM sale_order AS sale, res_partner AS part ' \
185
'WHERE sale.id NOT IN ' \
186
'(SELECT rel.order_id ' \
187
'FROM sale_order_invoice_rel AS rel) and sale.state != \'cancel\'' \
188
'AND sale.partner_id = part.id ' \
189
'AND sale.order_type = \'regular\' AND part.partner_type != \'internal\'')
190
res.extend(cursor.fetchall())
192
return [('id', '=', 0)]
193
return [('id', 'in', [x[0] for x in res])]
196
#@@@override sale.sale_order._invoiced_rate
197
def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
199
for sale in self.browse(cursor, user, ids, context=context):
204
for line in sale.order_line:
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
210
res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
216
def _get_noinvoice(self, cr, uid, ids, name, arg, context=None):
218
for sale in self.browse(cr, uid, ids):
219
res[sale.id] = sale.order_type != 'regular' or sale.partner_id.partner_type == 'internal'
222
def _vals_get_sale_override(self, cr, uid, ids, fields, arg, context=None):
227
for obj in self.browse(cr, uid, ids, context=context):
230
result[obj.id].update({f:False})
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'
239
def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
242
for order in self.browse(cr, uid, ids, context=context):
244
for line in order.order_line:
245
res[order.id] = False
247
# better: if order.order_line: res[order.id] = False
251
def _get_manually_corrected(self, cr, uid, ids, field_name, args, context=None):
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:
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),
268
'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
269
('donation_st', 'Standard donation'), ('loan', 'Loan'), ],
270
string='Order Type', required=True, readonly=True, states={'draft': [('readonly', False)]}),
271
'loan_id': fields.many2one('purchase.order', string='Linked loan', readonly=True),
272
'priority': fields.selection(ORDER_PRIORITY, string='Priority', readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
273
'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, 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)]}),
276
'invoiced': fields.function(_invoiced, method=True, string='Paid',
277
fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid."),
278
'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
279
'noinvoice': fields.function(_get_noinvoice, method=True, string="Don't create an invoice", type='boolean'),
280
'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', readonly=True, states={'draft': [('readonly', False)], 'validated': [('readonly', False)]}),
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).'),
317
'order_type': lambda *a: 'regular',
318
'invoice_quantity': lambda *a: 'procurement',
319
'priority': lambda *a: 'normal',
320
'categ': lambda *a: 'other',
321
'loan_duration': lambda *a: 2,
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',
327
'no_line': lambda *a: True,
330
def _check_empty_line(self, cr, uid, ids, context=None):
332
Check if all lines have a quantity larger than 0.00
335
line_obj = self.pool.get('sale.order.line')
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)
350
(_check_empty_line, 'All lines must have a quantity larger than 0.00', ['order_line']),
353
def _check_own_company(self, cr, uid, company_id, context=None):
355
Remove the possibility to make a SO to user's company
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 !'))
363
def _check_restriction_line(self, cr, uid, ids, context=None):
365
Check restriction on products
367
if isinstance(ids, (int, long)):
370
line_obj = self.pool.get('sale.order.line')
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)
378
def onchange_partner_id(self, cr, uid, ids, part=False, order_type=False, *a, **b):
380
Set the intl_customer_ok field if the partner is an ESC or an international partner
382
res = super(sale_order, self).onchange_partner_id(cr, uid, ids, part)
384
if part and order_type:
385
res2 = self.onchange_order_type(cr, uid, ids, order_type, part)
386
if res2.get('value'):
388
res['value'].update(res2['value'])
390
res.update({'value': res2['value']})
392
# Check the restrction of product in lines
394
product_obj = self.pool.get('product.product')
395
for order in self.browse(cr, uid, ids):
396
for line in order.order_line:
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'})
400
res.setdefault('value', {}).update({'partner_order_id': False, 'partner_shipping_id': False, 'partner_invoice_id': False})
405
def onchange_categ(self, cr, uid, ids, categ, context=None):
407
Check if the list of products is valid for this new category
409
if isinstance(ids, (int, long)):
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'
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)))
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)})
431
return {'warning': message}
433
def _check_service(self, cr, uid, ids, vals, context=None):
435
Avoid the saving of a FO with a non service products on Service FO
437
categ = {'transport': _('Transport'),
438
'service': _('Service')}
439
if isinstance(ids, (int, long)):
443
if context.get('import_in_progress'):
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))
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))
457
def create(self, cr, uid, vals, context=None):
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)
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'
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'
477
res = super(sale_order, self).create(cr, uid, vals, context)
478
self._check_service(cr, uid, [res], vals, context=context)
481
def write(self, cr, uid, ids, vals, context=None):
483
Remove the possibility to make a SO to user's company
485
if isinstance(ids, (int, long)):
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)
495
for order in self.browse(cr, uid, ids, context=context):
496
if order.yml_module_name == 'sale':
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'
502
vals['order_policy'] = 'picking'
504
self._check_service(cr, uid, ids, vals, context=context)
506
res = super(sale_order, self).write(cr, uid, ids, vals, context=context)
510
def ask_resource_lines(self, cr, uid, ids, context=None):
512
Launch the wizard to re-source lines
515
wiz_obj = self.pool.get('sale.order.cancelation.wizard')
518
wf_service = netsvc.LocalService("workflow")
523
if isinstance(ids, (int, long)):
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',
537
wf_service.trg_validate(uid, 'sale.order', order.id, 'cancel', cr)
541
def change_currency(self, cr, uid, ids, context=None):
543
Launches the wizard to change the currency and update lines
548
if isinstance(ids, (int, long)):
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',
568
def wkf_validated(self, cr, uid, ids, context=None):
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
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
582
:return True if all order have been written
586
line_obj = self.pool.get('sale.order.line')
587
pricelist_obj = self.pool.get('product.pricelist')
592
if isinstance(ids, (int, long)):
595
order_brw_list = self.browse(cr, uid, ids, context=context)
597
# 1/ Check validity of analytic distribution
598
self.analytic_distribution_checks(cr, uid, order_brw_list)
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 !'))
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'),
611
line_obj.write(cr, uid, non_mts_line, {'type': 'make_to_stock'}, context=context)
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(
618
_('The currency used on the order is not compatible with the supplier. '\
619
'Please change the currency to choose a compatible currency.'),
622
self.write(cr, uid, ids, {
623
'state': 'validated',
624
'validated_date': time.strftime('%Y-%m-%d'),
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)
632
self.log(cr, uid, order.id, 'The Internal Request \'%s\' has been validated.' % order.name, context=context)
636
def wkf_split(self, cr, uid, ids, context=None):
638
split function for sale order: original -> stock, esc, local purchase
643
if isinstance(ids, (int, long)):
646
line_obj = self.pool.get('sale.order.line')
647
fields_tools = self.pool.get('fields.tools')
648
wf_service = netsvc.LocalService("workflow")
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.'))
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.'))
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)
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'
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)
690
# the fo already exists
691
split_fo_dic[fo_type] = so_ids[0]
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,
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)
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)
725
def get_original_name(self, cr, uid, order, context=None):
727
Returns the name of the first original FO
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
736
def create_resource_order(self, cr, uid, order, context=None):
738
Create a new FO to re-source the needs.
740
context = context or {}
742
# Get the name of the original FO
743
old_order_name = order.name
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']
750
order_id = self.copy(cr, uid, order.id, {'order_line': [],
752
'parent_order_name': old_order_name,
753
'fo_to_resource': True}, context=context)
756
order_name = self.read(cr, uid, order_id, ['name'], context=context)['name']
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))
762
def sale_except_correction(self, cr, uid, ids, context=None):
764
Remove the link between a Field order and the canceled procurement orders
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'})
780
self.write(cr, uid, [order.id], {'state': 'progress'})
784
def wkf_split_done(self, cr, uid, ids, context=None):
786
split done function for sale order
791
if isinstance(ids, (int, long)):
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
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)
805
def get_po_ids_from_so_ids(self, cr, uid, ids, context=None):
807
receive the list of sale order ids
809
return the list of purchase order ids corresponding (through procurement process)
814
if isinstance(ids, (int, long)):
817
# procurement ids list
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)
827
# return the purchase order ids
830
def _hook_message_action_wait(self, cr, uid, *args, **kwargs):
832
Hook the message displayed on sale order confirmation
834
return _('The Field order \'%s\' has been confirmed.') % (kwargs['order'].name,)
836
def action_purchase_order_create(self, cr, uid, ids, context=None):
838
Create a purchase order as counterpart for the loan.
840
if isinstance(ids, (int, long)):
845
purchase_obj = self.pool.get('purchase.order')
846
purchase_line_obj = self.pool.get('purchase.order.line')
847
partner_obj = self.pool.get('res.partner')
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):
854
two_months = today() + RelativeDateTime(months=+2)
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,
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,
870
context['is_a_counterpart'] = True
871
order_id = purchase_obj.create(cr, uid, values, context=context)
872
for line in order.order_line:
873
purchase_line_obj.create(cr, uid, {'product_id': line.product_id and line.product_id.id or False,
874
'product_uom': line.product_uom.id,
875
'order_id': order_id,
876
'price_unit': line.price_unit,
877
'product_qty': line.product_uom_qty,
878
'date_planned': (today() + RelativeDateTime(months=+order.loan_duration)).strftime('%Y-%m-%d'),
879
'name': line.name, }, context)
880
self.write(cr, uid, [order.id], {'loan_id': order_id})
882
purchase = purchase_obj.browse(cr, uid, order_id)
884
message = _("Loan counterpart '%s' has been created.") % (purchase.name,)
886
purchase_obj.log(cr, uid, order_id, message)
890
def has_stockable_products(self, cr, uid, ids, *args):
892
Override the has_stockable_product to return False
893
when the internal_type of the order is 'direct'
895
for order in self.browse(cr, uid, ids):
896
if order.order_type != 'direct':
897
return super(sale_order, self).has_stockable_product(cr, uid, ids, args)
901
#@@@override sale.sale_order.action_invoice_end
902
def action_invoice_end(self, cr, uid, ids, context=None):
904
Modified to set lines invoiced when order_type is not regular
906
for order in self.browse(cr, uid, ids, context=context):
908
# Update the sale order lines state (and invoiced flag).
910
for line in order.order_line:
913
# Check if the line is invoiced (has asociated invoice
914
# lines from non-cancelled invoices).
916
invoiced = order.noinvoice
918
for iline in line.invoice_lines:
919
if iline.invoice_id and iline.invoice_id.state != 'cancel':
922
if line.invoiced != invoiced:
923
vals['invoiced'] = invoiced
924
# If the line was in exception state, now it gets confirmed.
925
if line.state == 'exception':
926
vals['state'] = 'confirmed'
927
# Update the line (only when needed).
929
self.pool.get('sale.order.line').write(cr, uid, [line.id], vals, context=context)
931
# Update the sales order state.
933
if order.state == 'invoice_except':
934
self.write(cr, uid, [order.id], {'state': 'progress'}, context=context)
938
def _get_reason_type(self, cr, uid, order, context=None):
940
'regular': 'reason_type_deliver_partner',
941
'loan': 'reason_type_loan',
942
'donation_st': 'reason_type_donation',
943
'donation_exp': 'reason_type_donation_expiry',
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]
951
def order_line_change(self, cr, uid, ids, order_line):
952
res = {'no_line': True}
955
res = {'no_line': False}
957
return {'value': res}
959
def _hook_ship_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
961
Please copy this to your module's method also.
962
This hook belongs to the action_ship_create method from sale>sale.py
964
- allow to modify the data for stock picking creation
966
result = super(sale_order, self)._hook_ship_create_stock_picking(cr, uid, ids, context=context, *args, **kwargs)
967
result['reason_type_id'] = self._get_reason_type(cr, uid, kwargs['order'], context)
971
def _get_date_planned(self, order, line):
973
Return the planned date for the FO/IR line according
974
to the order and line values.
976
:param order: browse_record of a sale.order
977
:param line: browse_record of a sale.order.line
979
:return The planned date
982
# Check type of parameter
983
self._check_browse_param(order, '_get_date_planned')
984
self._check_browse_param(line, '_get_date_planned')
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')
991
def _get_new_picking(self, line):
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.
997
:param line: The browse_record of the sale.order.line to check
999
:return True if the line needs a new picking or False
1002
# Check type of parameter
1003
self._check_browse_param(line, '_get_new_picking')
1005
res = line.product_id and line.product_id.type in ['product', 'consu', 'service']
1007
if line.order_id.manually_corrected:
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':
1014
elif line.order_id.location_requestor_id.usage == 'customer':
1019
def _get_picking_data(self, cr, uid, order, context=None):
1021
Define the values for the picking ticket associated to the
1022
FO/IR according to order values.
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
1028
:return A dictionary with the values of the picking to be create
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')
1039
self._check_browse_param(order, '_get_picking_data')
1041
setup = config_obj.get_config(cr, uid)
1044
'origin': order.name,
1047
'move_type': order.picking_policy,
1048
'sale_id': order.id,
1049
'address_id': order.partner_shipping_id.id,
1051
'invoice_state': (order.order_policy == 'picking' and '2binvoiced') or 'none',
1052
'company_id': order.company_id.id,
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({
1061
'subtype': 'standard',
1062
'reason_type_id': data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1],
1064
pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
1066
picking_data.update({
1068
'subtype': 'standard',
1069
'reason_type_id': data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_move')[1],
1071
pick_name = seq_obj.get(cr, uid, 'stock.picking.internal')
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')
1078
picking_data['subtype'] = 'picking'
1079
# use the name according to picking ticket sequence
1080
pick_name = seq_obj.get(cr, uid, 'picking.ticket')
1082
picking_data.update({
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),
1092
def _get_move_data(self, cr, uid, order, line, picking_id, context=None):
1094
Define the values for the stock move associated to the
1095
FO/IR line according to line and order values.
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
1102
:return A dictionary with the values of the move to be create
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')
1115
self._check_browse_param(order, '_get_move_data')
1116
self._check_browse_param(line, '_get_move_data')
1118
location_id = order.shop_id.warehouse_id.lot_stock_id.id
1119
output_id = order.shop_id.warehouse_id.lot_output_id.id
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,
1139
# 'state': 'waiting',
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,
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]
1151
if order.procurement_request and order.location_requestor_id:
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,
1158
if order.location_requestor_id.usage in ('supplier', 'customer'):
1159
move_data['type'] = 'out'
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
1168
move_data['location_dest_id'] = order.shop_id.warehouse_id.lot_packing_id.id
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)
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':
1178
'location_id': loc_obj.get_cross_docking_location(cr, uid),
1179
'move_cross_docking_ok': True,
1181
# Update the stock.picking
1182
pick_obj.write(cr, uid, move_data['picking_id'], {'cross_docking_ok': True}, context=context)
1184
move_data['state'] = 'confirmed'
1188
# @@@override sale>sale.py>sale_order>action_ship_create()
1189
def action_ship_create(self, cr, uid, ids, context=None):
1191
Create the picking ticket with the stock moves and the
1192
procurement orders according to FO/IR values.
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
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')
1215
if isinstance(ids, (int, long)):
1218
setup = config_obj.get_config(cr, uid)
1220
for order in self.browse(cr, uid, ids, context=context):
1225
for line in order.order_line:
1228
# Don't take care of closed lines
1229
if line.state == 'done':
1234
# In case of IR to internal location, the creation of
1235
# a picking is not need.
1236
if self._get_new_picking(line):
1238
picking_data = self._get_picking_data(cr, uid, order)
1239
picking_id = picking_obj.create(cr, uid, picking_data, context=context)
1241
# Get move data and create the move
1242
move_data = self._get_move_data(cr, uid, order, line, picking_id, context=context)
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
1250
if order.procurement_request:
1251
move_obj.action_confirm(cr, uid, [move_id], context=context)
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
1260
if order.state != 'shipping_except' and not order.procurement_request and line.procurement_id:
1261
cancel_move_id = False
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
1267
if line.procurement_id.move_id:
1268
cancel_move_id = line.procurement_id.move_id.id
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)
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)
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)
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]
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
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]
1296
rts_date = self._get_date_planned(order, line)
1297
proc_data = self._get_procurement_order_data(line, order, rts_date, context)
1299
# Just change some values because in case of IR, we need specific values
1301
'product_id': product_id,
1302
'product_uom': product_uom,
1303
'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
1305
'property_ids': [(6, 0, [x.id for x in line.property_ids])],
1308
proc_id = proc_obj.create(cr, uid, proc_data)
1309
proc_ids.append(proc_id)
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)
1320
for mov in move_obj.read(cr, uid, mov_ids, ['product_qty', 'product_uos_qty'], context=context):
1322
'product_qty': mov['product_qty'],
1323
'product_uos_qty': mov['product_uos_qty'],
1325
move_obj.write(cr, uid, [move_id], values, context=context)
1326
proc_obj.write(cr, uid, [proc_id], values, context=context)
1328
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
1330
# compute overall_qty
1332
compute_store = move_obj._store_get_values(cr, uid, move_ids, None, context)
1333
compute_store.sort()
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))
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)
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)
1353
wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
1356
# Launch a first check availability
1357
picking_obj.action_assign(cr, uid, [picking_id], context=context)
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
1365
picks_to_check.add(pick_id)
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)
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']),
1383
'state': order.order_policy and manual_lines and 'manual' or 'progress',
1387
self.write(cr, uid, [order.id], val)
1390
# @@@END override sale>sale.py>sale_order>action_ship_create()
1392
def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
1394
Set the sale order and all related documents to done state
1396
wf_service = netsvc.LocalService("workflow")
1398
if isinstance(ids, (int, long)):
1404
procurement_ids = []
1406
for order in self.browse(cr, uid, ids, context=context):
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)
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)
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)
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)
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)
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)
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)
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)
1459
def _get_procurement_order_data(self, line, order, rts_date, context=None):
1461
Get data for the procurement order creation according to
1462
sale.order.line and sale.order values.
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
1472
# Check type of parameters
1473
for param in [line, order]:
1474
self._check_browse_param(param, '_create_procurement_order')
1476
if line.type == 'make_to_order':
1477
location_id = order.shop_id.warehouse_id.lot_input_id.id
1479
location_id = order.shop_id.warehouse_id.lot_stock_id.id
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,
1505
proc_data['product_id'] = line.product_id.id
1509
def action_ship_proc_create(self, cr, uid, ids, context=None):
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
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
1524
:return True if all order have been written
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')
1538
if isinstance(ids, (int, long)):
1541
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1542
order_brw_list = self.browse(cr, uid, ids)
1546
# 1/ Check of the analytic distribution
1547
self.analytic_distribution_checks(cr, uid, order_brw_list)
1549
for order in order_brw_list:
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 !'))
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
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)
1564
# If the order is stock So, we update the confirmed delivery date
1565
if order.split_type_sale_order == 'stock_split_sale_order':
1567
ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
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)
1572
o_rts = (datetime.today() + relativedelta(days=ship_lt or 0)).strftime(db_date_format)
1574
o_write_vals.update({
1575
'delivery_confirmed_date': delivery_confirmed_date,
1576
'ready_to_ship_date': o_rts,
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')
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),
1590
sol_obj.write(cr, uid, line_to_write, {
1591
'confirmed_delivery_date': o_write_vals.get('delivery_confirmed_date', order.delivery_confirmed_date),
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)
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
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:
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)
1619
'procurement_id': proc_id,
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'
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)
1630
sol_obj.write(cr, uid, [line.id], line_values, context=context)
1632
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
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)
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
1643
self.log(cr, uid, order.id, _('The split \'%s\' is sourced.') % (order.name))
1646
sol_obj.write(cr, uid, lines, {'invoiced': 1}, context=context)
1650
def test_lines(self, cr, uid, ids, context=None):
1652
return True if all lines of type 'make_to_order' are 'confirmed'
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)
1657
line_obj = self.pool.get('sale.order.line')
1662
if isinstance(ids, (int, long)):
1665
# Update the context to get IR lines
1666
context['procurement_request'] = True
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']:
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'),
1679
('procurement_id', '=', 'False'),
1680
('procurement_id.state', '!=', 'cancel'),
1681
], count=True, context=context)
1691
class sale_order_line(osv.osv):
1692
_name = 'sale.order.line'
1693
_inherit = 'sale.order.line'
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.'),
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'),
1719
'is_line_split': False, # UTP-972: By default set False, not split
1722
def ask_unlink(self, cr, uid, ids, context=None):
1724
Call the user to know if the line must be re-sourced
1729
if isinstance(ids, (int, long)):
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)
1736
return self.ask_order_unlink(cr, uid, ids, context=context)
1738
def ask_order_unlink(self, cr, uid, ids, context=None):
1740
Call the unlink method for lines and if the FO becomes empty,
1741
ask the user if he wants to cancel the FO
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])
1749
self.unlink(cr, uid, ids, context=context)
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)
1757
def _check_restriction_line(self, cr, uid, ids, context=None):
1759
Check if there is restriction on lines
1761
if isinstance(ids, (int, long)):
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):
1774
def update_or_cancel_line(self, cr, uid, line, qty_diff, context=None):
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.
1779
If the qty_diff is equal or larger than the line quantity, delete the
1780
line and its procurement.
1783
proc_obj = self.pool.get('procurement.order')
1785
wf_service = netsvc.LocalService("workflow")
1790
if isinstance(line, (int, long)):
1791
line = self.browse(cr, uid, line, context=context)
1793
order = line.order_id and line.order_id.id
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)
1802
proc_obj.write(cr, uid, [proc], {'product_qty': 0.00}, context=context)
1803
proc_obj.action_cancel(cr, uid, [proc])
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)
1811
proc_obj.write(cr, uid, [proc], {'product_qty': minus_qty}, context=context)
1814
wf_service.trg_write(uid, 'sale.order', order, cr)
1818
def add_resource_line(self, cr, uid, line, order_id, qty_diff, context=None):
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
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')
1833
if isinstance(line, (int, long)):
1834
line = self.browse(cr, uid, line, context=context)
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)
1842
qty_diff = line.product_uom_qty
1845
'order_id': order_id,
1846
'product_uom_qty': qty_diff,
1847
'product_uos_qty': qty_diff,
1848
'procurement_id': False
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
1855
line_id = self.copy(cr, uid, line.id, values, context=context)
1857
order_name = self.pool.get('sale.order').read(cr, uid, [order_id], ['name'], context=context)[0]['name']
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]
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)
1868
def open_split_wizard(self, cr, uid, ids, context=None):
1870
Open the wizard to split the line
1875
if isinstance(ids, (int, long)):
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',
1889
def copy_data(self, cr, uid, id, default=None, context=None):
1891
reset link to purchase order from update of on order purchase order
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})
1901
return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
1903
def open_order_line_to_correct(self, cr, uid, ids, context=None):
1905
Open Order Line in form view
1909
if isinstance(ids, (int, long)):
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]
1914
'view_type': 'form',
1915
'view_mode': 'form',
1916
'res_model': 'sale.order.line',
1917
'type': 'ir.actions.act_window',
1921
'view_id': [view_id],
1923
return view_to_return
1925
def save_and_close(self, cr, uid, ids, context=None):
1927
Save and close the configuration window
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)
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
1946
raise osv.except_osv(_('Warning !'), _(message))
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',
1955
'view_id': [view_id],
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):
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.
1966
product_obj = self.pool.get('product.product')
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)
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'})
1982
type = product_obj.read(cr, uid, [product], 'procure_method')[0]['procure_method']
1984
res['value'].update({'type': type})
1986
res.update({'value':{'type': type}})
1987
res['value'].update({'product_uom_qty': qty, 'product_uos_qty': qty})
1990
res['value'].update({'type': 'make_to_order'})
1992
res.update({'value':{'type': 'make_to_order'}})
1993
res['value'].update({'product_uom_qty': 0.00, 'product_uos_qty': 0.00})
1997
def default_get(self, cr, uid, fields, context=None):
1999
Default procurement method is 'on order' if no product selected
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
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
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
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)
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', [])
2030
default_data.update({'type': 'make_to_order'})
2033
def copy(self, cr, uid, id, default=None, context=None):
2035
copy from sale order line
2043
default.update({'sync_order_line_db_id': False, 'manually_corrected': False})
2045
return super(sale_order_line, self).copy(cr, uid, id, default, context)
2047
def check_empty_line(self, cr, uid, ids, vals, context=None):
2049
Return an error if the line has no qty
2051
context = context is None and {} or context
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, [
2057
('order_id.state', 'not in', ['draft', 'cancel']),
2058
('product_uom_qty', '<=', 0.00),
2059
], count=True, context=context)
2061
raise osv.except_osv(_('Error'), _('A line must a have a quantity larger than 0.00'))
2065
def create(self, cr, uid, vals, context=None):
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.
2072
if not vals.get('product_id') and context.get('sale_id', []):
2073
vals.update({'type': 'make_to_order'})
2075
self.check_empty_line(cr, uid, False, vals, context=context)
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):
2084
vals.update({'product_uos_qty' : qty * product_obj.read(cr, uid, product_id, ['uos_coeff'])['uos_coeff']})
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)})
2092
Add the database ID of the SO line to the value sync_order_line_db_id
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)
2103
def write(self, cr, uid, ids, vals, context=None):
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.
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):
2115
product_id = self.browse(cr, uid, ids, context=context)[0].product_id
2118
vals.update({'type': 'make_to_order'})
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)})
2124
self.check_empty_line(cr, uid, ids, vals, context=context)
2126
res = super(sale_order_line, self).write(cr, uid, ids, vals, context=context)
2133
class procurement_order(osv.osv):
2134
_inherit = 'procurement.order'
2137
'sale_id': fields.many2one('sale.order', string='Sale'),
2143
class sale_config_picking_policy(osv.osv_memory):
2145
Set order_policy to picking
2147
_name = 'sale.config.picking_policy'
2148
_inherit = 'sale.config.picking_policy'
2151
'order_policy': 'picking',
2154
sale_config_picking_policy()
2156
class sale_order_line_unlink_wizard(osv.osv_memory):
2157
_name = 'sale.order.line.unlink.wizard'
2160
'order_line_id': fields.many2one('sale.order.line', 'Line to delete'),
2163
def ask_unlink(self, cr, uid, order_line_id, context=None):
2167
context = context or {}
2169
if isinstance(order_line_id, (int, long)):
2170
order_line_id = [order_line_id]
2172
wiz_id = self.create(cr, uid, {'order_line_id': order_line_id[0]}, context=context)
2174
return {'type': 'ir.actions.act_window',
2175
'res_model': self._name,
2177
'view_type': 'form',
2178
'view_mode': 'form',
2182
def close_window(self, cr, uid, ids, context=None):
2184
Close the pop-up and reload the FO
2186
return {'type': 'ir.actions.act_window_close'}
2188
def cancel_fo_line(self, cr, uid, ids, context=None):
2190
Cancel the FO line and display the FO form
2192
context = context or {}
2194
if isinstance(ids, (int, long)):
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)
2203
return res or {'type': 'ir.actions.act_window_close'}
2205
def resource_line(self, cr, uid, ids, context=None):
2207
Resource the FO line and display the FO form
2209
context = context or {}
2211
if isinstance(ids, (int, long)):
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)
2217
return self.cancel_fo_line(cr, uid, ids, context=context)
2219
sale_order_line_unlink_wizard()
2221
class sale_order_unlink_wizard(osv.osv_memory):
2222
_name = 'sale.order.unlink.wizard'
2225
'order_id': fields.many2one('sale.order', 'Order to delete'),
2228
def ask_unlink(self, cr, uid, order_id, context=None):
2232
context = context or {}
2234
wiz_id = self.create(cr, uid, {'order_id': order_id}, context=context)
2235
context['view_id'] = False
2237
return {'type': 'ir.actions.act_window',
2238
'res_model': self._name,
2240
'view_type': 'form',
2241
'view_mode': 'form',
2245
def close_window(self, cr, uid, ids, context=None):
2247
Close the pop-up and reload the FO
2249
return {'type': 'ir.actions.act_window_close'}
2251
def cancel_fo(self, cr, uid, ids, context=None):
2253
Cancel the FO and display the FO form
2255
context = context or {}
2257
if isinstance(ids, (int, long)):
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)
2263
return {'type': 'ir.actions.act_window_close'}
2265
sale_order_unlink_wizard()
2268
class sale_order_cancelation_wizard(osv.osv_memory):
2269
_name = 'sale.order.cancelation.wizard'
2272
'order_id': fields.many2one('sale.order', 'Order to delete', required=True),
2275
def only_cancel(self, cr, uid, ids, context=None):
2277
Cancel the FO w/o re-sourcing lines
2280
sale_obj = self.pool.get('sale.order')
2282
# Variables initialization
2286
if isinstance(ids, (int, long)):
2289
for wiz in self.browse(cr, uid, ids, context=context):
2290
sale_obj.action_cancel(cr, uid, [wiz.order_id.id], context=context)
2292
return {'type': 'ir.actions.act_window_close'}
2294
def resource_lines(self, cr, uid, ids, context=None):
2296
Cancel the FO and re-source all lines
2299
sale_obj = self.pool.get('sale.order')
2300
line_obj = self.pool.get('sale.order.line')
2302
# Variables initialization
2306
if isinstance(ids, (int, long)):
2309
wf_service = netsvc.LocalService("workflow")
2311
for wiz in self.browse(cr, uid, ids, context=context):
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)
2317
wf_service.trg_validate(uid, 'sale.order', wiz.order_id.id, 'cancel', cr)
2319
return {'type': 'ir.actions.act_window_close'}
2321
sale_order_cancelation_wizard()
2323
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: