diff --git a/.mailmap b/.mailmap index de72af905da..9e806a9ffa2 100644 --- a/.mailmap +++ b/.mailmap @@ -4,6 +4,8 @@ Ajay Javiya Ajay Javiya Akash Balar Akash Balar +Akta Dakwala +Akta Dakwala EDA(Axelor) <> Albert Cervera i Areny Albert Cervera i Areny Albert Cervera i Areny - http://www.NaN-tic.com <> Albert Cervera i Areny Albert Cervera i Areny <> @@ -20,6 +22,7 @@ Alexis de Lattre Alexis de Lattre Alexis de Lattre Alexis de Lattre <> Alexis de Lattre alexis.delattre@akretion.com <> +Aliane Abdelouahab Aline Preillon Aline Preillon Amit Bhavsar @@ -103,6 +106,9 @@ Atul Patel ATP(OpenERP) <> Atul Patel ATUL (OpenERP) <> Atul Patel atp (OpenERP) <> Augustin Cisterne-Kaas Augustin Cisterne-Kaas +Avdhesh Patel +Avdhesh Patel AVP(Axelor) <> +Avdhesh Patel avp Baskhuu Jacara Jacara <> Baskhuu Jacara jacara <> Benoit Guillot @@ -113,6 +119,7 @@ Bharat Devnani Bharat Devnani Bharat Devnani (Open ERP) <> Bharat Devnani Bharat Devnani(OpenERP) <> Bharat Devnani bde@tinyerp.com <> +Bhatt Chintal bch <> Bhavik Bagdiya Bhavik Bagdiya Bhumi Patel @@ -217,6 +224,8 @@ Dhara Shah Dhara (OpenERP) <> Dhara Shah Dhara Dhara Shah dsh (Open ERP) <> Dhara Shah dsh(Open ERP) <> +Dharmesh Rathod +Dharmesh Rathod DRA Axelor <> Dharmraj Jhala Dharmraj Jhala Dharmraj Jhala @@ -320,6 +329,7 @@ Guewen Baconnier Guewen Baconnier <> Guillem Barba Guillem Barba <> Guillem Barba guillem <> Guillermo Bisheimer +Gulamfarid Ghanchi Géry Debongnie Géry Debongnie Géry Debongnie @@ -330,6 +340,7 @@ Hardik Ansodariya Hardik Ansodariya (OpenERP) <> Hardik Ansodariya Hardik Ansodariya <> Hardik R. Shamnani Hardik R. Shamnani hsa-tinyerp <> +Hardik Sanchawat Hardik Sanchawat (OpenERP) <> Harshad Modi Harshad Modi HMO (Open ERP) <> Harshad Modi HMO(OpenERP) <> @@ -345,6 +356,7 @@ Herbert Petersen Conexus.at <> Hiral Patel Hitesh Trivedi Hugo Santos hugo <> +Hugo Santos hugosdsantos Humberto Arocha hbto(humbertoarocha) <> Husen Daudi Husen Daudi @@ -352,6 +364,7 @@ Husen Daudi Husen Daudi Husen Daudi Husen Daudi JVO,HDA <> +Husen Daudi ashish Husen Daudi hda(Open ERP) <> Husen Daudi hda@tinyerp.com <> Husen Daudi (Serpent CS) @@ -412,6 +425,10 @@ Jean-Baptiste Aubort Jean-Christophe Vassort Jean-Christophe Vassort Jean-Sebastien Suzanne Jean-Sebastien SUZANNE <> +Jenil Malavia +Jenil Malavia JMA(OpenEPR) <> +Jenil Malavia JMA(OpenERP) <> +Jenil Malavia Jenil(OpenERP) <> Jeremy Kersten Jeremy Kersten Jeremy Kersten jke-openerp <> @@ -422,6 +439,7 @@ Jigar Amin Jigar Amin Jigar Amin JAM (OpenERP) <> Jigar Amin JAM(OpenERP) <> +Jigar Amin amami <> Jigar Amin jam <> Jignesh Rathod Jignesh Rathod @@ -461,6 +479,9 @@ Kuldeep Joshi Kuldeep Joshi Kuldeep Joshi kjo <> Kunal Chavda +Kunal Mathur +Kunal Mathur +Kunal Mathur KUM(OpenERP) <> Laetitia Gangloff Launchpad Launchpad Launchpad Translations on behalf of openerp <> @@ -497,6 +518,8 @@ Mantavya Gajjar Mantavya Gajjar MGA(OpenERP) <> Mantavya Gajjar Matvaya Gajjar(Openerp) <> Mantavya Gajjar mga@tinyerp.com <> +Manuel Abadia Manu <> +Manuel Abadia Manu, Jigar Amin - OpenERP <> Marcelo Hamra Marcelo Hamra <> Marcelo Hamra marcelo Mario Arias Badila @@ -579,6 +602,7 @@ Nicolas Bessi Nicolas Bessi Nicolas <> Nicolas Bessi Nicolas Bessi(C2C) <> Nicolas Bessi nicolas.bessi@camptocamp.com <> +Nicolas De Smet Zyph0s <> Nicolas Seinlet Nicolas Vanhoren Nicolas Vanhoren @@ -592,6 +616,9 @@ Niels Huylebroeck Niels Huylebroeck <> Niels Huylebroeck Niels(Red15) <> Niels Huylebroeck Niels(Rrd15) <> Niels Huylebroeck Red15 <> +Nikunj Jani +Nikunj Jani +Nikunj Jani Reporting Team(JNI OpenERP) <> Nimesh Contractor Oliver Laurent Olivier Dony @@ -599,24 +626,33 @@ Olivier Dony Olivier Dony Olivier Dony Odowan Kenobi <> Olivier Dony Olivier Dony <> -Olivier Dony Olvier Dony <> +Olivier Dony Olivier Laurent, Olivier Dony <> Olivier Dony OpenERP Framework R&D Team <> Olivier Dony odo@tinyerp.com <> Olivier Dossmann Olivier DOSSMANN Olivier Laurent Olivier Laurent Olivier Laurent -Olivier Laurent Olivier Laurent, Olivier Dony <> Olivier Laurent olt at tinyerp <> Olivier Laurent olt@tinyerp.com <> +Olivier Macchioni Omar Castiñeira Saavedra Omar Castiñeira Saavedra Omar (Pexego) <> Omar Castiñeira Saavedra Omar Omar Castiñeira Saavedra Omar(Pexego),Anup(OpenERP) <> Omar Castiñeira Saavedra omar7r <> +Ostap Yakovenko Pankaj Joshi Pankaj Joshi Pankaj Joshi Pankita Shah +Pankti Parikh +Pankti Parikh +Pankti Parikh PAP (Openerp) <> +Pankti Parikh PAP(OpenERP) <> +Pankti Parikh PAP(Openerp) <> +Pankti Parikh pap (OpenERP) <> +Pankti Parikh pap(openerp) <> +Pankti Parikh pap@tinyerp.com <> Panos Christeas Panos Christeas Panos Christeas @@ -640,8 +676,10 @@ Pariket Trivedi Parth Gajjar Parthiv Patel Parthiv Patel +Patrick Monnerat Patrik <> Patrick Thieuleux Patrick Thieuleux <> Paul J Stevens Paul J Stevens <> +Paul Rad-Dupuy vivisci <> Paulius Sladkevičius Paulius Sladkevičius Paulius Sladkevičius Paulius Sladkevičius Paulius Sladkevičius @@ -654,6 +692,7 @@ Peter Langenberg Peter Langenberg <> Philu philubert@gmail.com <> Phong Nguyen Phong Nguyen Phong Nguyen-Thanh <> +Phuong Lu phu <> Pinakin Nayi Pinakin Nayi Pinakin Nayi Pinakin Nai(OpenERP) <> @@ -732,6 +771,10 @@ Ravi Gohil Ravi Gohil Ravi Gohil Ravi Gohil (OpenERP) <> Ravi Gohil rgo <> +Ravindra Mekhiya +Ravindra Mekhiya +Ravindra Mekhiya RME(OpenERP) <> +Ravindra Mekhiya Ravindra Mekhiya (OpenERP) <> Ravish Murari Ravish Murari Ravish Murari Ravish(OpenERP) <> @@ -767,6 +810,11 @@ Rikenkumar Bhorania RCH(OpenERP). <> Rikenkumar Bhorania rch (Open ERP) <> Rikenkumar Bhorania rch (OpenERP) <> Rikenkumar Bhorania rch(OpenERP) <> +Rima Vora +Rima Vora +Rima Vora RVO <> +Rima Vora RVO(OpenERP) <> +Rima Vora Rvo(Open ERP) <> Robert Sevcik Robajz <> Roberto López López Rohan Nayani @@ -785,8 +833,23 @@ Rucha Patel Rucha Patel RPA(OpenERP) <> Rucha Patel rpa(Open ERP) <> Rucha Patel rpa-openerp <> +Sabrina Romero +Saiyed Azadahmed +Saiyed Azadahmed +Saiyed Azadahmed SAZ (Open ERP) <> +Saiyed Azadahmed SAZ (OpenERP) <> +Saiyed Azadahmed SAZ (Opn ERP) <> +Saiyed Azadahmed SAZ (OprnERP) <> +Saiyed Azadahmed SAZ(OpenERP) <> +Saiyed Azadahmed SAz <> +Saiyed Azadahmed saz <> +Saiyed Azadahmed saz(OpenERP) <> +Saiyed Azadahmed saz(openerp) <> Salvatore Trimarchi Salvi Angjeli Salvi Angjeli <> +Sananaz Mansuri +Sananaz Mansuri SMA(OpenERP) <> +Sananaz Mansuri sma@tinyerp.com <> Sandy Carter Sandy Carter Sanjay Gohel @@ -818,6 +881,7 @@ Sheth Sunny Sheth Sunny Sheth Sunny Sheth Sunny sus +Shyamli Singh Simon Lejeune Simon Lejeune Simone Orsi @@ -897,6 +961,11 @@ Ujjvala Collins uco(OpenERP) <> Vadim Chobanu Vadim - SmartMode LTD <> Vaibhav Darji Vaibhav Darji +Vaishali Thakkar +Vaishali Thakkar VTH(OpenERP) <> +Valencia Rodrigues +Valencia Rodrigues VRO(OpenERP) <> +Vee vitridex <> Vianney da Costa Vianney da Costa Vianney - www.akretion.com.br <> Vianney da Costa Vianney,Anup(OpenERP) <> @@ -941,6 +1010,9 @@ Xavier Morel Xavier Morel Xavier Morel <> Xavier Morel Xavier(OpenERP) <> Xavier Morel xmo <> +Yajushi Yagnik +Yajushi Yagnik YYA(Axelor) <> +Yajushi Yagnik YYA(OpenERP) <> Yann Papouin Yann Papouin Yann Papouin <> Yann Papouin Yann papouin <> @@ -950,6 +1022,7 @@ Yannick Vaucher Yannick Vaucher <> Yannick Vaucher Yannick Vaucher Yannick Vaucher Yannick Vaucher@Camptocamp <> Yogesh Parekh +Yogesh Parekh Yogesh Parekh Yogesh Sakhreliya Yogesh Sakhreliya @@ -968,63 +1041,12 @@ Yury Tello Yury Tello _FIXME ajm _FIXME ajm AJM (OpenERP) <> -_FIXME bch bch <> -_FIXME ggh -_FIXME jma -_FIXME jma JMA(OpenEPR) <> -_FIXME jma JMA(OpenERP) <> -_FIXME jni -_FIXME jni -_FIXME jni Reporting Team(JNI OpenERP) <> -_FIXME kum -_FIXME kum -_FIXME kum KUM(OpenERP) <> -_FIXME pap -_FIXME pap -_FIXME pap PAP (Openerp) <> -_FIXME pap PAP(OpenERP) <> -_FIXME pap PAP(Openerp) <> -_FIXME pap pap (OpenERP) <> -_FIXME pap pap(openerp) <> -_FIXME pap pap@tinyerp.com <> _FIXME rde rde <> -_FIXME rme -_FIXME rme -_FIXME rme RME(OpenERP) <> -_FIXME rvo -_FIXME rvo -_FIXME rvo RVO <> -_FIXME rvo RVO(OpenERP) <> -_FIXME rvo Rvo(Open ERP) <> -_FIXME saz -_FIXME saz -_FIXME saz SAZ (Open ERP) <> -_FIXME saz SAZ (OpenERP) <> -_FIXME saz SAZ (Opn ERP) <> -_FIXME saz SAZ (OprnERP) <> -_FIXME saz SAZ(OpenERP) <> -_FIXME saz SAz <> -_FIXME saz saz <> -_FIXME saz saz(OpenERP) <> -_FIXME saz saz(openerp) <> -_FIXME sma -_FIXME sma SMA(OpenERP) <> -_FIXME sma sma@tinyerp.com <> _FIXME sme _FIXME sme SME(OpenERP) <> _FIXME sme SME(Tiny/Axelor) <> _FIXME sme SME(tiny/Axelor) <> _FIXME sme sme <> -_FIXME ssi -_FIXME vro -_FIXME vro VRO(OpenERP) <> -_FIXME vth -_FIXME vth VTH(OpenERP) <> -_FIXME yya -_FIXME yya EDA(Axelor) <> -_FIXME yya -_FIXME yya YYA(Axelor) <> -_FIXME yya YYA(OpenERP) <> __FIXME bhumi __FIXME tiny __FIXME tiny @@ -1032,25 +1054,22 @@ __FIXME tiny tiny <> # TODO INTERNAL -# 2 AVP(Axelor) <> -# 1 DRA(Axelor) <> -# 1 avp # 2 Priyanka Jani(OpenERP Trainee) <> -# 2 Hardik Sanchawat (OpenERP) <> -# 2 Jenil(OpenERP) <> # 1 Dhruvit Darji(OpenERP Trainee) <> -# 1 Ravindra Mekhiya (OpenERP) <> -# 1 Yogesh Parekh (OpenERP Trainee) # 1 Nikita Parmar (OpenERP Trainee) # 1 Sejal Bhut (OpenERP Trainee) -# 1 Amit (Open ERP) <> -# 1 phu <> # 1 vba <> + +# TODO INTERNAL Conflict + +# 1 Amit (Open ERP) <> # 1 Ajay Patel # 1 Yogesh(Open ERP) <> # 1 Yogesh(OpenERP) <> # 1 Chirag +# TODO INTERNAL Couples + # 1 ACH,Jay <> # 2 JMA,VRA <> # 2 Vir,Amp <> @@ -1088,6 +1107,8 @@ __FIXME tiny tiny <> # 1 Olivier Dony, Jay Vora <> # 1 Christophe Simonis, Olivier Dony <> +# TODO INTERNAL Team Leaders + # 2 Framework Team <> # 2 OpenERP R&D Framework Team <> # 2 Quality Team <> @@ -1095,6 +1116,8 @@ __FIXME tiny tiny <> # 1 Mod2 Team(OpenERP) <> # 1 OnDemand Open Object User +# TODO INTERNAL Leading Space + # 1 Amit Dodiya (OpenERP) <> # 1 Amit Dodiya(OpenERP) <> # 1 Jean-Christophe VASSORT <> @@ -1108,7 +1131,6 @@ __FIXME tiny tiny <> # 2 jkei # 2 richierichrawr # 2 watermelonjuice -# 1 vrenaville # 2 youring # 2 Дмитро Катюха # 1 jbeficent @@ -1117,9 +1139,7 @@ __FIXME tiny tiny <> # 1 jmustonen # 1 celm1990 # 1 cysnake4713 -# 1 dharmesh rathod # 1 driou -# 1 dsabrinarg # 1 eneldoserrata # 1 fdesmottes # 1 g.shipilov @@ -1127,38 +1147,25 @@ __FIXME tiny tiny <> # 1 senthilnathang # 1 the-digital-engineer # 1 yingliu4203 -# 1 hugosdsantos # 1 krnkris # 1 buke -# 1 OstapYakovenko # 1 DEEP SILVER ACCESSORY # 1 neps1192 -# 1 ALIANE Abdelouahab # 1 Invitu # 1 Jorge # 1 Maria G # 1 Mack -# TODO MISSING INFO - -# 1 Olivier Macchioni -# Email not found +# TODO MISSING INFO EMAIL # 2 Denis Seleznyov <> -# Email not found # 1 openerp user # Name: Milan Tribuson -# email not found -# 1 Manu, Jigar Amin - OpenERP <> -# 1 Ferchu <> -# 1 Patrik <> # 1 R.Messier <> -# 1 Manu <> -# 1 Zyph0s <> -# 1 amami <> -# 1 vitridex <> -# 1 vivisci <> -# 1 ashish +# Name: Raphael Messier + +# 1 Ferchu <> +# Launchpad: https://launchpad.net/~fgb diff --git a/MANIFEST.in b/MANIFEST.in index 54a5dcd8994..eea6b83adeb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,6 +17,7 @@ recursive-include openerp *.rng recursive-include openerp *.rst recursive-include openerp *.sass recursive-include openerp *.sql +recursive-include openerp *.template recursive-include openerp *.txt recursive-include openerp *.ttf recursive-include openerp *.woff @@ -25,4 +26,3 @@ recursive-include openerp *.xml recursive-include openerp *.yml recursive-exclude * *.py[co] recursive-exclude * *.hg* -graft openerp/cli/templates diff --git a/addons/account_payment/account_payment.py b/addons/account_payment/account_payment.py index 0b617eeacae..44345abe8e1 100644 --- a/addons/account_payment/account_payment.py +++ b/addons/account_payment/account_payment.py @@ -397,8 +397,8 @@ class payment_line(osv.osv): break return {'value': data} - def fields_get(self, cr, uid, fields=None, context=None): - res = super(payment_line, self).fields_get(cr, uid, fields, context) + def fields_get(self, cr, uid, fields=None, context=None, write_access=True, attributes=None): + res = super(payment_line, self).fields_get(cr, uid, fields, context, write_access, attributes) if 'communication2' in res: res['communication2'].setdefault('states', {}) res['communication2']['states']['structured'] = [('readonly', True)] diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py index 5963ee2a66d..937e672ef89 100644 --- a/addons/auth_ldap/users_ldap.py +++ b/addons/auth_ldap/users_ldap.py @@ -105,7 +105,7 @@ class CompanyLDAP(osv.osv): if results and len(results) == 1: dn = results[0][0] conn = self.connect(conf) - conn.simple_bind_s(dn, password) + conn.simple_bind_s(dn, password.encode('utf-8')) conn.unbind() entry = results[0] except ldap.INVALID_CREDENTIALS: @@ -141,7 +141,7 @@ class CompanyLDAP(osv.osv): try: conn = self.connect(conf) conn.simple_bind_s(conf['ldap_binddn'] or '', - conf['ldap_password'] or '') + conf['ldap_password'].encode('utf-8') or '') results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE, filter, retrieve_attributes, timeout=60) conn.unbind() diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py index 0276e49582d..f34e03b69a7 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py @@ -281,7 +281,8 @@ class account_analytic_line(osv.osv): details.append("%s" % (line['unit_amount'], )) if data.get('name', False): details.append(line['name']) - note.append(u' - '.join(map(lambda x: unicode(x) or '',details))) + if details: + note.append(u' - '.join(map(lambda x: unicode(x) or '',details))) if note: curr_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '',note))) invoice_line_obj.create(cr, uid, curr_line, context=context) diff --git a/addons/procurement_jit/procurement_jit.py b/addons/procurement_jit/procurement_jit.py index 9fa3ed45f83..c7b36ebed98 100644 --- a/addons/procurement_jit/procurement_jit.py +++ b/addons/procurement_jit/procurement_jit.py @@ -26,7 +26,9 @@ class procurement_order(osv.osv): _inherit = "procurement.order" def create(self, cr, uid, vals, context=None): + context = context or {} procurement_id = super(procurement_order, self).create(cr, uid, vals, context=context) - self.run(cr, uid, [procurement_id], context=context) - self.check(cr, uid, [procurement_id], context=context) + if not context.get('procurement_autorun_defer'): + self.run(cr, uid, [procurement_id], context=context) + self.check(cr, uid, [procurement_id], context=context) return procurement_id diff --git a/addons/product/product_demo.xml b/addons/product/product_demo.xml index c239098bcf7..144685d45cf 100644 --- a/addons/product/product_demo.xml +++ b/addons/product/product_demo.xml @@ -186,7 +186,6 @@ FaceTime HD Camera, 1.2 MP Photos consu - Custom computer assembled on order based on customer's requirement. Bose's smallest portable Bluetooth speaker B3423 diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 6122c46edb9..98a3fa1ae33 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -197,6 +197,13 @@ class purchase_order(osv.osv): ('done', 'Done'), ('cancel', 'Cancelled') ] + + READONLY_STATES = { + 'confirmed': [('readonly', True)], + 'approved': [('readonly', True)], + 'done': [('readonly', True)] + } + _track = { 'state': { 'purchase.mt_rfq_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirmed', @@ -225,16 +232,16 @@ class purchase_order(osv.osv): copy=False), 'date_approve':fields.date('Date Approved', readonly=1, select=True, copy=False, help="Date on which purchase order has been approved"), - 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, + 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states=READONLY_STATES, change_default=True, track_visibility='always'), 'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)', - states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, + states=READONLY_STATES, help="Put an address if you want to deliver directly from the supplier to the customer. " \ "Otherwise, keep empty to deliver to your own company." ), - 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]} ), - 'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."), - 'currency_id': fields.many2one('res.currency','Currency', readonly=True, required=True,states={'draft': [('readonly', False)],'sent': [('readonly', False)]}), + 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states=READONLY_STATES), + 'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states=READONLY_STATES, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."), + 'currency_id': fields.many2one('res.currency','Currency', required=True, states=READONLY_STATES), 'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, help="The status of the purchase order or the quotation request. " "A request for quotation is a purchase order in a 'Draft' status. " diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index 277b3945798..5a8630c9a90 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -173,6 +173,7 @@ class purchase_requisition(osv.osv): 'order_id': purchase_id, 'product_id': product.id, 'account_analytic_id': requisition_line.account_analytic_id.id, + 'taxes_id': [(6, 0, vals.get('taxes_ids', []))], }) return vals diff --git a/addons/sale/sale.py b/addons/sale/sale.py index cce41a92050..78d887469ee 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -690,7 +690,7 @@ class sale_order(osv.osv): :return: True """ - context = dict(context) + context = context or {} context['lang'] = self.pool['res.users'].browse(cr, uid, uid).lang procurement_obj = self.pool.get('procurement.order') sale_line_obj = self.pool.get('sale.order.line') @@ -714,7 +714,9 @@ class sale_order(osv.osv): if (line.state == 'done') or not line.product_id: continue vals = self._prepare_order_line_procurement(cr, uid, order, line, group_id=order.procurement_group_id.id, context=context) - proc_id = procurement_obj.create(cr, uid, vals, context=context) + ctx = context.copy() + ctx['procurement_autorun_defer'] = True + proc_id = procurement_obj.create(cr, uid, vals, context=ctx) proc_ids.append(proc_id) #Confirm procurement order such that rules will be applied on it #note that the workflow normally ensure proc_ids isn't an empty list diff --git a/addons/sale/sale_data.xml b/addons/sale/sale_data.xml index 74f3f060bd7..f5126a25727 100644 --- a/addons/sale/sale_data.xml +++ b/addons/sale/sale_data.xml @@ -40,7 +40,7 @@ Sales Order Confirmed sale.order - + Quotation confirmed diff --git a/addons/stock/product.py b/addons/stock/product.py index 17dbdc17cc2..729f9051846 100644 --- a/addons/stock/product.py +++ b/addons/stock/product.py @@ -94,11 +94,29 @@ class product_product(osv.osv): operator = context.get('compute_child', True) and 'child_of' or 'in' domain = context.get('force_company', False) and ['&', ('company_id', '=', context['force_company'])] or [] - return ( - domain + [('location_id', operator, location_ids)], - domain + ['&', ('location_dest_id', operator, location_ids), '!', ('location_id', operator, location_ids)], - domain + ['&', ('location_id', operator, location_ids), '!', ('location_dest_id', operator, location_ids)] - ) + locations = location_obj.browse(cr, uid, location_ids, context=context) + if operator == "child_of" and locations and locations[0].parent_left != 0: + loc_domain = [] + dest_loc_domain = [] + for loc in locations: + if loc_domain: + loc_domain = ['|'] + loc_domain + ['&', ('location_id.parent_left', '>=', loc.parent_left), ('location_id.parent_left', '<', loc.parent_right)] + dest_loc_domain = ['|'] + dest_loc_domain + ['&', ('location_dest_id.parent_left', '>=', loc.parent_left), ('location_dest_id.parent_left', '<', loc.parent_right)] + else: + loc_domain += ['&', ('location_id.parent_left', '>=', loc.parent_left), ('location_id.parent_left', '<', loc.parent_right)] + dest_loc_domain += ['&', ('location_dest_id.parent_left', '>=', loc.parent_left), ('location_dest_id.parent_left', '<', loc.parent_right)] + + return ( + domain + loc_domain, + domain + ['&'] + dest_loc_domain + ['!'] + loc_domain, + domain + ['&'] + loc_domain + ['!'] + dest_loc_domain + ) + else: + return ( + domain + [('location_id', operator, location_ids)], + domain + ['&', ('location_dest_id', operator, location_ids), '!', ('location_id', operator, location_ids)], + domain + ['&', ('location_id', operator, location_ids), '!', ('location_dest_id', operator, location_ids)] + ) def _get_domain_dates(self, cr, uid, ids, context): from_date = context.get('from_date', False) @@ -115,7 +133,8 @@ class product_product(osv.osv): field_names = field_names or [] domain_products = [('product_id', 'in', ids)] - domain_quant, domain_move_in, domain_move_out = self._get_domain_locations(cr, uid, ids, context=context) + domain_quant, domain_move_in, domain_move_out = [], [], [] + domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations(cr, uid, ids, context=context) domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products domain_quant += domain_products @@ -129,9 +148,12 @@ class product_product(osv.osv): moves_in = [] moves_out = [] else: + domain_move_in += domain_move_in_loc + domain_move_out += domain_move_out_loc moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context) moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context) + domain_quant += domain_quant_loc quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context) quants = dict(map(lambda x: (x['product_id'][0], x['qty']), quants)) @@ -163,17 +185,33 @@ class product_product(osv.osv): if operator == '=': operator = '==' - product_ids = self.search(cr, uid, [], context=context) ids = [] - if product_ids: - #TODO: use a query instead of this browse record which is probably making the too much requests, but don't forget - #the context that can be set with a location, an owner... - for element in self.browse(cr, uid, product_ids, context=context): - if eval(str(element[field]) + operator + str(value)): - ids.append(element.id) - res.append(('id', 'in', ids)) + if name == 'qty_available': + res.append(('id', 'in', self._search_qty_available(cr, uid, operator, value, context))) + else: + product_ids = self.search(cr, uid, [], context=context) + if product_ids: + #TODO: Still optimization possible when searching virtual quantities + for element in self.browse(cr, uid, product_ids, context=context): + if eval(str(element[field]) + operator + str(value)): + ids.append(element.id) + res.append(('id', 'in', ids)) return res + def _search_qty_available(self, cr, uid, operator, value, context): + domain_quant = [] + if context.get('lot_id'): + domain_quant.append(('lot_id', '=', context['lot_id'])) + if context.get('owner_id'): + domain_quant.append(('owner_id', '=', context['owner_id'])) + if context.get('package_id'): + domain_quant.append(('package_id', '=', context['package_id'])) + domain_quant += self._get_domain_locations(cr, uid, [], context=context)[0] + quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context) + quants = dict(map(lambda x: (x['product_id'][0], x['qty']), quants)) + quants = dict((k, v) for k, v in quants.iteritems() if eval(str(v) + operator + str(value))) + return(list(quants)) + def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None): res = {} for product in self.browse(cr, uid, ids, context=context): @@ -294,41 +332,35 @@ class product_template(osv.osv): _inherit = 'product.template' def _product_available(self, cr, uid, ids, name, arg, context=None): - res = dict.fromkeys(ids, 0) - for product in self.browse(cr, uid, ids, context=context): - res[product.id] = { - # "reception_count": sum([p.reception_count for p in product.product_variant_ids]), - # "delivery_count": sum([p.delivery_count for p in product.product_variant_ids]), - "qty_available": sum([p.qty_available for p in product.product_variant_ids]), - "virtual_available": sum([p.virtual_available for p in product.product_variant_ids]), - "incoming_qty": sum([p.incoming_qty for p in product.product_variant_ids]), - "outgoing_qty": sum([p.outgoing_qty for p in product.product_variant_ids]), + prod_available = {} + product_ids = self.browse(cr, uid, ids, context=context) + var_ids = [] + for product in product_ids: + var_ids += [p.id for p in product.product_variant_ids] + variant_available= self.pool['product.product']._product_available(cr, uid, var_ids, context=context) + + for product in product_ids: + qty_available = 0 + virtual_available = 0 + incoming_qty = 0 + outgoing_qty = 0 + for p in product.product_variant_ids: + qty_available += variant_available[p.id]["qty_available"] + virtual_available += variant_available[p.id]["virtual_available"] + incoming_qty += variant_available[p.id]["incoming_qty"] + outgoing_qty += variant_available[p.id]["outgoing_qty"] + prod_available[product.id] = { + "qty_available": qty_available, + "virtual_available": virtual_available, + "incoming_qty": incoming_qty, + "outgoing_qty": outgoing_qty, } - return res + return prod_available def _search_product_quantity(self, cr, uid, obj, name, domain, context): prod = self.pool.get("product.product") - res = [] - for field, operator, value in domain: - #to prevent sql injections - assert field in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty'), 'Invalid domain left operand' - assert operator in ('<', '>', '=', '!=', '<=', '>='), 'Invalid domain operator' - assert isinstance(value, (float, int)), 'Invalid domain right operand' - - if operator == '=': - operator = '==' - - product_ids = prod.search(cr, uid, [], context=context) - ids = [] - if product_ids: - #TODO: use a query instead of this browse record which is probably making the too much requests, but don't forget - #the context that can be set with a location, an owner... - for element in prod.browse(cr, uid, product_ids, context=context): - if eval(str(element[field]) + operator + str(value)): - ids.append(element.id) - res.append(('product_variant_ids', 'in', ids)) - return res - + product_variant_ids = prod.search(cr, uid, domain, context=context) + return [('product_variant_ids', 'in', product_variant_ids)] def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None): res = {} @@ -372,13 +404,13 @@ class product_template(osv.osv): # fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), # 'delivery_count': fields.function(_product_available, multi='qty_available', # fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), - 'qty_available': fields.function(_product_available, multi='qty_available', + 'qty_available': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), - 'virtual_available': fields.function(_product_available, multi='qty_available', + 'virtual_available': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Quantity Available'), - 'incoming_qty': fields.function(_product_available, multi='qty_available', + 'incoming_qty': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Incoming'), - 'outgoing_qty': fields.function(_product_available, multi='qty_available', + 'outgoing_qty': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Outgoing'), 'route_ids': fields.many2many('stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes', domain="[('product_selectable', '=', True)]", diff --git a/addons/stock/stock.py b/addons/stock/stock.py index fa004c232d0..d2a5265ef13 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -296,7 +296,7 @@ class stock_quant(osv.osv): _columns = { 'name': fields.function(_get_quant_name, type='char', string='Identifier'), 'product_id': fields.many2one('product.product', 'Product', required=True, ondelete="restrict", readonly=True, select=True), - 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict", readonly=True, select=True), + 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict", readonly=True, select=True, auto_join=True), 'qty': fields.float('Quantity', required=True, help="Quantity of products in this quant, in the default unit of measure of the product", readonly=True, select=True), 'product_uom_id': fields.related('product_id', 'uom_id', type='many2one', relation="product.uom", string='Unit of Measure', readonly=True), 'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant", readonly=True, select=True), @@ -1770,8 +1770,10 @@ class stock_move(osv.osv): 'product_packaging': fields.many2one('product.packaging', 'Prefered Packaging', help="It specifies attributes of packaging like type, quantity of packaging,etc."), - 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True, states={'done': [('readonly', True)]}, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."), - 'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True, states={'done': [('readonly', True)]}, select=True, help="Location where the system will stock the finished products."), + 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True, auto_join=True, + states={'done': [('readonly', True)]}, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."), + 'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True, states={'done': [('readonly', True)]}, select=True, + auto_join=True, help="Location where the system will stock the finished products."), 'partner_id': fields.many2one('res.partner', 'Destination Address ', states={'done': [('readonly', True)]}, help="Optional address where goods are to be delivered, specifically used for allotment"), @@ -2587,7 +2589,7 @@ class stock_inventory(osv.osv): :rtype: list of tuple """ #default available choices - res_filter = [('none', _('All products')), ('product', _('One product only'))] + res_filter = [('none', _('All products')), ('partial', _('Manual Selection of Products')), ('product', _('One product only'))] settings_obj = self.pool.get('stock.config.settings') config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context) #If we don't have updated config until now, all fields are by default false and so should be not dipslayed @@ -2630,7 +2632,10 @@ class stock_inventory(osv.osv): 'partner_id': fields.many2one('res.partner', 'Inventoried Owner', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Owner to focus your inventory on a particular Owner."), 'lot_id': fields.many2one('stock.production.lot', 'Inventoried Lot/Serial Number', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Lot/Serial Number to focus your inventory on a particular Lot/Serial Number.", copy=False), 'move_ids_exist': fields.function(_get_move_ids_exist, type='boolean', string=' Stock Move Exists?', help='technical field for attrs in view'), - 'filter': fields.selection(_get_available_filters, 'Selection Filter', required=True), + 'filter': fields.selection(_get_available_filters, 'Inventory of', required=True, + help="If you do an entire inventory, you can choose 'All Products' and it will prefill the inventory with the current stock. If you only do some products "\ + "(e.g. Cycle Counting) you can choose 'Manual Selection of Products' and the system won't propose anything. You can also let the "\ + "system propose for a single product / lot /... "), 'total_qty': fields.function(_get_total_qty, type="float"), } @@ -2742,25 +2747,11 @@ class stock_inventory(osv.osv): for inventory in self.browse(cr, uid, ids, context=context): # If there are inventory lines already (e.g. from import), respect those and set their theoretical qty line_ids = [line.id for line in inventory.line_ids] - if not line_ids: + if not line_ids and inventory.filter != 'partial': #compute the inventory lines and create them vals = self._get_inventory_lines(cr, uid, inventory, context=context) for product_line in vals: inventory_line_obj.create(cr, uid, product_line, context=context) - else: - # On import calculate theoretical quantity - quant_obj = self.pool.get("stock.quant") - for line in inventory.line_ids: - dom = [('company_id', '=', line.company_id.id), ('location_id', 'child_of', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id), - ('product_id','=', line.product_id.id), ('owner_id', '=', line.partner_id.id)] - if line.package_id: - dom += [('package_id', '=', line.package_id.id)] - quants = quant_obj.search(cr, uid, dom, context=context) - tot_qty = 0 - for quant in quant_obj.browse(cr, uid, quants, context=context): - tot_qty += quant.qty - inventory_line_obj.write(cr, uid, [line.id], {'theoretical_qty': tot_qty}, context=context) - return self.write(cr, uid, ids, {'state': 'confirm', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) def _get_inventory_lines(self, cr, uid, inventory, context=None): @@ -2816,6 +2807,19 @@ class stock_inventory_line(osv.osv): def _get_prodlot_change(self, cr, uid, ids, context=None): return self.pool.get('stock.inventory.line').search(cr, uid, [('prod_lot_id', 'in', ids)], context=context) + def _get_theoretical_qty(self, cr, uid, ids, name, args, context=None): + res = {} + quant_obj = self.pool["stock.quant"] + uom_obj = self.pool["product.uom"] + for line in self.browse(cr, uid, ids, context=context): + quant_ids = self._get_quants(cr, uid, line, context=context) + quants = quant_obj.browse(cr, uid, quant_ids, context=context) + tot_qty = sum([x.qty for x in quants]) + if line.product_uom_id and line.product_id.uom_id.id != line.product_uom_id.id: + tot_qty = uom_obj._compute_qty_obj(cr, uid, line.product_id.uom_id, tot_qty, line.product_uom_id, context=context) + res[line.id] = tot_qty + return res + _columns = { 'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True), 'location_id': fields.many2one('stock.location', 'Location', required=True, select=True), @@ -2826,7 +2830,8 @@ class stock_inventory_line(osv.osv): 'company_id': fields.related('inventory_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, select=True, readonly=True), 'prod_lot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"), 'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True), - 'theoretical_qty': fields.float('Theoretical Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), readonly=True), + 'theoretical_qty': fields.function(_get_theoretical_qty, type='float', digits_compute=dp.get_precision('Product Unit of Measure'), + store=True, readonly=True, string="Theoretical Quantity"), 'partner_id': fields.many2one('res.partner', 'Owner'), 'product_name': fields.related('product_id', 'name', type='char', string='Product Name', store={ 'product.product': (_get_product_name_change, ['name', 'default_code'], 20), @@ -2843,7 +2848,7 @@ class stock_inventory_line(osv.osv): } _defaults = { - 'product_qty': 1, + 'product_qty': 0, } def create(self, cr, uid, values, context=None): @@ -2854,6 +2859,37 @@ class stock_inventory_line(osv.osv): values['product_uom_id'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id return super(stock_inventory_line, self).create(cr, uid, values, context=context) + def _get_quants(self, cr, uid, line, context=None): + quant_obj = self.pool["stock.quant"] + dom = [('company_id', '=', line.company_id.id), ('location_id', 'child_of', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id), + ('product_id','=', line.product_id.id), ('owner_id', '=', line.partner_id.id), ('package_id', '=', line.package_id.id)] + quants = quant_obj.search(cr, uid, dom, context=context) + return quants + + def onchange_createline(self, cr, uid, ids, location_id=False, product_id=False, uom_id=False, package_id=False, prod_lot_id=False, partner_id=False, company_id=False, context=None): + quant_obj = self.pool["stock.quant"] + uom_obj = self.pool["product.uom"] + res = {'value': {}} + # If no UoM already put the default UoM of the product + if product_id and not uom_id: + product = self.pool.get('product.product').browse(cr, uid, product_id, context=context) + res['value']['product_uom_id'] = product.uom_id.id + res['domain'] = {'product_uom_id': [('category_id','=',product.uom_id.category_id.id)]} + uom_id = product.uom_id.id + # Calculate theoretical quantity by searching the quants as in quants_get + if product_id and location_id: + product = self.pool.get('product.product').browse(cr, uid, product_id, context=context) + if not company_id: + company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + dom = [('company_id', '=', company_id), ('location_id', 'child_of', location_id), ('lot_id', '=', prod_lot_id), + ('product_id','=', product_id), ('owner_id', '=', partner_id), ('package_id', '=', package_id)] + quants = quant_obj.search(cr, uid, dom, context=context) + th_qty = sum([x.qty for x in quant_obj.browse(cr, uid, quants, context=context)]) + if product_id and uom_id and product.uom_id.id != uom_id: + th_qty = uom_obj._compute_qty(cr, uid, product.uom_id.id, th_qty, uom_id) + res['value']['theoretical_qty'] = th_qty + return res + def _resolve_inventory_line(self, cr, uid, inventory_line, context=None): stock_move_obj = self.pool.get('stock.move') diff = inventory_line.theoretical_qty - inventory_line.product_qty @@ -2884,26 +2920,11 @@ class stock_inventory_line(osv.osv): vals['product_uom_qty'] = diff return stock_move_obj.create(cr, uid, vals, context=context) + # Should be left out in next version def restrict_change(self, cr, uid, ids, theoretical_qty, context=None): - if ids and theoretical_qty: - #if the user try to modify a line prepared by openerp, reject the change and display an error message explaining how he should do - old_value = self.browse(cr, uid, ids[0], context=context) - return { - 'value': { - 'product_id': old_value.product_id.id, - 'product_uom_id': old_value.product_uom_id.id, - 'location_id': old_value.location_id.id, - 'prod_lot_id': old_value.prod_lot_id.id, - 'package_id': old_value.package_id.id, - 'partner_id': old_value.partner_id.id, - }, - 'warning': { - 'title': _('Error'), - 'message': _('You can only change the checked quantity of an existing inventory line. If you want modify a data, please set the checked quantity to 0 and create a new inventory line.') - } - } return {} + # Should be left out in next version def on_change_product_id(self, cr, uid, ids, product, uom, theoretical_qty, context=None): """ Changes UoM @param location_id: Location id @@ -2911,8 +2932,6 @@ class stock_inventory_line(osv.osv): @param uom: UoM product @return: Dictionary of changed values """ - if ids and theoretical_qty: - return self.restrict_change(cr, uid, ids, theoretical_qty, context=context) if not product: return {'value': {'product_uom_id': False}} obj_product = self.pool.get('product.product').browse(cr, uid, product, context=context) diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 6c1de13baa9..43f5463dc8c 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -58,7 +58,7 @@ - + stock.inventory.filter stock.inventory @@ -112,10 +112,10 @@ - - - - + + + + @@ -123,12 +123,12 @@