23
23
from osv import osv
24
24
from osv import fields
25
from msf_partner import PARTNER_TYPE
27
from tools.translate import _
28
from lxml import etree
31
27
class res_partner(osv.osv):
32
28
_name = 'res.partner'
33
29
_inherit = 'res.partner'
35
def search_in_product(self, cr, uid, obj, name, args, context=None):
37
Search function of related field 'in_product'
43
if not context.get('product_id', False) or 'choose_supplier' not in context:
46
supinfo_obj = self.pool.get('product.supplierinfo')
47
sup_obj = self.pool.get('res.partner')
50
info_ids = supinfo_obj.search(cr, uid, [('product_product_ids', '=', context.get('product_id'))])
51
info = supinfo_obj.read(cr, uid, info_ids, ['name'])
53
sup_in = [x['name'] for x in info]
60
res = sup_obj.search(cr, uid, [('id', 'not in', sup_in)])
63
return [('id', '=', 0)]
64
return [('id', 'in', [x[0] for x in res])]
67
def _set_in_product(self, cr, uid, ids, field_name, arg, context=None):
69
Returns according to the context if the partner is in product form
75
product_obj = self.pool.get('product.product')
77
# If we aren't in the context of choose supplier on procurement list
78
if not context.get('product_id', False) or 'choose_supplier' not in context:
80
res[i] = {'in_product': False, 'min_qty': 'N/A', 'delay': 'N/A'}
82
product = product_obj.browse(cr, uid, context.get('product_id'))
85
# Get all suppliers defined on product form
86
for s in product.seller_ids:
87
seller_ids.append(s.name.id)
88
seller_info.update({s.name.id: {'min_qty': s.min_qty, 'delay': s.delay}})
89
# Check if the partner is in product form
92
res[i] = {'in_product': True, 'min_qty': '%s' %seller_info[i]['min_qty'], 'delay': '%s' %seller_info[i]['delay']}
94
res[i] = {'in_product': False, 'min_qty': 'N/A', 'delay': 'N/A'}
98
def _get_price_info(self, cr, uid, ids, fiedl_name, args, context=None):
100
Returns information from product supplierinfo if product_id is in context
105
partner_price = self.pool.get('pricelist.partnerinfo')
109
res[id] = {'price_currency': False,
111
'valide_until_date': False}
113
if context.get('product_id'):
114
for partner in self.browse(cr, uid, ids, context=context):
115
product = self.pool.get('product.product').browse(cr, uid, context.get('product_id'), context=context)
116
uom = context.get('uom', product.uom_id.id)
117
pricelist = partner.property_product_pricelist_purchase
118
context.update({'uom': uom})
119
price_list = self.pool.get('product.product')._get_partner_info_price(cr, uid, product, partner.id, context.get('product_qty', 1.00), pricelist.currency_id.id, time.strftime('%Y-%m-%d'), uom, context=context)
121
func_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
122
price = self.pool.get('res.currency').compute(cr, uid, func_currency_id, pricelist.currency_id.id, product.standard_price, round=False, context=context)
123
res[partner.id] = {'price_currency': pricelist.currency_id.id,
125
'valide_until_date': False}
127
info_price = partner_price.browse(cr, uid, price_list[0], context=context)
128
partner_currency_id = pricelist.currency_id.id
129
price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, partner_currency_id, info_price.price, round=False, context=context)
130
currency = partner_currency_id
131
# Uncomment the following 2 lines if you want the price in currency of the pricelist.partnerinfo instead of partner default currency
132
# currency = info_price.currency_id.id
133
# price = info_price.price
134
res[partner.id] = {'price_currency': currency,
136
'valide_until_date': info_price.valid_till}
140
## QT : Remove _get_price_unit
142
## QT : Remove _get_valide_until_date
145
32
'manufacturer': fields.boolean(string='Manufacturer', help='Check this box if the partner is a manufacturer'),
146
'partner_type': fields.selection(PARTNER_TYPE, string='Partner type', required=True),
147
'in_product': fields.function(_set_in_product, fnct_search=search_in_product, string='In product', type="boolean", readonly=True, method=True, multi='in_product'),
148
'min_qty': fields.function(_set_in_product, string='Min. Qty', type='char', readonly=True, method=True, multi='in_product'),
149
'delay': fields.function(_set_in_product, string='Delivery Lead time', type='char', readonly=True, method=True, multi='in_product'),
150
'property_product_pricelist_purchase': fields.property(
153
relation='product.pricelist',
154
domain=[('type','=','purchase')],
155
string="Purchase default currency",
159
help="This currency will be used, instead of the default one, for purchases from the current partner"),
160
'property_product_pricelist': fields.property(
163
relation='product.pricelist',
164
domain=[('type','=','sale')],
165
string="Field orders default currency",
169
help="This currency will be used, instead of the default one, for field orders to the current partner"),
170
'price_unit': fields.function(_get_price_info, method=True, type='float', string='Unit price', multi='info'),
171
'valide_until_date' : fields.function(_get_price_info, method=True, type='char', string='Valid until date', multi='info'),
172
'price_currency': fields.function(_get_price_info, method=True, type='many2one', relation='res.currency', string='Currency', multi='info'),
33
'partner_type': fields.selection([('internal', 'Internal'), ('section', 'Inter-section'),
34
('external', 'External')], string='Partner type', required=True),
176
38
'manufacturer': lambda *a: False,
177
'partner_type': lambda *a: 'external',
39
'partner_type': lambda *a: 'internal',
181
def _check_main_partner(self, cr, uid, ids, vals, context=None):
184
if isinstance(ids, (int, long)):
186
bro_uid = self.pool.get('res.users').browse(cr,uid,uid)
188
bro = bro_uid.company_id
189
res = bro and bro.partner_id and bro.partner_id.id
190
cur = bro and bro.currency_id and bro.currency_id.id
192
po_def_cur = self.pool.get('product.pricelist').browse(cr,uid,vals.get('property_product_pricelist_purchase'))
193
fo_def_cur = self.pool.get('product.pricelist').browse(cr,uid,vals.get('property_product_pricelist'))
196
for obj in self.browse(cr, uid, [res], context=context):
198
if context.get('from_setup') and bro.second_time and po_def_cur and po_def_cur.currency_id and po_def_cur.currency_id.id != cur:
199
raise osv.except_osv(_('Warning !'), _('You can not change the Purchase Default Currency of this partner anymore'))
201
if not context.get('from_setup') and po_def_cur and po_def_cur.currency_id and po_def_cur.currency_id.id != cur:
202
raise osv.except_osv(_('Warning !'), _('You can not change the Purchase Default Currency of this partner'))
204
if context.get('from_setup') and bro.second_time and fo_def_cur and fo_def_cur.currency_id and fo_def_cur.currency_id.id != cur:
205
raise osv.except_osv(_('Warning !'), _('You can not change the Field Orders Default Currency of this partner anymore'))
207
if not context.get('from_setup') and fo_def_cur and fo_def_cur.currency_id and fo_def_cur.currency_id.id != cur:
208
raise osv.except_osv(_('Warning !'), _('You can not change the Field Orders Default Currency of this partner'))
211
raise osv.except_osv(_('Warning !'), _('This partner can not be checked as customer'))
214
raise osv.except_osv(_('Warning !'), _('This partner can not be checked as supplier'))
221
def get_objects_for_partner(self, cr, uid, ids, context):
223
According to partner's ids:
224
return the most important objects linked to him that are not closed or opened
227
if isinstance(ids, (int, long)):
232
purchase_obj = self.pool.get('purchase.order')
233
sale_obj = self.pool.get('sale.order')
234
account_invoice_obj = self.pool.get('account.invoice') # for Supplier invoice/ Debit Note
235
pick_obj = self.pool.get('stock.picking') # for PICK/ PACK/ PPL/ INCOMING SHIPMENT/ DELIVERY
236
tender_obj = self.pool.get('tender')
237
com_vouch_obj = self.pool.get('account.commitment')# for commitment voucher
238
ship_obj = self.pool.get('shipment')
239
absl_obj = self.pool.get('account.bank.statement.line') # for register lines
240
aml_obj = self.pool.get('account.move.line')
242
# ids list (the domain are the same as the one used for the action window of the menus)
243
purchase_ids = purchase_obj.search(cr, uid,
244
[('rfq_ok', '=', False), ('partner_id', '=', ids[0]), ('state', 'not in', ['done', 'cancel'])],
245
context=context.update({'purchase_order': True}))
246
rfq_ids = purchase_obj.search(cr, uid,
247
[('rfq_ok', '=', True), ('partner_id', '=', ids[0]), ('state', 'not in', ['done', 'cancel'])],
248
context=context.update({'request_for_quotation': True}))
249
sale_ids = sale_obj.search(cr, uid,
250
[('procurement_request', '=', False), ('partner_id', '=', ids[0]), ('state', 'not in', ['done', 'cancel'])],
252
intermission_vouch_in_ids = account_invoice_obj.search(cr, uid, [
253
('type','=','in_invoice'), ('is_debit_note', '=', False), ('is_inkind_donation', '=', False),
254
('is_intermission', '=', True), ('partner_id', '=', ids[0]), ('state', 'in', ['draft'])
255
], context = context.update({'type':'in_invoice', 'journal_type': 'intermission'}))
257
intermission_vouch_out_ids = account_invoice_obj.search(cr, uid, [
258
('type','=','out_invoice'), ('is_debit_note', '=', False), ('is_inkind_donation', '=', False),
259
('is_intermission', '=', True), ('partner_id', '=', ids[0]), ('state', 'in', ['draft'])
260
], context = context.update({'type':'out_invoice', 'journal_type': 'intermission'}))
262
donation_ids = account_invoice_obj.search(cr, uid, [
263
('type','=','in_invoice'), ('is_debit_note', '=', False), ('is_inkind_donation', '=', True),
264
('partner_id', '=', ids[0]), ('state', 'in', ['draft'])
265
], context = context.update({'type':'in_invoice', 'journal_type': 'inkind'}))
266
supp_invoice_ids = account_invoice_obj.search(cr, uid, [
267
('type','=','in_invoice'), ('register_line_ids', '=', False), ('is_inkind_donation', '=', False),
268
('is_intermission', '=', False), ('is_debit_note', "=", False), ('partner_id', '=', ids[0]), ('state', 'in', ['draft'])
269
], context = context.update({'type':'in_invoice', 'journal_type': 'purchase'}))
271
cust_refunds_ids = account_invoice_obj.search(cr, uid,
272
[('type','=','out_refund'), ('partner_id', '=', ids[0]), ('state', 'in', ['draft'])],
273
context = context.update({'type':'out_refund', 'journal_type': 'sale_refund'}))
275
debit_note_ids = account_invoice_obj.search(cr, uid, [
276
('type','=','out_invoice'), ('is_debit_note', '!=', False), ('is_inkind_donation', '=', False),
277
('partner_id', '=', ids[0]), ('state', 'in', ['draft'])
278
], context = context.update({'type':'out_invoice', 'journal_type': 'sale', 'is_debit_note': True}))
280
stock_transfer_vouch_ids = account_invoice_obj.search(cr, uid, [
281
('type','=','out_invoice'), ('is_debit_note', '=', False), ('is_inkind_donation', '=', False),
282
('is_intermission', '=', False), ('partner_id', '=', ids[0]), ('state', 'in', ['draft'])
283
], context = context.update({'type':'out_invoice', 'journal_type': 'sale'}))
284
incoming_ship_ids = pick_obj.search(cr, uid, [
285
('state', 'not in', ['done', 'cancel']), ('type', '=', 'in'), ('subtype', '=', 'standard'),
286
'|', ('partner_id', '=', ids[0]), ('partner_id2', '=', ids[0])
287
], context = context.update({
288
'contact_display': 'partner_address', 'subtype': 'in', 'picking_type': 'incoming_shipment', 'search_default_available':1
290
out_ids = pick_obj.search(cr, uid, [
291
('state', 'not in', ['done', 'cancel']), ('type', '=', 'out'), ('subtype', '=', 'standard'),
292
'|', ('partner_id', '=', ids[0]), ('partner_id2', '=', ids[0])
293
], context = context.update({
294
'contact_display': 'partner_address', 'search_default_available': 1,'picking_type': 'delivery_order', 'subtype': 'standard'
296
pick_ids = pick_obj.search(cr, uid, [
297
('state', 'not in', ['done', 'cancel']), ('type', '=', 'out'), ('subtype', '=', 'picking'),
298
'|', ('partner_id', '=', ids[0]), ('partner_id2', '=', ids[0])
299
], context = context.update({
300
'picking_screen':True, 'picking_type': 'picking_ticket', 'test':True, 'search_default_not_empty':1
302
ppl_ids = pick_obj.search(cr, uid, [
303
('state', 'not in', ['done', 'cancel']), ('type', '=', 'out'), ('subtype', '=', 'ppl'),
304
'|', ('partner_id', '=', ids[0]), ('partner_id2', '=', ids[0])
305
], context=context.update({
306
'contact_display': 'partner_address', 'ppl_screen':True, 'picking_type': 'picking_ticket', 'search_default_available':1
308
tender_ids = [tend for tend in tender_obj.search(cr, uid, [('state', '=', 'comparison')]) if ids[0] in tender_obj.read(cr, uid, tend, ['supplier_ids'])['supplier_ids']]
309
com_vouch_ids = com_vouch_obj.search(cr, uid, [('partner_id', '=', ids[0]), ('state', '!=', 'done')], context=context)
310
ship_ids = ship_obj.search(cr, uid,
311
[('state', 'not in', ['done', 'delivered']), '|', ('partner_id', '=', ids[0]), ('partner_id2', '=', ids[0])],
313
absl_ids = absl_obj.search(cr, uid, [('state', 'in', ['draft', 'temp']), ('partner_id', '=', ids[0])], context=context)
314
aml_ids = aml_obj.search(cr, uid, [('partner_id', '=', ids[0]), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
317
po['name']+_(' (Purchase)') for po in purchase_obj.read(cr, uid, purchase_ids, ['name'], context) if po['name']]
318
+[rfq['name']+_(' (RfQ)') for rfq in purchase_obj.read(cr, uid, rfq_ids, ['name'], context) if rfq['name']]
319
+[so['name']+_(' (Field Order)') for so in sale_obj.read(cr, uid, sale_ids, ['name'], context) if so['name']]
320
+(intermission_vouch_in_ids and [_('%s Intermission Voucher IN') % (len(intermission_vouch_in_ids),)] or [])
321
+(intermission_vouch_out_ids and [_('%s Intermission Voucher OUT') % (len(intermission_vouch_out_ids),)] or [])
322
+(donation_ids and [_('%s Donation(s)') % (len(donation_ids),)] or [])
323
+(supp_invoice_ids and [_('%s Supplier Invoice(s)') % (len(supp_invoice_ids), )] or [])
324
+(cust_refunds_ids and [_('%s Customer Refund(s)') % (len(cust_refunds_ids), )] or [])
325
+(debit_note_ids and [_('%s Debit Note(s)') % (len(debit_note_ids), )] or [])
326
+(stock_transfer_vouch_ids and [_('%s Stock Transfer Voucher(s)') % (len(stock_transfer_vouch_ids),)] or [])
327
+[inc_ship['name']+_(' (Incoming Shipment)') for inc_ship in pick_obj.read(cr, uid, incoming_ship_ids, ['name'], context) if inc_ship['name']]
328
+[out['name']+_(' (OUT)') for out in pick_obj.read(cr, uid, out_ids, ['name'], context) if out['name']]
329
+[pick['name']+_(' (PICK)') for pick in pick_obj.read(cr, uid, pick_ids, ['name'], context) if pick['name']]
330
+[ppl['name']+_(' (PPL)') for ppl in pick_obj.read(cr, uid, ppl_ids, ['name'], context) if ppl['name']]
331
+[tend['name']+_(' (Tender)') for tend in tender_obj.read(cr, uid, tender_ids, ['name'], context) if tend['name']]
332
+[com_vouch['name']+_(' (Commitment Voucher)') for com_vouch in com_vouch_obj.read(cr, uid, com_vouch_ids, ['name'], context) if com_vouch['name']]
333
+[ship['name']+_(' (Shipment)') for ship in ship_obj.read(cr, uid, ship_ids, ['name'], context) if ship['name']]
334
+[absl.name + '(' + absl.statement_id.name + _(' Register)') for absl in absl_obj.browse(cr, uid, absl_ids, context) if absl.name and absl.statement_id and absl.statement_id.name]
335
+[_('%s (Journal Item)') % (aml['move_id'] and aml['move_id'][1] or '') for aml in aml_obj.read(cr, uid, aml_ids, ['move_id'])]
338
def write(self, cr, uid, ids, vals, context=None):
339
if isinstance(ids, (int, long)):
344
self._check_main_partner(cr, uid, ids, vals, context=context)
345
bro_uid = self.pool.get('res.users').browse(cr,uid,uid)
346
bro = bro_uid.company_id
347
res = bro and bro.partner_id and bro.partner_id.id
349
# Avoid the modification of the main partner linked to the company
350
if not context.get('from_config') and res and res in ids:
351
for field in ['name', 'partner_type', 'customer', 'supplier']:
354
# [utp-315] avoid deactivating partner that have still open document linked to them
355
if 'active' in vals and vals.get('active') == False:
356
objects_linked_to_partner = self.get_objects_for_partner(cr, uid, ids, context)
357
if objects_linked_to_partner:
358
raise osv.except_osv(_('Warning'),
359
_("""The following documents linked to the partner need to be closed before deactivating the partner: %s"""
360
) % (objects_linked_to_partner))
361
return super(res_partner, self).write(cr, uid, ids, vals, context=context)
363
def create(self, cr, uid, vals, context=None):
364
if 'partner_type' in vals and vals['partner_type'] in ('internal', 'section', 'esc', 'intermission'):
365
msf_customer = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_internal_customers')
366
msf_supplier = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_internal_suppliers')
367
if msf_customer and not 'property_stock_customer' in vals:
368
vals['property_stock_customer'] = msf_customer[1]
369
if msf_supplier and not 'property_stock_supplier' in vals:
370
vals['property_stock_supplier'] = msf_supplier[1]
371
return super(res_partner, self).create(cr, uid, vals, context=context)
374
def copy_data(self, cr, uid, id, default=None, context=None):
376
Erase some unused data copied from the original object, which sometime could become dangerous, as in UF-1631/1632,
377
when duplicating a new partner (by button duplicate), or company, it creates duplicated currencies
383
fields_to_reset = ['ref_companies'] # reset this value, otherwise the content of the field triggers the creation of a new company
385
for ftr in fields_to_reset:
386
if ftr not in default:
388
res = super(res_partner, self).copy_data(cr, uid, id, default=default, context=context)
394
def on_change_active(self, cr, uid, ids, active, context=None):
396
[utp-315] avoid deactivating partner that have still open document linked to them.
400
if isinstance(ids, (int, long)):
405
objects_linked_to_partner = self.get_objects_for_partner(cr, uid, ids, context)
406
if objects_linked_to_partner:
407
return {'value': {'active': True},
408
'warning': {'title': _('Error'),
409
'message': _("Some documents linked to this partner need to be closed or cancelled before deactivating the partner: %s"
410
) % (objects_linked_to_partner,)}}
413
def on_change_partner_type(self, cr, uid, ids, partner_type, sale_pricelist, purchase_pricelist):
415
Change the procurement method according to the partner type
417
price_obj = self.pool.get('product.pricelist')
418
cur_obj = self.pool.get('res.currency')
419
user_obj = self.pool.get('res.users')
421
r = {'po_by_project': 'project'}
423
if not partner_type or partner_type in ('external', 'internal'):
424
r.update({'po_by_project': 'all'})
426
sale_authorized_price = price_obj.search(cr, uid, [('type', '=', 'sale'), ('in_search', '=', partner_type)])
427
if sale_authorized_price and sale_pricelist not in sale_authorized_price:
428
r.update({'property_product_pricelist': sale_authorized_price[0]})
430
purchase_authorized_price = price_obj.search(cr, uid, [('type', '=', 'purchase'), ('in_search', '=', partner_type)])
431
if purchase_authorized_price and purchase_pricelist not in purchase_authorized_price:
432
r.update({'property_product_pricelist_purchase': purchase_authorized_price[0]})
434
if partner_type and partner_type in ('internal', 'section', 'esc', 'intermission'):
435
msf_customer = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_internal_customers')
437
r.update({'property_stock_customer': msf_customer[1]})
438
msf_supplier = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_internal_suppliers')
440
r.update({'property_stock_supplier': msf_supplier[1]})
442
other_customer = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_customers')
444
r.update({'property_stock_customer': other_customer[1]})
445
other_supplier = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_suppliers')
447
r.update({'property_stock_supplier': other_supplier[1]})
451
def search(self, cr, uid, args=None, offset=0, limit=None, order=None, context=None, count=False):
453
Sort suppliers to have all suppliers in product form at the top of the list
455
supinfo_obj = self.pool.get('product.supplierinfo')
462
tmp_res = super(res_partner, self).search(cr, uid, args, offset, limit, order, context=context, count=count)
463
if not context.get('product_id', False) or 'choose_supplier' not in context or count:
466
# Get all supplier in product form
467
args.append(('in_product', '=', True))
468
res_in_prod = super(res_partner, self).search(cr, uid, args, offset, limit, order, context=context, count=count)
471
# Sort suppliers by sequence in product form
472
if 'product_id' in context:
473
supinfo_ids = supinfo_obj.search(cr, uid, [('name', 'in', res_in_prod), ('product_product_ids', '=', context.get('product_id'))], order='sequence')
475
for result in supinfo_obj.read(cr, uid, supinfo_ids, ['name']):
477
tmp_res.remove(result['name'][0])
478
new_res.append(result['name'][0])
482
#return new_res # comment this line to have all suppliers (with suppliers in product form at the top of the list)
484
new_res.extend(tmp_res)
488
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
490
Show the button "Show inactive" in the partner search view only when we have in the context {'show_button_show_inactive':1}.
494
view = super(res_partner, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
495
if view_type == 'search':
496
if not context or not context.get('show_button_show_inactive', False):
497
tree = etree.fromstring(view['arch'])
498
fields = tree.xpath('//filter[@name="inactive"]')
500
field.set('invisible', "1")
501
view['arch'] = etree.tostring(tree)
506
44
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: