From c850875ca386d81c69f02aa6614bb290b4264cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 2 Oct 2012 12:04:20 +0200 Subject: [PATCH 001/153] [FIX] point_of_sale: Number.isNaN is not supported on older but supported browsers bzr revid: fva@openerp.com-20121002100420-pcvbn7qqxyp62bzw --- addons/point_of_sale/static/src/js/widgets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 47a527a010d..72e7c5c1612 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -712,7 +712,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); this.$('.button.set_weight').click(function(){ var kg = Number(self.$('input.weight').val()); - if(!Number.isNaN(kg)){ + if(!isNaN(kg)){ self.pos.proxy.debug_set_weight(kg); } }); From 0fa7da31685d353e1d397f0f078629447ec9660e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 2 Oct 2012 12:13:53 +0200 Subject: [PATCH 002/153] [WIP FIX] point_of_sale: investigating a bug with codebar scanning in subcategories bzr revid: fva@openerp.com-20121002101353-imjbqe1vxtdjbqu7 --- addons/point_of_sale/static/src/js/devices.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 142d35bd340..f998ed334df 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -432,13 +432,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // The barcode readers acts as a keyboard, we catch all keyup events and try to find a // barcode sequence in the typed keys, then act accordingly. $('body').delegate('','keyup', function (e){ - + console.log('keyup:'+String.fromCharCode(e.keyCode)+' '+e.keyCode,e); //We only care about numbers - if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { + if (e.keyCode >= 48 && e.keyCode < 58){ // The barcode reader sends keystrokes with a specific interval. // We look if the typed keys fit in the interval. - if (codeNumbers.length==0) { + if (codeNumbers.length === 0) { timeStamp = new Date().getTime(); } else { if (lastTimeStamp + 30 < new Date().getTime()) { @@ -449,7 +449,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } codeNumbers.push(e.keyCode - 48); lastTimeStamp = new Date().getTime(); - if (codeNumbers.length == 13) { + if (codeNumbers.length === 13) { //We have found what seems to be a valid codebar self.on_ean(codeNumbers.join('')); codeNumbers = []; From 5f2459422335c966567370eadce3281adff729f7 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 11 Oct 2012 17:20:08 +0200 Subject: [PATCH 003/153] [IMP] edi: work-in-progress: remove permanent storage of edi.document The EDI documents will now be generated on demand and available from the Portal view of each document. Instead of getting a link to a statically generated EDI document, customers will receive a link to the portal access to the document. They will be able to signup on the portal the first time as well, provided they are using the secure token that was sent to them (i.e. the right link). The link to pay online will be available in the portal as well. Still much to do, this is a small first step, with edi.document renamed to edi.edi for consistency, as it will not persist any edi.document anymore. bzr revid: odo@openerp.com-20121011152008-bht7ub6woaex0a7u --- addons/account/edi/invoice.py | 12 +- addons/account/edi/invoice_action_data.xml | 26 +-- addons/account/test/test_edi_invoice.yml | 7 +- addons/edi/__init__.py | 8 +- addons/edi/__openerp__.py | 2 - addons/edi/controllers/main.py | 65 +------- addons/edi/edi_service.py | 12 +- addons/edi/models/edi.py | 153 ++++-------------- addons/edi/models/res_company.py | 4 +- addons/edi/models/res_currency.py | 4 +- addons/edi/models/res_partner.py | 6 +- addons/edi/security/ir.model.access.csv | 3 - addons/edi/test/edi_partner_test.yml | 10 +- .../wizard/mail_compose_message.py | 1 + addons/mail/static/src/xml/mail.xml | 4 +- addons/purchase/edi/purchase_order.py | 21 +-- .../edi/purchase_order_action_data.xml | 29 +--- .../test/process/edi_purchase_order.yml | 14 +- addons/sale/edi/sale_order.py | 23 +-- addons/sale/edi/sale_order_action_data.xml | 10 +- addons/sale/test/edi_sale_order.yml | 9 +- 21 files changed, 86 insertions(+), 337 deletions(-) delete mode 100644 addons/edi/security/ir.model.access.csv diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py index 751eb8656f7..e84bec6614d 100644 --- a/addons/account/edi/invoice.py +++ b/addons/account/edi/invoice.py @@ -71,16 +71,6 @@ INVOICE_EDI_STRUCT = { class account_invoice(osv.osv, EDIMixin): _inherit = 'account.invoice' - def action_invoice_sent(self, cr, uid, ids, context=None): - """"Override this method to add a link to mail""" - if context is None: - context = {} - invoice_objs = self.browse(cr, uid, ids, context=context) - edi_token = self.pool.get('edi.document').export_edi(cr, uid, invoice_objs, context = context)[0] - web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') - ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token)) - return super(account_invoice, self).action_invoice_sent(cr, uid, ids, context=ctx) - def edi_export(self, cr, uid, records, edi_struct=None, context=None): """Exports a supplier or customer invoice""" edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT) @@ -136,7 +126,7 @@ class account_invoice(osv.osv, EDIMixin): self._edi_requires_attributes(('company_id','company_address','type'), edi_document) res_partner = self.pool.get('res.partner') - src_company_id, src_company_name = edi_document.pop('company_id') + _, src_company_name = edi_document.pop('company_id') invoice_type = edi_document['type'] partner_value = {} diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml index 53fa03a6902..1ad023c3a60 100644 --- a/addons/account/edi/invoice_action_data.xml +++ b/addons/account/edi/invoice_action_data.xml @@ -1,17 +1,6 @@ - - - if (object.type in ('out_invoice', 'out_refund')) and not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='account.email_template_edi_invoice', context=context) - - code - ir.actions.server - - True - Auto-email confirmed invoices - - Email Templates @@ -27,17 +16,12 @@ - - - - - - - Automated Invoice Notification Mail + Invoice - Send by mail ${object.user_id.email or object.company_id.email or 'noreply@localhost'} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' }) ${object.partner_id.email or ''} @@ -61,12 +45,6 @@   Your contact: ${object.user_id.name}

-

- You can view the invoice document, download it and pay online using the following link: -

- View Invoice - % if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'): <% comp_name = quote(object.company_id.name) diff --git a/addons/account/test/test_edi_invoice.yml b/addons/account/test/test_edi_invoice.yml index 80dc098e770..3d57f8dc608 100644 --- a/addons/account/test/test_edi_invoice.yml +++ b/addons/account/test/test_edi_invoice.yml @@ -38,11 +38,12 @@ - Then I export the customer invoice - - !python {model: edi.document}: | + !python {model: edi.edi}: | + import json invoice_pool = self.pool.get('account.invoice') invoice = invoice_pool.browse(cr, uid, ref("invoice_edi_1")) - token = self.export_edi(cr, uid, [invoice]) - assert token, 'Invalid EDI Token' + edi_doc = self.generate_edi(cr, uid, [invoice]) + assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict' - Then I import a sample EDI document of another customer invoice - diff --git a/addons/edi/__init__.py b/addons/edi/__init__.py index 46fabbc2fd0..71ee8aaa53d 100644 --- a/addons/edi/__init__.py +++ b/addons/edi/__init__.py @@ -20,14 +20,14 @@ ############################################################################## import logging -import models -import edi_service -from models.edi import EDIMixin, edi_document +from . import models +from . import edi_service +from models.edi import EDIMixin, edi _logger = logging.getLogger(__name__) # web try: - import controllers + import openerp.addons.web.controllers except ImportError: _logger.warn( """Could not load openerp-web section of EDI, EDI will not behave correctly diff --git a/addons/edi/__openerp__.py b/addons/edi/__openerp__.py index b06c01eb03b..72ccb32d701 100644 --- a/addons/edi/__openerp__.py +++ b/addons/edi/__openerp__.py @@ -36,12 +36,10 @@ documentation at http://doc.openerp.com. 'website': 'http://www.openerp.com', 'depends': ['base', 'email_template'], 'icon': '/edi/static/src/img/knowledge.png', - 'data': ['security/ir.model.access.csv'], 'test': ['test/edi_partner_test.yml'], 'js': ['static/src/js/edi.js'], 'css': ['static/src/css/edi.css'], 'qweb': ['static/src/xml/*.xml'], - 'installable': True, 'auto_install': False, } diff --git a/addons/edi/controllers/main.py b/addons/edi/controllers/main.py index c62fef19cba..0322099b6f3 100644 --- a/addons/edi/controllers/main.py +++ b/addons/edi/controllers/main.py @@ -1,31 +1,10 @@ -import json -import textwrap - -import simplejson -import werkzeug.wrappers - -import openerp.addons.web.http as openerpweb +import openerp.addons.web.common.http as openerpweb import openerp.addons.web.controllers.main as webmain class EDI(openerpweb.Controller): - # http://hostname:8069/edi/view?db=XXXX&token=XXXXXXXXXXX # http://hostname:8069/edi/import_url?url=URIEncodedURL _cp_path = "/edi" - def template(self, req, mods='web,edi'): - d = {} - d["js"] = "\n".join(''%i for i in webmain.manifest_list(req, mods, 'js')) - d["css"] = "\n".join(''%i for i in webmain.manifest_list(req, mods, 'css')) - d["modules"] = simplejson.dumps(mods.split(',')) - return d - - @openerpweb.httprequest - def view(self, req, db, token): - d = self.template(req) - d["init"] = 's.edi.edi_view("%s","%s");'%(db,token) - r = webmain.html_template % d - return r - @openerpweb.httprequest def import_url(self, req, url): d = self.template(req) @@ -33,46 +12,6 @@ class EDI(openerpweb.Controller): r = webmain.html_template % d return r - @openerpweb.httprequest - def download(self, req, db, token): - result = req.session.proxy('edi').get_edi_document(db, token) - response = werkzeug.wrappers.Response( result, headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]) - return response - - @openerpweb.httprequest - def download_attachment(self, req, db, token): - result = req.session.proxy('edi').get_edi_document(db, token) - doc = json.loads(result)[0] - attachment = doc['__attachments'] and doc['__attachments'][0] - if attachment: - result = attachment["content"].decode('base64') - import email.Utils as utils - - # Encode as per RFC 2231 - filename_utf8 = attachment['file_name'] - filename_encoded = "%s=%s" % ('filename*', - utils.encode_rfc2231(filename_utf8, 'utf-8')) - response = werkzeug.wrappers.Response(result, headers=[('Content-Type', 'application/pdf'), - ('Content-Disposition', 'inline; ' + filename_encoded), - ('Content-Length', len(result))]) - return response - - @openerpweb.httprequest - def binary(self, req, db, token, field_path="company_address.logo", content_type='image/png'): - result = req.session.proxy('edi').get_edi_document(db, token) - doc = json.loads(result)[0] - for name in field_path.split("."): - doc = doc[name] - result = doc.decode('base64') - response = werkzeug.wrappers.Response(result, headers=[('Content-Type', content_type), - ('Content-Length', len(result))]) - return response - - @openerpweb.jsonrequest - def get_edi_document(self, req, db, token): - result = req.session.proxy('edi').get_edi_document(db, token) - return json.loads(result) - @openerpweb.jsonrequest def import_edi_url(self, req, url): result = req.session.proxy('edi').import_edi_url(req.session._db, req.session._uid, req.session._password, url) @@ -80,6 +19,4 @@ class EDI(openerpweb.Controller): return {"action": webmain.clean_action(req, result[0][2])} return True -# - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/edi/edi_service.py b/addons/edi/edi_service.py index 63fe38786e0..fd911139170 100644 --- a/addons/edi/edi_service.py +++ b/addons/edi/edi_service.py @@ -20,8 +20,8 @@ ############################################################################## import logging -import netsvc import openerp +import openerp.netsvc as netsvc _logger = logging.getLogger(__name__) @@ -34,10 +34,10 @@ class edi(netsvc.ExportService): try: registry = openerp.modules.registry.RegistryManager.get(db_name) assert registry, 'Unknown database %s' % db_name - edi_document = registry['edi.document'] + edi = registry['edi.edi'] cr = registry.db.cursor() res = None - res = getattr(edi_document, method_name)(cr, *method_args) + res = getattr(edi, method_name)(cr, *method_args) cr.commit() except Exception: _logger.exception('Failed to execute EDI method %s with args %r.', method_name, method_args) @@ -46,9 +46,6 @@ class edi(netsvc.ExportService): cr.close() return res - def exp_get_edi_document(self, db_name, edi_token): - return self._edi_dispatch(db_name, 'get_document', 1, edi_token) - def exp_import_edi_document(self, db_name, uid, passwd, edi_document, context=None): return self._edi_dispatch(db_name, 'import_edi', uid, edi_document, None) @@ -59,9 +56,6 @@ class edi(netsvc.ExportService): if method in ['import_edi_document', 'import_edi_url']: (db, uid, passwd ) = params[0:3] openerp.service.security.check(db, uid, passwd) - elif method in ['get_edi_document']: - # No security check for these methods - pass else: raise KeyError("Method not found: %s." % method) fn = getattr(self, 'exp_'+method) diff --git a/addons/edi/models/edi.py b/addons/edi/models/edi.py index 6d11db1c52c..29e19f2da3b 100644 --- a/addons/edi/models/edi.py +++ b/addons/edi/models/edi.py @@ -30,9 +30,9 @@ import urllib2 import openerp import openerp.release as release -import netsvc -import pooler -from osv import osv,fields,orm +import openerp.netsvc as netsvc +from openerp.modules.registry import RegistryManager +from openerp.osv import osv, fields from tools.translate import _ from tools.safe_eval import safe_eval as eval _logger = logging.getLogger(__name__) @@ -74,16 +74,9 @@ def last_update_for(record): return False -class edi_document(osv.osv): - _name = 'edi.document' - _description = 'EDI Document' - _columns = { - 'name': fields.char("EDI token", size = 128, help="Unique identifier for retrieving an EDI document."), - 'document': fields.text("Document", help="EDI document content") - } - _sql_constraints = [ - ('name_uniq', 'unique (name)', 'EDI Tokens must be unique!') - ] +class edi(osv.AbstractModel): + _name = 'edi.edi' + _description = 'EDI Subsystem' def new_edi_token(self, cr, uid, record): """Return a new, random unique token to identify this model record, @@ -109,7 +102,7 @@ class edi_document(osv.osv): """Generates a final EDI document containing the EDI serialization of the given records, which should all be instances of a Model that has the :meth:`~.edi` mixin. The document is not saved in the - database, this is done by :meth:`~.export_edi`. + database. :param list(browse_record) records: records to export as EDI :return: UTF-8 encoded string containing the serialized records @@ -120,19 +113,6 @@ class edi_document(osv.osv): edi_list += record_model_obj.edi_export(cr, uid, [record], context=context) return self.serialize(edi_list) - def get_document(self, cr, uid, edi_token, context=None): - """Retrieve the EDI document corresponding to the given edi_token. - - :return: EDI document string - :raise: ValueError if requested EDI token does not match any know document - """ - _logger.debug("get_document(%s)", edi_token) - edi_ids = self.search(cr, uid, [('name','=', edi_token)], context=context) - if not edi_ids: - raise ValueError('Invalid EDI token: %s.' % edi_token) - edi = self.browse(cr, uid, edi_ids[0], context=context) - return edi.document - def load_edi(self, cr, uid, edi_documents, context=None): """Import the given EDI document structures into the system, using :meth:`~.import_edi`. @@ -171,38 +151,18 @@ class edi_document(osv.osv): """ return json.loads(edi_documents_string) - def export_edi(self, cr, uid, records, context=None): - """Export the given database records as EDI documents, stores them - permanently with a new unique EDI token, for later retrieval via :meth:`~.get_document`, - and returns the list of the new corresponding ``ir.edi.document`` records. - - :param records: list of browse_record of any model - :return: list of IDs of the new ``ir.edi.document`` entries, in the same - order as the provided ``records``. - """ - exported_ids = [] - for record in records: - document = self.generate_edi(cr, uid, [record], context) - token = self.new_edi_token(cr, uid, record) - self.create(cr, uid, { - 'name': token, - 'document': document - }, context=context) - exported_ids.append(token) - return exported_ids - def import_edi(self, cr, uid, edi_document=None, edi_url=None, context=None): """Import a JSON serialized EDI Document string into the system, first retrieving it from the given ``edi_url`` if provided. - :param str|unicode edi_document: UTF-8 string or unicode containing JSON-serialized + :param str|unicode edi: UTF-8 string or unicode containing JSON-serialized EDI Document to import. Must not be provided if ``edi_url`` is given. - :param str|unicode edi_url: URL where the EDI document (same format as ``edi_document``) + :param str|unicode edi_url: URL where the EDI document (same format as ``edi``) may be retrieved, without authentication. """ if edi_url: - assert not edi_document, 'edi_document must not be provided if edi_url is given.' + assert not edi_document, 'edi must not be provided if edi_url is given.' edi_document = urllib2.urlopen(edi_url).read() assert edi_document, 'EDI Document is empty!' edi_documents = self.deserialize(edi_document) @@ -215,10 +175,10 @@ class EDIMixin(object): ``edi_import()`` and ``edi_export()`` methods to implement their specific behavior, based on the primitives provided by this mixin.""" - def _edi_requires_attributes(self, attributes, edi_document): - model_name = edi_document.get('__imported_model') or edi_document.get('__model') or self._name + def _edi_requires_attributes(self, attributes, edi): + model_name = edi.get('__imported_model') or edi.get('__model') or self._name for attribute in attributes: - assert edi_document.get(attribute),\ + assert edi.get(attribute),\ 'Attribute `%s` is required in %s EDI documents.' % (attribute, model_name) # private method, not RPC-exposed as it creates ir.model.data entries as @@ -318,7 +278,6 @@ class EDIMixin(object): :return: list of dicts containing boilerplate EDI metadata for each record, at the corresponding index from ``records``. """ - data_ids = [] ir_attachment = self.pool.get('ir.attachment') results = [] for record in records: @@ -398,7 +357,7 @@ class EDIMixin(object): return [self.edi_m2o(cr, uid, r, context=context) for r in records] def edi_export(self, cr, uid, records, edi_struct=None, context=None): - """Returns a list of dicts representing an edi.document containing the + """Returns a list of dicts representing EDI documents containing the records, and matching the given ``edi_struct``, if provided. :param edi_struct: if provided, edi_struct should be a dictionary @@ -443,50 +402,6 @@ class EDIMixin(object): results.append(edi_dict) return results - def edi_export_and_email(self, cr, uid, ids, template_ext_id, context=None): - """Export the given records just like :meth:`~.export_edi`, the render the - given email template, in order to trigger appropriate notifications. - This method is intended to be called as part of business documents' - lifecycle, so it silently ignores any error occurring during the process, - as this is usually non-critical. To avoid any delay, it is also asynchronous - and will spawn a short-lived thread to perform the action. - - :param str template_ext_id: external id of the email.template to use for - the mail notifications - :return: True - """ - def email_task(): - db = pooler.get_db(cr.dbname) - local_cr = None - try: - time.sleep(3) # lame workaround to wait for commit of parent transaction - # grab a fresh browse_record on local cursor - local_cr = db.cursor() - web_root_url = self.pool.get('ir.config_parameter').get_param(local_cr, uid, 'web.base.url') - if not web_root_url: - _logger.warning('Ignoring EDI mail notification, web.base.url is not defined in parameters.') - return - mail_tmpl = self._edi_get_object_by_external_id(local_cr, uid, template_ext_id, 'email.template', context=context) - if not mail_tmpl: - # skip EDI export if the template was not found - _logger.warning('Ignoring EDI mail notification, template %s cannot be located.', template_ext_id) - return - for edi_record in self.browse(local_cr, uid, ids, context=context): - edi_token = self.pool.get('edi.document').export_edi(local_cr, uid, [edi_record], context = context)[0] - edi_context = dict(context, edi_web_url_view=EDI_VIEW_WEB_URL % (web_root_url, local_cr.dbname, edi_token)) - self.pool.get('email.template').send_mail(local_cr, uid, mail_tmpl.id, edi_record.id, - force_send=False, context=edi_context) - _logger.info('EDI export successful for %s #%s, email notification sent.', self._name, edi_record.id) - except Exception: - _logger.warning('Ignoring EDI mail notification, failed to generate it.', exc_info=True) - finally: - if local_cr: - local_cr.commit() - local_cr.close() - - threading.Thread(target=email_task, name='EDI ExportAndEmail for %s %r' % (self._name, ids)).start() - return True - def _edi_get_object_by_name(self, cr, uid, name, model_name, context=None): model = self.pool.get(model_name) search_results = model.name_search(cr, uid, name, operator='=', context=context) @@ -515,18 +430,20 @@ class EDIMixin(object): file_name = record.name_get()[0][1] file_name = re.sub(r'[^a-zA-Z0-9_-]', '_', file_name) file_name += ".pdf" - ir_attachment = self.pool.get('ir.attachment').create(cr, uid, - {'name': file_name, - 'datas': result, - 'datas_fname': file_name, - 'res_model': self._name, - 'res_id': record.id, - 'type': 'binary'}, - context=context) + self.pool.get('ir.attachment').create(cr, uid, + { + 'name': file_name, + 'datas': result, + 'datas_fname': file_name, + 'res_model': self._name, + 'res_id': record.id, + 'type': 'binary' + }, + context=context) - def _edi_import_attachments(self, cr, uid, record_id, edi_document, context=None): + def _edi_import_attachments(self, cr, uid, record_id, edi, context=None): ir_attachment = self.pool.get('ir.attachment') - for attachment in edi_document.get('__attachments', []): + for attachment in edi.get('__attachments', []): # check attachment data is non-empty and valid file_data = None try: @@ -614,19 +531,19 @@ class EDIMixin(object): self._edi_external_id(cr, uid, target, existing_id=ext_id_members['id'], existing_module=module, context=context) return target.id - def edi_import(self, cr, uid, edi_document, context=None): - """Imports a dict representing an edi.document into the system. + def edi_import(self, cr, uid, edi, context=None): + """Imports a dict representing an EDI document into the system. - :param dict edi_document: EDI document to import + :param dict edi: EDI document to import :return: the database ID of the imported record """ - assert self._name == edi_document.get('__import_model') or \ - ('__import_model' not in edi_document and self._name == edi_document.get('__model')), \ + assert self._name == edi.get('__import_model') or \ + ('__import_model' not in edi and self._name == edi.get('__model')), \ "EDI Document Model and current model do not match: '%s' (EDI) vs '%s' (current)." % \ - (edi_document['__model'], self._name) + (edi['__model'], self._name) # First check the record is now already known in the database, in which case it is ignored - ext_id_members = split_external_id(edi_document['__id']) + ext_id_members = split_external_id(edi['__id']) existing = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context) if existing: _logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full']) @@ -634,7 +551,7 @@ class EDIMixin(object): record_values = {} o2m_todo = {} # o2m values are processed after their parent already exists - for field_name, field_value in edi_document.iteritems(): + for field_name, field_value in edi.iteritems(): # skip metadata and empty fields if field_name.startswith('__') or field_value is None or field_value is False: continue @@ -679,7 +596,7 @@ class EDIMixin(object): dest_model.edi_import(cr, uid, o2m_line, context=context) # process the attachments, if any - self._edi_import_attachments(cr, uid, record_id, edi_document, context=context) + self._edi_import_attachments(cr, uid, record_id, edi, context=context) return record_id diff --git a/addons/edi/models/res_company.py b/addons/edi/models/res_company.py index 0ab6607cdad..d87827df99d 100644 --- a/addons/edi/models/res_company.py +++ b/addons/edi/models/res_company.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,7 +19,7 @@ # ############################################################################## -from osv import fields,osv +from openerp.osv import osv class res_company(osv.osv): """Helper subclass for res.company providing util methods for working with diff --git a/addons/edi/models/res_currency.py b/addons/edi/models/res_currency.py index 0f56bc83f45..474e386a248 100644 --- a/addons/edi/models/res_currency.py +++ b/addons/edi/models/res_currency.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,7 +19,7 @@ # ############################################################################## -from osv import fields,osv +from openerp.osv import osv from edi import EDIMixin from openerp import SUPERUSER_ID diff --git a/addons/edi/models/res_partner.py b/addons/edi/models/res_partner.py index 86d8c81b790..37483c1d38d 100644 --- a/addons/edi/models/res_partner.py +++ b/addons/edi/models/res_partner.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -20,10 +20,10 @@ ############################################################################## import logging -from osv import fields,osv +from openerp.osv import osv from edi import EDIMixin from openerp import SUPERUSER_ID -from tools.translate import _ +from openerp.tools.translate import _ _logger = logging.getLogger(__name__) RES_PARTNER_EDI_STRUCT = { diff --git a/addons/edi/security/ir.model.access.csv b/addons/edi/security/ir.model.access.csv deleted file mode 100644 index b1f470d80ae..00000000000 --- a/addons/edi/security/ir.model.access.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_ir_edi_all_read,access_ir_edi_all_read,model_edi_document,,1,0,0,0 -access_ir_edi_employee_create,access_ir_edi_employee_create,model_edi_document,base.group_user,1,0,1,0 diff --git a/addons/edi/test/edi_partner_test.yml b/addons/edi/test/edi_partner_test.yml index 9fc523bf84f..97ae669e52e 100644 --- a/addons/edi/test/edi_partner_test.yml +++ b/addons/edi/test/edi_partner_test.yml @@ -6,11 +6,10 @@ with an attached file, check the result, the alter the data and reimport it. - - !python {model: edi.document}: | + !python {model: edi.edi}: | import json - partner_obj = self.pool.get('res.partner') - tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_2'))]) - doc = self.get_document(cr, uid, tokens[0], context=context) + res_partner = self.pool.get('res.partner') + doc = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, ref('base.res_partner_2'))]) edi_doc, = json.loads(doc) # check content of the document @@ -36,8 +35,7 @@ "Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_2'), result) # export the same partner we just created, and see if the output matches the input - tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])]) - doc_output = self.get_document(cr, uid, tokens[0], context=context) + doc_output = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, result[1])]) edi_doc_output, = json.loads(doc_output) for attribute in ('__model', '__module', '__id', 'name', '__attachments'): assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \ diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index c0cb7fc92e5..3e434c4d65e 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -83,6 +83,7 @@ class mail_compose_message(osv.osv_memory): 'datas_fname': attach_fname, 'res_model': model, 'res_id': res_id, + 'type': 'binary', # override default_type from context, possibly meant for another model! } values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context)) else: diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 2fc97154e33..66556a30c24 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -60,7 +60,7 @@
  • - ...wait upload... + Upload in progress...
    @@ -73,7 +73,7 @@ - x + x
  • diff --git a/addons/purchase/edi/purchase_order.py b/addons/purchase/edi/purchase_order.py index d1c00a3e60d..f44c16eb0d9 100644 --- a/addons/purchase/edi/purchase_order.py +++ b/addons/purchase/edi/purchase_order.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,13 +19,8 @@ # ############################################################################## -from datetime import datetime, timedelta -from dateutil.relativedelta import relativedelta - -from osv import fields, osv, orm +from openerp.osv import osv from edi import EDIMixin -from edi.models import edi -from tools import DEFAULT_SERVER_DATE_FORMAT from tools.translate import _ PURCHASE_ORDER_LINE_EDI_STRUCT = { @@ -62,16 +57,6 @@ PURCHASE_ORDER_EDI_STRUCT = { class purchase_order(osv.osv, EDIMixin): _inherit = 'purchase.order' - def wkf_send_rfq(self, cr, uid, ids, context=None): - """"Override this method to add a link to mail""" - if context is None: - context = {} - purchase_objs = self.browse(cr, uid, ids, context=context) - edi_token = self.pool.get('edi.document').export_edi(cr, uid, purchase_objs, context = context)[0] - web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') - ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token)) - return super(purchase_order, self).wkf_send_rfq(cr, uid, ids, context=ctx) - def edi_export(self, cr, uid, records, edi_struct=None, context=None): """Exports a purchase order""" edi_struct = dict(edi_struct or PURCHASE_ORDER_EDI_STRUCT) @@ -109,7 +94,7 @@ class purchase_order(osv.osv, EDIMixin): res_partner_obj = self.pool.get('res.partner') # imported company_address = new partner address - src_company_id, src_company_name = edi_document.pop('company_id') + _, src_company_name = edi_document.pop('company_id') address_info = edi_document.pop('company_address') address_info['customer'] = True if 'name' not in address_info: diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml index daad4467929..02e7a1c1ae1 100644 --- a/addons/purchase/edi/purchase_order_action_data.xml +++ b/addons/purchase/edi/purchase_order_action_data.xml @@ -1,18 +1,6 @@ - - - Email Templates @@ -25,19 +13,12 @@ - - - - - Automated Purchase Order Notification Mail + Purchase Order - Send by mail ${object.validator.email or ''} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) ${object.partner_id.email} @@ -64,12 +45,6 @@   Your contact: ${object.validator.name}

    -

    - You can view the ${object.state in ('draft', 'sent') and 'request for quotation' or 'order confirmation'} document and download it using the following link: -

    - View Order -

    If you have any question, do not hesitate to contact us.

    Thank you!

    diff --git a/addons/purchase/test/process/edi_purchase_order.yml b/addons/purchase/test/process/edi_purchase_order.yml index d0e90f3788d..79f244a283d 100644 --- a/addons/purchase/test/process/edi_purchase_order.yml +++ b/addons/purchase/test/process/edi_purchase_order.yml @@ -27,16 +27,16 @@ - Then I export the purchase order via EDI - - !python {model: edi.document}: | - order_pool = self.pool.get('purchase.order') - order = order_pool.browse(cr, uid, ref("purchase_order_edi_1")) - token = self.export_edi(cr, uid, [order]) - assert token, 'Invalid EDI Token' - + !python {model: edi.edi}: | + import json + order_pool = self.pool.get('purchase.order') + order = order_pool.browse(cr, uid, ref("purchase_order_edi_1")) + edi_doc = self.generate_edi(cr, uid, [order]) + assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict' - Then I import a sample EDI document of a sale order - - !python {model: edi.document}: | + !python {model: edi.edi}: | purchase_order_pool = self.pool.get('purchase.order') edi_document = { "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test", diff --git a/addons/sale/edi/sale_order.py b/addons/sale/edi/sale_order.py index edec15a86ae..0ca1b55898c 100644 --- a/addons/sale/edi/sale_order.py +++ b/addons/sale/edi/sale_order.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,13 +19,8 @@ # ############################################################################## -from datetime import datetime, timedelta -from dateutil.relativedelta import relativedelta - -from osv import fields, osv, orm +from openerp.osv import osv from edi import EDIMixin -from edi.models import edi -from tools import DEFAULT_SERVER_DATE_FORMAT SALE_ORDER_LINE_EDI_STRUCT = { 'sequence': True, @@ -65,15 +60,6 @@ SALE_ORDER_EDI_STRUCT = { class sale_order(osv.osv, EDIMixin): _inherit = 'sale.order' - def action_quotation_send(self, cr, uid, ids, context=None): - if context is None: - context = {} - sale_objs = self.browse(cr, uid, ids, context=context) - edi_token = self.pool.get('edi.document').export_edi(cr, uid, sale_objs, context = context)[0] - web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') - ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token)) - return super(sale_order, self).action_quotation_send(cr, uid, ids, context=ctx) - def edi_export(self, cr, uid, records, edi_struct=None, context=None): """Exports a Sale order""" edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT) @@ -112,7 +98,7 @@ class sale_order(osv.osv, EDIMixin): res_partner_obj = self.pool.get('res.partner') # imported company_address = new partner address - src_company_id, src_company_name = edi_document.pop('company_id') + _, src_company_name = edi_document.pop('company_id') address_info = edi_document.pop('company_address') address_info['supplier'] = True @@ -171,7 +157,6 @@ class sale_order(osv.osv, EDIMixin): currency_id = res_currency.edi_import(cr, uid, currency_info, context=context) order_currency = res_currency.browse(cr, uid, currency_id) - date_order = edi_document['date_order'] partner_ref = edi_document.pop('partner_ref', False) edi_document['client_order_ref'] = edi_document['name'] edi_document['name'] = partner_ref or edi_document['name'] @@ -185,7 +170,7 @@ class sale_order(osv.osv, EDIMixin): order_lines = edi_document['order_line'] for order_line in order_lines: - self._edi_requires_attributes(( 'product_id', 'product_uom', 'product_qty', 'price_unit'), order_line) + self._edi_requires_attributes(('product_id', 'product_uom', 'product_qty', 'price_unit'), order_line) order_line['product_uom_qty'] = order_line['product_qty'] del order_line['product_qty'] diff --git a/addons/sale/edi/sale_order_action_data.xml b/addons/sale/edi/sale_order_action_data.xml index 9796d0bda16..5ce35bf497d 100644 --- a/addons/sale/edi/sale_order_action_data.xml +++ b/addons/sale/edi/sale_order_action_data.xml @@ -1,7 +1,6 @@ - Email Templates @@ -15,14 +14,13 @@ - - Automated Sale Order Notification Mail + Sale Order - Send by mail ${object.user_id.email or ''} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) ${object.partner_invoice_id.email} @@ -49,12 +47,6 @@   Your contact: ${object.user_id.name}

    -

    - You can view the ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} document, download it and pay online using the following link: -

    - View Order - % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'): <% comp_name = quote(object.company_id.name) diff --git a/addons/sale/test/edi_sale_order.yml b/addons/sale/test/edi_sale_order.yml index 967aaa1337c..65a5887fe40 100644 --- a/addons/sale/test/edi_sale_order.yml +++ b/addons/sale/test/edi_sale_order.yml @@ -25,15 +25,16 @@ - Then I export the sale order via EDI - - !python {model: edi.document}: | + !python {model: edi.edi}: | + import json sale_order = self.pool.get('sale.order') so = sale_order.browse(cr, uid, ref("sale_order_edi_1")) - token = self.export_edi(cr, uid, [so]) - assert token, 'Invalid EDI Token' + edi_doc = self.generate_edi(cr, uid, [so]) + assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict' - Then I import a sample EDI document of a purchase order - - !python {model: edi.document}: | + !python {model: edi.edi}: | sale_order_pool = self.pool.get('sale.order') edi_document = { "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test", From c7757baf7768dee5699824f0ed9110c73c917288 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 16 Oct 2012 14:29:13 +0200 Subject: [PATCH 004/153] [IMP] edi: ported web part to 7.0 + removed the EDI preview- and export-related parts As started in previous commits, in 7.0 there is no more static storage of exported EDI documents, and they are instead replaced by the improved portal - which will provide the preview as well as export capabilities. (The export might be added in a second phase) bzr revid: odo@openerp.com-20121016122913-ndl0l36buhku3kxy --- addons/account/edi/invoice.py | 10 +- addons/account/test/test_edi_invoice.yml | 145 ++++++++++-- addons/auth_signup/controllers/main.py | 1 - addons/edi/__init__.py | 13 +- addons/edi/controllers/main.py | 19 +- addons/edi/models/edi.py | 4 +- addons/edi/models/res_partner.py | 1 - addons/edi/static/src/css/edi.css | 210 ------------------ addons/edi/static/src/img/pdf.png | Bin 6960 -> 0 bytes addons/edi/static/src/js/edi.js | 137 ++---------- addons/edi/static/src/xml/edi.xml | 93 -------- addons/edi/static/src/xml/edi_account.xml | 163 -------------- .../edi/static/src/xml/edi_sale_purchase.xml | 169 -------------- 13 files changed, 163 insertions(+), 802 deletions(-) delete mode 100644 addons/edi/static/src/css/edi.css delete mode 100644 addons/edi/static/src/img/pdf.png delete mode 100644 addons/edi/static/src/xml/edi.xml delete mode 100644 addons/edi/static/src/xml/edi_account.xml delete mode 100644 addons/edi/static/src/xml/edi_sale_purchase.xml diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py index e84bec6614d..91747de4ea1 100644 --- a/addons/account/edi/invoice.py +++ b/addons/account/edi/invoice.py @@ -19,9 +19,8 @@ # ############################################################################## -from osv import fields, osv, orm +from openerp.osv import osv from edi import EDIMixin -from edi.models import edi INVOICE_LINE_EDI_STRUCT = { 'name': True, @@ -137,9 +136,14 @@ class account_invoice(osv.osv, EDIMixin): # imported company_address = new partner address address_info = edi_document.pop('company_address') + if '__import_model' not in address_info and '__model' not in address_info: + # for pre-7.0 EDI format - address used to be a record of res.partner.address + address_info['__import_model'] = 'res.partner' if 'name' not in address_info: + # for pre-7.0 EDI format - address name was not required address_info['name'] = src_company_name address_info['type'] = 'invoice' + address_info['is_company'] = True address_info.update(partner_value) address_id = res_partner.edi_import(cr, uid, address_info, context=context) @@ -190,7 +194,7 @@ class account_invoice(osv.osv, EDIMixin): invoice_type = invoice_type.startswith('in_') and invoice_type.replace('in_','out_') or invoice_type.replace('out_','in_') edi_document['type'] = invoice_type - #import company as a new partner + # import company as a new partner partner_id = self._edi_import_company(cr, uid, edi_document, context=context) # Set Account diff --git a/addons/account/test/test_edi_invoice.yml b/addons/account/test/test_edi_invoice.yml index 3d57f8dc608..fea829e4ba3 100644 --- a/addons/account/test/test_edi_invoice.yml +++ b/addons/account/test/test_edi_invoice.yml @@ -45,39 +45,39 @@ edi_doc = self.generate_edi(cr, uid, [invoice]) assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict' - - Then I import a sample EDI document of another customer invoice + Then I import a sample EDI document of another customer invoice from OpenERP 7.0 - !python {model: account.invoice}: | import time edi_document = { - "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms", + "__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.random_invoice_763jsms", "__module": "account", "__model": "account.invoice", - "__version": [6,1,0], - "internal_number": time.strftime("SAJ/%Y/002"), + "__version": [7,0,0], + "internal_number": time.strftime("SAJ/%Y/070"), "company_address": { - "__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.main_address", + "__id": "base:b33adf8a-decd-11f0-a4de-702a04e25700.main_address", "__module": "base", "__model": "res.partner", "city": "Gerompont", "name": "Company main address", "zip": "1367", - "country_id": ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.be", "Belgium"], + "country_id": ["base:b33adf8a-decd-11f0-a4de-702a04e25700.be", "Belgium"], "phone": "(+32).81.81.37.00", "street": "Chaussee de Namur 40", "bank_ids": [ - ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 123465789-156113"] + ["base:b33adf8a-decd-11f0-a4de-702a04e25700.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 70-123465789-156113"] ], }, - "company_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_company_test11", "Thomson pvt. ltd."], + "company_id": ["account:b33adf8a-decd-11f0-a4de-702a04e25700.res_company_test11", "Thomson pvt. ltd."], "currency": { - "__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.EUR", + "__id": "base:b33adf8a-decd-11f0-a4de-702a04e25700.EUR", "__module": "base", "__model": "res.currency", "code": "EUR", "symbol": "€", }, - "partner_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_test20", "Junjun wala"], + "partner_id": ["account:b33adf8a-decd-11f0-a4de-702a04e25700.res_partner_test20", "Junjun wala"], "partner_address": { "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh", "__module": "base", @@ -92,7 +92,7 @@ "date_invoice": time.strftime('%Y-%m-%d'), "name": "sample invoice", "tax_line": [{ - "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_tax-4g4EutbiEMVl", + "__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_tax-4g4EutbiEMVl", "__module": "account", "__model": "account.invoice.tax", "amount": 1000.0, @@ -103,21 +103,21 @@ "invoice_line": [{ "__module": "account", "__model": "account.invoice.line", - "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so", - "uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"], + "__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_line-1RP3so", + "uos_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_uom_unit", "Unit"], "name": "PC Assemble SC234", "price_unit": 10.0, - "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"], + "product_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_product_3", "[PCSC234] PC Assemble SC234"], "quantity": 1.0 }, { "__module": "account", "__model": "account.invoice.line", - "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5", - "uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"], + "__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_line-u2XV5", + "uos_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_uom_unit", "Unit"], "name": "PC on Demand", "price_unit": 100.0, - "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"], + "product_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_product_5", "[PC-DEM] PC on Demand"], "quantity": 5.0 }] } @@ -128,10 +128,10 @@ # check bank info on partner assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" bank_info = invoice_new.partner_id.bank_ids[0] - assert bank_info.acc_number == "Sample bank: 123465789-156113", 'Expected "Sample bank: 123465789-156113", got %s' % bank_info.acc_number + assert bank_info.acc_number == "Sample bank: 70-123465789-156113", 'Expected "Sample bank: 70-123465789-156113", got %s' % bank_info.acc_number assert invoice_new.partner_id.supplier, 'Imported Partner is not marked as supplier' - assert invoice_new.reference == time.strftime("SAJ/%Y/002"), "internal number is not stored in reference" + assert invoice_new.reference == time.strftime("SAJ/%Y/070"), "internal number is not stored in reference" assert invoice_new.reference_type == 'none', "reference type is not set to 'none'" assert invoice_new.internal_number == False, "internal number is not reset" assert invoice_new.journal_id.id, "journal id is not selected" @@ -153,3 +153,110 @@ for inv_tax in invoice_new.tax_line: assert inv_tax.manual, "tax line not set to manual" assert inv_tax.account_id, "missing tax line account" +- + Then I import a sample EDI document of another customer invoice from OpenERP 6.1 (to test backwards compatibility) +- + !python {model: account.invoice}: | + import time + edi_document = { + "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms", + "__module": "account", + "__model": "account.invoice", + "__version": [6,1,0], + "internal_number": time.strftime("SAJ/%Y/061"), + "company_address": { + "__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.main_address", + "__module": "base", + "__model": "res.partner.address", + "city": "Gerompont", + "zip": "1367", + "country_id": ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.be", "Belgium"], + "phone": "(+32).81.81.37.00", + "street": "Chaussee de Namur 40", + "bank_ids": [ + ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 123465789-156113"] + ], + }, + "company_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_company_test11", "Thomson pvt. ltd."], + "currency": { + "__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.EUR", + "__module": "base", + "__model": "res.currency", + "code": "EUR", + "symbol": "€", + }, + "partner_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_test20", "Junjun wala"], + "partner_address": { + "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh", + "__module": "base", + "__model": "res.partner.address", + "phone": "(+32).81.81.37.00", + "street": "Chaussee de Namur 40", + "city": "Gerompont", + "zip": "1367", + "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"], + }, + "date_invoice": time.strftime('%Y-%m-%d'), + "name": "sample invoice", + "tax_line": [{ + "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_tax-4g4EutbiEMVl", + "__module": "account", + "__model": "account.invoice.tax", + "amount": 1000.0, + "manual": True, + "name": "sale tax", + }], + "type": "out_invoice", + "invoice_line": [{ + "__module": "account", + "__model": "account.invoice.line", + "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so", + "uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "PCE"], + "name": "Basic PC", + "price_unit": 10.0, + "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_pc1", "[PC1] Basic PC"], + "quantity": 1.0 + }, + { + "__module": "account", + "__model": "account.invoice.line", + "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5", + "uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "PCE"], + "name": "Medium PC", + "price_unit": 100.0, + "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_pc3", "[PC3] Medium PC"], + "quantity": 5.0 + }] + } + invoice_id = self.edi_import(cr, uid, edi_document, context=context) + assert invoice_id, 'EDI import failed' + invoice_new = self.browse(cr, uid, invoice_id) + + # check bank info on partner + assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" + bank_info = invoice_new.partner_id.bank_ids[0] + assert bank_info.acc_number == "Sample bank: 123465789-156113", 'Expected "Sample bank: 123465789-156113", got %s' % bank_info.acc_number + + assert invoice_new.partner_id.supplier, 'Imported Partner is not marked as supplier' + assert invoice_new.reference == time.strftime("SAJ/%Y/061"), "internal number is not stored in reference" + assert invoice_new.reference_type == 'none', "reference type is not set to 'none'" + assert invoice_new.internal_number == False, "internal number is not reset" + assert invoice_new.journal_id.id, "journal id is not selected" + assert invoice_new.type == 'in_invoice', "Invoice type was not set properly" + assert len(invoice_new.invoice_line) == 2, "invoice lines are not same" + for inv_line in invoice_new.invoice_line: + if inv_line.name == 'Basic PC': + assert inv_line.uos_id.name == "Unit" , "uom is not same" + assert inv_line.price_unit == 10 , "price unit is not same" + assert inv_line.quantity == 1 , "product qty is not same" + assert inv_line.price_subtotal == 10, "price sub total is not same" + elif inv_line.name == 'Medium PC': + assert inv_line.uos_id.name == "Unit" , "uom is not same" + assert inv_line.price_unit == 100 , "price unit is not same" + assert inv_line.quantity == 5 , "product qty is not same" + assert inv_line.price_subtotal == 500, "price sub total is not same" + else: + raise AssertionError('unknown invoice line: %s' % inv_line) + for inv_tax in invoice_new.tax_line: + assert inv_tax.manual, "tax line not set to manual" + assert inv_tax.account_id, "missing tax line account" diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 54dcc5b4362..65a05255f97 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -35,7 +35,6 @@ class Controller(openerp.addons.web.http.Controller): def retrieve(self, req, dbname, token): """ retrieve the user info (name, login or email) corresponding to a signup token """ registry = RegistryManager.get(dbname) - user_info = None with registry.cursor() as cr: res_partner = registry.get('res.partner') user_info = res_partner.signup_retrieve_info(cr, openerp.SUPERUSER_ID, token) diff --git a/addons/edi/__init__.py b/addons/edi/__init__.py index 71ee8aaa53d..b1563ed72cd 100644 --- a/addons/edi/__init__.py +++ b/addons/edi/__init__.py @@ -20,18 +20,9 @@ ############################################################################## import logging +from . import controllers from . import models from . import edi_service -from models.edi import EDIMixin, edi -_logger = logging.getLogger(__name__) - -# web -try: - import openerp.addons.web.controllers -except ImportError: - _logger.warn( - """Could not load openerp-web section of EDI, EDI will not behave correctly - -To fix, launch openerp-web in embedded mode""") +from .models.edi import EDIMixin, edi # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/edi/controllers/main.py b/addons/edi/controllers/main.py index 0322099b6f3..14cd97b3e62 100644 --- a/addons/edi/controllers/main.py +++ b/addons/edi/controllers/main.py @@ -1,4 +1,6 @@ -import openerp.addons.web.common.http as openerpweb +import simplejson + +import openerp.addons.web.http as openerpweb import openerp.addons.web.controllers.main as webmain class EDI(openerpweb.Controller): @@ -7,10 +9,17 @@ class EDI(openerpweb.Controller): @openerpweb.httprequest def import_url(self, req, url): - d = self.template(req) - d["init"] = 's.edi.edi_import("%s");'%(url) - r = webmain.html_template % d - return r + modules = webmain.module_boot(req) + ['edi'] + modules_str = ','.join(modules) + modules_json = simplejson.dumps(modules) + js = "\n ".join('' % i for i in webmain.manifest_list(req, modules_str, 'js')) + css = "\n ".join('' % i for i in webmain.manifest_list(req, modules_str, 'css')) + return webmain.html_template % { + 'js': js, + 'css': css, + 'modules': modules_json, + 'init': 's.edi.edi_import("%s");' % url, + } @openerpweb.jsonrequest def import_edi_url(self, req, url): diff --git a/addons/edi/models/edi.py b/addons/edi/models/edi.py index 29e19f2da3b..7c6fcc3ddfe 100644 --- a/addons/edi/models/edi.py +++ b/addons/edi/models/edi.py @@ -24,14 +24,12 @@ import hashlib import json import logging import re -import threading import time import urllib2 import openerp import openerp.release as release import openerp.netsvc as netsvc -from openerp.modules.registry import RegistryManager from openerp.osv import osv, fields from tools.translate import _ from tools.safe_eval import safe_eval as eval @@ -540,7 +538,7 @@ class EDIMixin(object): assert self._name == edi.get('__import_model') or \ ('__import_model' not in edi and self._name == edi.get('__model')), \ "EDI Document Model and current model do not match: '%s' (EDI) vs '%s' (current)." % \ - (edi['__model'], self._name) + (edi.get('__model'), self._name) # First check the record is now already known in the database, in which case it is ignored ext_id_members = split_external_id(edi['__id']) diff --git a/addons/edi/models/res_partner.py b/addons/edi/models/res_partner.py index 37483c1d38d..057e84b5541 100644 --- a/addons/edi/models/res_partner.py +++ b/addons/edi/models/res_partner.py @@ -23,7 +23,6 @@ import logging from openerp.osv import osv from edi import EDIMixin from openerp import SUPERUSER_ID -from openerp.tools.translate import _ _logger = logging.getLogger(__name__) RES_PARTNER_EDI_STRUCT = { diff --git a/addons/edi/static/src/css/edi.css b/addons/edi/static/src/css/edi.css deleted file mode 100644 index 6385959c036..00000000000 --- a/addons/edi/static/src/css/edi.css +++ /dev/null @@ -1,210 +0,0 @@ -/** EDI content **/ -.openerp .company_logo { - background-size: 180px 46px; -} -.oe_edi_view { - width: 65%; - vertical-align: top; - padding: 0px 25px; - border-right: 1px solid #D2CFCF; -} -.oe_edi_sidebar_container { - width: 35%; - padding: 0px 10px; - vertical-align: top; -} -button.oe_edi_action_print { - font-size: 1.5em; - margin-left: 35%; - margin-bottom: 20px; -} -button.oe_edi_action_print img { - vertical-align: bottom; - width: 32px; - height: 32px; -} - -/** EDI Sidebar **/ -.oe_edi_sidebar_title { - border-bottom: 1px solid #D2CFCF; - font-weight: bold; - font-size: 1.3em; - min-width: 10em; -} -.oe_edi_nested_block, .oe_edi_nested_block_import, .oe_edi_nested_block_pay { - margin: 0px 40px; - min-width: 10em; - display: none; /* made visible by click on parent input/label */ -} -.oe_edi_right_top .oe_edi_nested_block label { - float: left; - text-align: right; - margin-right: 0.5em; - line-height: 180%; - font-weight: bold; - min-width: 5em; -} -.oe_edi_option { - padding-left: 5px; - line-height: 2em; -} -.oe_edi_option:hover { - background: #e8e8e8; -} -.oe_edi_import_button { - margin: 2px 10px; - white-space: nowrap; -} -.oe_edi_small, .oe_edi_small input { - font-size: 90%; -} - -/** Sidebar bottom **/ -.oe_edi_paypal_button { - margin: 6px; -} - - -/** Paperbox, from http://www.sitepoint.com/pure-css3-paper-curl/ **/ -.oe_edi_paperbox { - position: relative; - width: 700px; - padding: 30px; - padding-bottom: 50px; - margin: 20px auto; - background-color: #fff; - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), inset 0 0 50px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), inset 0 0 50px rgba(0, 0, 0, 0.1); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.2), inset 0 0 50px rgba(0, 0, 0, 0.1); -} -.oe_edi_paperbox:before, .oe_edi_paperbox:after { - position: absolute; - width: 40%; - height: 10px; - content: ' '; - left: 12px; - bottom: 15px; - background: transparent; - -webkit-transform: skew(-5deg) rotate(-5deg); - -moz-transform: skew(-5deg) rotate(-5deg); - -ms-transform: skew(-5deg) rotate(-5deg); - -o-transform: skew(-5deg) rotate(-5deg); - transform: skew(-5deg) rotate(-5deg); - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); - z-index: -1; -} -.oe_edi_paperbox:after { - left: auto; right: 12px; - -webkit-transform: skew(5deg) rotate(5deg); - -moz-transform: skew(5deg) rotate(5deg); - -ms-transform: skew(5deg) rotate(5deg); - -o-transform: skew(5deg) rotate(5deg); - transform: skew(5deg) rotate(5deg); -} - -/** Sale Order / Purchase Order Preview **/ -table.oe_edi_data, .oe_edi_doc_title { - border-collapse: collapse; - clear: both; -} -.oe_edi_data th { - white-space: nowrap; -} -.oe_edi_data .oe_edi_floor { - border-bottom: 1px solid black; -} -.oe_edi_data .oe_edi_ceiling { - border-top: 1px solid black; -} -.oe_edi_data .oe_edi_data_row { - border-bottom: 1px solid #D2CFCF; -} -.oe_edi_data_row td { - vertical-align: top; -} -.oe_edi_inner_note { - font-style: italic; - font-size: 95%; - padding-left: 10px; - - /* prevent wide notes from disrupting layout due to
     styling */
    -    white-space: pre-line;
    -    width: 90%;
    -}
    -.oe_edi_data_row .oe_edi_inner_note {
    -    /* prevent wide notes from disrupting layout due to 
     styling */
    -    width: 25em;
    -}
    -.oe_edi_shade {
    -    background: #e8e8e8;
    -}
    -.oe_edi_company_name {
    -    text-transform: uppercase;
    -    font-weight: bold;
    -}
    -.oe_edi_address_from {
    -    float: left;
    -}
    -.oe_edi_address_to {
    -    float: right;
    -    margin-top: 25px;
    -    margin-bottom: 30px;
    -}
    -.oe_edi_company_block_title {
    -    width: 375px;
    -    margin: 0px;
    -    padding: 2px 14px;
    -    background-color: #252525;
    -    border-top-left-radius: 5px 5px;
    -    border-top-right-radius: 5px 5px;
    -    background-repeat: repeat no-repeat; 
    -}
    -.oe_edi_company_block_title .oe_edi_company_name {
    -    margin: 0px;
    -    font-size: 1em;
    -    color: #FFF;
    -}
    -.oe_edi_company_block_body {
    -    width: 375px;
    -    margin: 0px;
    -    padding: 5px 14px;
    -    line-height: 16px;
    -    background-color: rgb(242, 242, 242);
    -}
    -.oe_edi_company_block_body p {
    -    color: #222;
    -    margin: 5px 0px;
    -}
    -.oe_edi_summary_label {
    -    float: left;
    -}
    -.oe_edi_summary_value {
    -    float: right;
    -}
    -
    -
    -/** Python code highlighting **/
    -/*  GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann
    -    (http://qbnz.com/highlighter/ and http://geshi.org/) */
    -.python .de1, .python .de2 {font: normal normal 1em/1.2em monospace; margin:0; padding:0; background:none; vertical-align:top;}
    -.python  {font-family:monospace;}
    -.python .imp {font-weight: bold; color: red;}
    -.python li, .python .li1 {background: #ffffff; list-style: none;}
    -.python .ln {width:1px;text-align:right;margin:0;padding:0 2px;vertical-align:top;}
    -.python .li2 {background: #f8f8f8;}
    -.python .kw1 {color: #ff7700;font-weight:bold;}
    -.python .kw2 {color: #008000;}
    -.python .kw3 {color: #dc143c;}
    -.python .kw4 {color: #0000cd;}
    -.python .co1 {color: #808080; font-style: italic;}
    -.python .coMULTI {color: #808080; font-style: italic;}
    -.python .es0 {color: #000099; font-weight: bold;}
    -.python .br0 {color: black;}
    -.python .sy0 {color: #66cc66;}
    -.python .st0 {color: #483d8b;}
    -.python .nu0 {color: #ff4500;}
    -.python .me1 {color: black;}
    -.python span.xtra { display:block; }
    -.python ol { padding: 0px; }
    diff --git a/addons/edi/static/src/img/pdf.png b/addons/edi/static/src/img/pdf.png
    deleted file mode 100644
    index d35dfd20c2db2622560c7f04dc174b52f223a753..0000000000000000000000000000000000000000
    GIT binary patch
    literal 0
    HcmV?d00001
    
    literal 6960
    zcmV-08_(p4P)Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz-
    zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8
    z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc-
    z5#WRK{dmp}uFlRjj{U%*%WZ25jX
    z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq
    zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S
    z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG
    z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU
    zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3
    zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q
    zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF
    zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}*
    z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C
    z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C
    zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c
    z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{|
    zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP
    zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By
    zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2
    zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd
    zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im
    z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x
    zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote
    z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA
    zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti
    zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B
    zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o
    z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0
    z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ?
    z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd
    z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P`
    z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60
    z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RW0Spcx60bO@qyPXAVo5|n
    zRA}DCnR|3s)s@FT=l))KCqPJIAb>o?mRhP{Ddjbx9Z*|J%1|q6@l~g*7{oQMQr8-n
    zQ>L`g+7>PK!9y%lkvhI81*ru@421#_Mjb2yNq7W8LXsbO{2upr?>T$^_&xk3kRUjn
    z*=wD3?>+b2d-gtipYMJgMS@nXS~VQN@AsR#?z*cM!1~9WoLkO*@ryFOwe{$iTU(C?
    z@436?(4j*Dpnw37|Iv{njQ~n1a(Xx9eC^$LnLK&2>g?b%vP=Ga2#yQ!!^%73y}yJ9uHXp*&;+D
    z5f&|4BukboNoguY;c!@7*G)Y~s}GPd_J%G?nU|O6?edYW3)knP?7jEN9Y3CoK7E)~Ur(mi76=MVlP|WtT+4py1H{l>v*S9sKfx3fJXqMHL+NX
    za4^WEi4*%~Wn~4!;qV2ssOdR5w`?3YZnO4SxUR+yhbi2%2dBQCy1VaY(5h7g1`noc
    z>{uorI6&m=Szaz1g!cQe9S2)$`u*lN3>q~``uFdzKKt3v_EAcmPv2pdBSNQb6OP(M
    zVlkpl41n5-Z(_H$()jz|vw!MTif7LzeEKw-KL2@CMg~R%A#R!hzaOQP%FWHCt*x!~
    z@ZrM(^i4@JH7l$9iQ?ivYZ1``j%cDrh$-cZ8bid@gpDSmHDMjUMvcKvG)EgM*1yQ`
    z>#rvm43dN*+qOA;`0%se|Ni&qdEW|$cYMCC&ev@j^Ex{_+qBjf)#SOQzog(hbMOr)
    z?b2UVRFsZLXst5=%iEJlc)i|~g1b}JwIlHN>4V4X?bWQ3`_bxcz(t#75!t?zlhbD4
    z@%wuomuLVzvSL}*1<(#~`kyNqyml4FaR|Tj7u@D%^8Tp;jOkI5UMJ~Jr|!6i;zu9B
    zIa-7AddQnP<>OeOxrL55{zRx+oU`Tu-HbTlfBKlxd*OG2TL
    z*tRVqB0G2PO!Yta+;fsn@4ox)AG-%0-6v@iV{lHK#DDb=5P<||Km6ejS+{N-lP6E+
    zGoSekwY9Yr6%|okUCsFMZ1XW_zyZ;Tl;W=2nv^GT$tEJYy1IH*
    zkCNPe`|Vlf<>jTM;gr@|DW$}+EVS0j7=uzu0)as2o_p?T1Wb?HAz{XWbUbUw$*CAy
    zUcN2~hUws#1WQD4VlkXpjH;@t9g`+a`f5)TDSPy2W98?om@&k(CSu2k+EF56h(v8d
    zu^15%!m$`3+s4)!TZE|A#I)v2zkYmW#R|#F%Gxk(+O%&07r=zq0p-ich}*g2l6&i|
    z^qDXL|8>`4LLoXo{1E?u0W`k(CM6XW)c)>wRR8$L__V2$ERNKp;SV
    zem-Nzj-9+|)24OZk;IB>P0V$1T$h;ZqKy!#tK-A#uSa>kxUNf|2@?RQS+j=V-#)}3
    z7!i>8L_VL7rluye)=Zo@@wTm7x4s3W0ts(KYb@8rh{4qc`RFiqQxlHkaPE~?82Q8#
    z?3yrvj1eQSgF%coh)GP4kSGuc@b=qp)6md>ZQIzkJ>{{-9$U72`SOQ?!JttGhYYcm
    zQc8>9ir^SS#2Cv~$`jR^h&F_^OGIlT#(eBgv?eA(Mo9^kZ@*1vXD6XhC~59QA`zxd
    zo8|``1r&6P*{$2}yz`s?_Sj=jU)1Owhv=cdk$K|{;JEnxezLQ(F~(qwNr9>{hJgbI
    z<^dgE;Cy$p9&puF9VNvV4?=W%2aNv`o`O6u(j)jKHnHl_pD3kzY@UFh=8`@!GD#_gm6>@-Uy=Ua+nXd8BNK^Iy5!%{jZOGU
    zic|34o71~&owO|bHTg5XM$^Ck2+P~EUaoi&kKd1Ubb{+r_8+S`@%34F;(^@-tvl3T
    za!nH*oVx3N%GRy|tueMu*$Zo^oA!0A+#KBcGvM)%Jz+e>KV8&Ck{)$(K_FpSjYQ-mJ#pmLnW-{op;_zy|1dO!m=!ynwqdI
    z3k7&`b5eX!8KAljoOix)5cR&Oy^@+u-1T(#EmR40!4*%wqf(z62~ub20qgj^s9
    zxZQuEdRRII_?ItV{*~FYXK%h(wEsyc{kx=-i^XDuLZSASmX_ZxS+ZpDwr$&5N=r-4
    znKNfD54ach@Bg_Umy{f|z%nADfw;Rj36~#t{V`*3j3`H8#qSL<5gZYcQB^E8&cv@G
    z@jk8N|4M`qu9(Ce;2J|lX{n6+(T`MaX(C?zxRYiNSIlGDeQXH)#@NW#P8B)Ar_@M2;Mx;Lbbo6c=Me2p>OAZY2Snhm$y
    za?5vtU}0gQX>M-rT@V8*W(-PeL_jcT6AurK(dbx=jL&_JoVjzUuc*N4*N;K#*3s~j
    zpHO)B-88IRN&U)|1V)bJOS^YdvuqhwMh3+*X0Y?7n`l3ABC)M~adU!?s|@LO8wik-
    zlS3pDVfgUj^zGYs=AlD}DvlpNzUASEAHKJ_x%pf?HoUl5TRNEnxGs)%iD`qYHLlh;
    ziF;!VVhjV;ucvs~GMd+{p>^X%l;4k77Wn-{&YU5*dpG$v+<+KEW@#xmu3X7KY~D=K
    z)T!uLEWSsKArV5Ss(F$?e4o#kigF4I3)MB(TqEPgjl21^*IsMbvSrIg5y=Ov?Ck7|
    zXe1$s<+`c(Ic{C?&FE@PAmO*_?!1HWks}x^wAX`(kT+=(-l8IkZ@rc1*|Y4es9?wg
    z4-jo?V&4M~(0=?l+8FS95o6M{@v%k#Jc&YYXJ;o*J@pjz_4PQ8qa4S<@Ap$yR(A7-
    z4I9q8uKUX4k3T+t@7}#l=_!z!oRxX=iU9-O$jr`(uWkZeOsW~Q0ga%w#x(|QO#EtD
    zXozPukqF70z%_B8a9uYwHu;?viMQmvF`O+f=Bq#dIU{epkytE7Fc>5h3gI|Te4mLV
    zQBI=KsER})95`^`rq!!gZ%YD#@<3URQlqp|g~_x?L{KEswKym%fsAWMjrv06f(74m
    z9Vabh6fQ7ZrRd)22G3){VLW-ccoIco0I$bG|Dqy>4IfUQzI_vEiG%C9m~MrdL|qGG
    z3?F>(!Si$G%&AOxMB~+Ps!gPx-h4w`uH30N-PHN5+iv?_Perz#$aHqnvf?@V-gh4=
    zI}0li=+)13-_YsLB7$XEoIZWpN`!JMJ*N949!`=W)@3_H%R)IW#A4V-j*>HZB9eB*
    za=DZQhD*kcm-@-&HcwU-p4=Q-fBiJRqW-D)^V75o_`2Nu?Mj;E_mfvX1^c}Z$Q*eM
    zO5}?Fb+OFqiUhf$6;ulQzyF45X`%V2kK@V8`qUcfo|ygpHDXz~^{450Z3Dxrwi6gO
    zl=?Y$VflRje;P^dkxx9C*NbUwTxl-aZBNx#QcEEP9la-#igF%i|yc7N1HZJ)Lr-
    zk7@ngi?nZigTXJX0k0QC2n-u~$py$BbA8vkgEoj5GKLK$vU3-f*Gio@{;z1HJLBPl
    z?;*C0f7npYJg|U*Ip1c;%P(|soaqx^tiOAo@_3L$Jp&Q?+&ee`|rO`
    zd3kx)P2TPN*Rm}Cr$VhHgU-oQoL~16mfz3WAN(sSBZIORpU3J`aDm#j*6i4^gGU~D
    zgvE;&12BL7d=@QQ#P;pmF~%@&-aJ;USi#JhGpVhurLC=v<;#~defo3`9z4j*nKN0r
    zawV0Om9(|Bp|!RFNroy{Y+AlSWej-c$x8x(h|u2N&a1D!N>)}DCr+GT{rdH^x3{x&
    z=~6N?Gug9e4@E^q+<*W5G&VM}b?a6N3k#V)e?H-Gm|r~p3mO|6X>4p{(4aviPPG)E
    zk^$`HnwIIU+|rZw_U+rpefQnRjvYH#vt|uPj~->#tXa&PHxHlB$E;bim_B_v_uhLi
    znVFeXR#q})$`l@a@IgYM5YPYic^-P`A!=%B$jHnjGhXIa$!g5y@I&ILt^_Cp^3#uW
    z{*TE3A)r1lFYioydwUp=%Po$nE8SOr2a@zu_kREmz&vfVZpA170000
    -
    -    
    -
    - - - - - - - - - - - - - - -
    -
    - -
    - - - - - -
    -

    - -
    -

    - Import this document -

    -
    - - -
    -

    - -
    -
    - -

    - -
    - - -
    -

    - -

    - -
    - - -
    -

    - OpenERP's Electronic Data Interchange documents are based on a generic and language - independent JSON serialization of the document's attribute. - It is usually very quick and straightforward to create a small plug-in for your preferred - application that will be capable of importing any OpenERP EDI document. - You can find out more details about how to do this and what the content of OpenERP EDI documents - is like in the OpenERP documentation. -
    - To get started immediately, see is all it takes to use this EDI document in Python. -

    -
    -
    1. import urllib2, simplejson
    2. -
    3. edi_document = urllib2.urlopen('').read()
    4. -
    5. document_data = simplejson.loads(edi_document)[0]
    6. -
    7. print "Amount: ", document_data['amount_total']
    8. -
    -

    - You can download the raw EDI document here:
    - - -

    - -
    - -
    -
    -
    - -
    -
    - diff --git a/addons/edi/static/src/xml/edi_account.xml b/addons/edi/static/src/xml/edi_account.xml deleted file mode 100644 index 3ce1daebf93..00000000000 --- a/addons/edi/static/src/xml/edi_account.xml +++ /dev/null @@ -1,163 +0,0 @@ - diff --git a/addons/edi/static/src/xml/edi_sale_purchase.xml b/addons/edi/static/src/xml/edi_sale_purchase.xml deleted file mode 100644 index 8d6bab67761..00000000000 --- a/addons/edi/static/src/xml/edi_sale_purchase.xml +++ /dev/null @@ -1,169 +0,0 @@ - From b42801595e48ab266a60ae6b9e77d73a1adb98fe Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 17 Oct 2012 18:28:55 +0200 Subject: [PATCH 005/153] [IMP] product.uom: support name_create, allows EDI import to work silently bzr revid: odo@openerp.com-20121017162855-dkrr07bldsnc6f5s --- addons/product/product.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/addons/product/product.py b/addons/product/product.py index 6c6c5f2650c..9623a5f1248 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -100,6 +100,23 @@ class product_uom(osv.osv): def _factor_inv_write(self, cursor, user, id, name, value, arg, context=None): return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context) + def name_create(self, cr, uid, name, context=None): + """ The UoM category and factor are required, so we'll have to add temporary values + for imported UoMs """ + uom_categ = self.pool.get('product.uom.categ') + # look for the category based on the english name, i.e. no context on purpose! + # TODO: should find a way to have it translated but not created until actually used + categ_misc = 'Unsorted/Imported Units' + categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)]) + if categ_id: + categ_id = categ_id[0] + else: + categ_id, _ = uom_categ.name_create(cr, uid, categ_misc) + uom_id = self.create(cr, uid, {self._rec_name: name, + 'category_id': categ_id, + 'factor': 1}) + return self.name_get(cr, uid, [uom_id], context=context)[0] + def create(self, cr, uid, data, context=None): if 'factor_inv' in data: if data['factor_inv'] <> 1: From 48cfc1bb029407fce29d2ac31f7be474444da775 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 17 Oct 2012 18:29:37 +0200 Subject: [PATCH 006/153] [IMP] edi,account: fix EDI import of invoices, now compatible again with 6.1, and correct wrt. partners import (vs old addresses) bzr revid: odo@openerp.com-20121017162937-fprd53n4ljo5bdim --- addons/account/edi/invoice.py | 49 +++++++++++------------- addons/account/test/test_edi_invoice.yml | 4 +- addons/edi/models/edi.py | 13 +++++-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py index 91747de4ea1..80a4b145054 100644 --- a/addons/account/edi/invoice.py +++ b/addons/account/edi/invoice.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -100,8 +100,8 @@ class account_invoice(osv.osv, EDIMixin): return tax_account def _edi_invoice_account(self, cr, uid, partner_id, invoice_type, context=None): - partner_pool = self.pool.get('res.partner') - partner = partner_pool.browse(cr, uid, partner_id, context=context) + res_partner = self.pool.get('res.partner') + partner = res_partner.browse(cr, uid, partner_id, context=context) if invoice_type in ('out_invoice', 'out_refund'): invoice_account = partner.property_account_receivable else: @@ -125,36 +125,31 @@ class account_invoice(osv.osv, EDIMixin): self._edi_requires_attributes(('company_id','company_address','type'), edi_document) res_partner = self.pool.get('res.partner') - _, src_company_name = edi_document.pop('company_id') + xid, company_name = edi_document.pop('company_id') + # Retrofit address info into a unified partner info (changed in v7 - used to keep them separate) + company_address_edi = edi_document.pop('company_address') + company_address_edi['name'] = company_name + company_address_edi['is_company'] = True + company_address_edi['__import_model'] = 'res.partner' + company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway + if company_address_edi.get('logo'): + company_address_edi['image'] = company_address_edi.pop('logo') invoice_type = edi_document['type'] - partner_value = {} - if invoice_type in ('out_invoice', 'out_refund'): - partner_value.update({'customer': True}) - if invoice_type in ('in_invoice', 'in_refund'): - partner_value.update({'supplier': True}) - - # imported company_address = new partner address - address_info = edi_document.pop('company_address') - if '__import_model' not in address_info and '__model' not in address_info: - # for pre-7.0 EDI format - address used to be a record of res.partner.address - address_info['__import_model'] = 'res.partner' - if 'name' not in address_info: - # for pre-7.0 EDI format - address name was not required - address_info['name'] = src_company_name - address_info['type'] = 'invoice' - address_info['is_company'] = True - address_info.update(partner_value) - address_id = res_partner.edi_import(cr, uid, address_info, context=context) + if invoice_type.startswith('out_'): + company_address_edi['customer'] = True + else: + company_address_edi['supplier'] = True + partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context) # modify edi_document to refer to new partner - partner_address = res_partner.browse(cr, uid, address_id, context=context) - address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context) - edi_document['partner_id'] = address_edi_m2o - edi_document.pop('partner_address', False) # ignored + partner = res_partner.browse(cr, uid, partner_id, context=context) + partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context) + edi_document['partner_id'] = partner_edi_m2o + edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address! - return address_id + return partner_id def edi_import(self, cr, uid, edi_document, context=None): """ During import, invoices will import the company that is provided in the invoice as diff --git a/addons/account/test/test_edi_invoice.yml b/addons/account/test/test_edi_invoice.yml index fea829e4ba3..5ca3b979f28 100644 --- a/addons/account/test/test_edi_invoice.yml +++ b/addons/account/test/test_edi_invoice.yml @@ -246,12 +246,12 @@ assert len(invoice_new.invoice_line) == 2, "invoice lines are not same" for inv_line in invoice_new.invoice_line: if inv_line.name == 'Basic PC': - assert inv_line.uos_id.name == "Unit" , "uom is not same" + assert inv_line.uos_id.name == "PCE" , "uom is not same" assert inv_line.price_unit == 10 , "price unit is not same" assert inv_line.quantity == 1 , "product qty is not same" assert inv_line.price_subtotal == 10, "price sub total is not same" elif inv_line.name == 'Medium PC': - assert inv_line.uos_id.name == "Unit" , "uom is not same" + assert inv_line.uos_id.name == "PCE" , "uom is not same" assert inv_line.price_unit == 100 , "price unit is not same" assert inv_line.quantity == 5 , "product qty is not same" assert inv_line.price_subtotal == 500, "price sub total is not same" diff --git a/addons/edi/models/edi.py b/addons/edi/models/edi.py index 7c6fcc3ddfe..5edbca11d13 100644 --- a/addons/edi/models/edi.py +++ b/addons/edi/models/edi.py @@ -488,8 +488,10 @@ class EDIMixin(object): if data_ids: model = self.pool.get(model) data = ir_model_data.browse(cr, uid, data_ids[0], context=context) - result = model.browse(cr, uid, data.res_id, context=context) - return result + if model.exists(cr, uid, [data.res_id]): + return model.browse(cr, uid, data.res_id, context=context) + # stale external-id, cleanup to allow re-import, as the corresponding record is gone + ir_model_data.unlink(cr, 1, [data_ids[0]]) def edi_import_relation(self, cr, uid, model, value, external_id, context=None): """Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the @@ -503,6 +505,10 @@ class EDIMixin(object): * If previous steps gave no result, create a new record with the given value in the target model, assign it the given external_id, and return the new database ID + + :param str value: display name of the record to import + :param str external_id: fully-qualified external ID of the record + :return: database id of newly-imported or pre-existing record """ _logger.debug("%s: Importing EDI relationship [%r,%r]", model, external_id, value) target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context) @@ -517,8 +523,7 @@ class EDIMixin(object): self._name, external_id, value) # also need_new_ext_id here, but already been set above model = self.pool.get(model) - # should use name_create() but e.g. res.partner won't allow it at the moment - res_id = model.create(cr, uid, {model._rec_name: value}, context=context) + res_id, _ = model.name_create(cr, uid, value, context=context) target = model.browse(cr, uid, res_id, context=context) if need_new_ext_id: ext_id_members = split_external_id(external_id) From e0fcab1f620affb1a69308cdf215c9a90ab864a6 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 18 Oct 2012 17:21:29 +0200 Subject: [PATCH 007/153] [FIX] edi,sale,purchase: fix EDI import of SO/PO, now compatible again with 6.1, and correct wrt. partners import (vs old addresses) bzr revid: odo@openerp.com-20121018152129-s22uozt77s37urp1 --- addons/account/test/test_edi_invoice.yml | 2 + addons/purchase/edi/purchase_order.py | 32 +++-- .../test/process/edi_purchase_order.yml | 134 +++++++++++++++--- addons/sale/edi/sale_order.py | 38 ++--- addons/sale/test/edi_sale_order.yml | 131 +++++++++++++++-- 5 files changed, 274 insertions(+), 63 deletions(-) diff --git a/addons/account/test/test_edi_invoice.yml b/addons/account/test/test_edi_invoice.yml index 5ca3b979f28..2225ae5ba9b 100644 --- a/addons/account/test/test_edi_invoice.yml +++ b/addons/account/test/test_edi_invoice.yml @@ -126,6 +126,7 @@ invoice_new = self.browse(cr, uid, invoice_id) # check bank info on partner + assert invoice_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a supplier invoice" assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" bank_info = invoice_new.partner_id.bank_ids[0] assert bank_info.acc_number == "Sample bank: 70-123465789-156113", 'Expected "Sample bank: 70-123465789-156113", got %s' % bank_info.acc_number @@ -233,6 +234,7 @@ invoice_new = self.browse(cr, uid, invoice_id) # check bank info on partner + assert invoice_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a supplier invoice" assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" bank_info = invoice_new.partner_id.bank_ids[0] assert bank_info.acc_number == "Sample bank: 123465789-156113", 'Expected "Sample bank: 123465789-156113", got %s' % bank_info.acc_number diff --git a/addons/purchase/edi/purchase_order.py b/addons/purchase/edi/purchase_order.py index f44c16eb0d9..cdb4c0ca73d 100644 --- a/addons/purchase/edi/purchase_order.py +++ b/addons/purchase/edi/purchase_order.py @@ -91,22 +91,26 @@ class purchase_order(osv.osv, EDIMixin): # the desired company among the user's allowed companies self._edi_requires_attributes(('company_id','company_address'), edi_document) - res_partner_obj = self.pool.get('res.partner') + res_partner = self.pool.get('res.partner') - # imported company_address = new partner address - _, src_company_name = edi_document.pop('company_id') - address_info = edi_document.pop('company_address') - address_info['customer'] = True - if 'name' not in address_info: - address_info['name'] = src_company_name - address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context) + xid, company_name = edi_document.pop('company_id') + # Retrofit address info into a unified partner info (changed in v7 - used to keep them separate) + company_address_edi = edi_document.pop('company_address') + company_address_edi['name'] = company_name + company_address_edi['is_company'] = True + company_address_edi['__import_model'] = 'res.partner' + company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway + if company_address_edi.get('logo'): + company_address_edi['image'] = company_address_edi.pop('logo') + company_address_edi['supplier'] = True + partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context) - # modify edi_document to refer to new partner/address - partner_address = res_partner_obj.browse(cr, uid, address_id, context=context) - edi_document.pop('partner_address', False) # ignored - edi_document['partner_id'] = self.edi_m2o(cr, uid, partner_address, context=context) - - return address_id + # modify edi_document to refer to new partner + partner = res_partner.browse(cr, uid, partner_id, context=context) + partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context) + edi_document['partner_id'] = partner_edi_m2o + edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address! + return partner_id def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None): # TODO: refactor into common place for purchase/sale, e.g. into product module diff --git a/addons/purchase/test/process/edi_purchase_order.yml b/addons/purchase/test/process/edi_purchase_order.yml index 79f244a283d..8e34606afb4 100644 --- a/addons/purchase/test/process/edi_purchase_order.yml +++ b/addons/purchase/test/process/edi_purchase_order.yml @@ -34,29 +34,29 @@ edi_doc = self.generate_edi(cr, uid, [order]) assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict' - - Then I import a sample EDI document of a sale order + Then I import a sample EDI document of a sale order (v7.0) - !python {model: edi.edi}: | purchase_order_pool = self.pool.get('purchase.order') edi_document = { - "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test", + "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_test", "__module": "sale", "__model": "sale.order", "__import_module": "purchase", "__import_model": "purchase.order", - "__version": [6,1,0], + "__version": [7,0,0], "name": "SO008", "currency": { - "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR", + "__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.EUR", "__module": "base", "__model": "res.currency", "code": "EUR", "symbol": "€", }, "date_order": "2011-09-13", - "partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"], + "partner_id": ["sale:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_test22", "Junjun wala"], "partner_address": { - "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh", + "__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_address_7wdsjasdjh", "__module": "base", "__model": "res.partner", "phone": "(+32).81.81.37.00", @@ -65,48 +65,48 @@ "zip": "1367", "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"], }, - "company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."], + "company_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_company", "Supplier S.A."], "company_address": { - "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address", + "__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_address", "__module": "base", "__model": "res.partner", "city": "Gerompont", "zip": "1367", - "country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"], + "country_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.be", "Belgium"], "phone": "(+32).81.81.37.00", "street": "Chaussee de Namur 40", "street2": "mailbox 34", "bank_ids": [ - ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"] + ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"] ], }, "order_line": [{ - "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0", + "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeuI-SSP0", "__module": "sale", "__model": "sale.order.line", "__import_module": "purchase", "__import_model": "purchase.order.line", "name": "PC Assemble SC234", - "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"], + "product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"], "product_qty": 1.0, "date_planned": "2011-09-30", "sequence": 10, "price_unit": 150.0, - "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"], + "product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"], }, { - "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad", + "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeadasdad", "__module": "sale", "__model": "sale.order.line", "__import_module": "purchase", "__import_model": "purchase.order.line", "name": "PC on Demand", - "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"], + "product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"], "product_qty": 10.0, "sequence": 11, "date_planned": "2011-09-15", "price_unit": 20.0, - "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"], + "product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"], }], } new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context) @@ -114,6 +114,7 @@ order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id) # check bank info on partner + assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order" assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" bank_info = order_new.partner_id.bank_ids[0] assert bank_info.acc_number == "Guys bank: 123477777-156113", 'Expected "Guys bank: 123477777-156113", got %s' % bank_info.acc_number @@ -133,3 +134,104 @@ assert purchase_line.product_qty == 10 , "product qty is not same" else: raise AssertionError('unknown order line: %s' % purchase_line) +- + "Then I import a sample EDI document of a sale order (v6.1 - to test backwards compatibility)" +- + !python {model: edi.edi}: | + purchase_order_pool = self.pool.get('purchase.order') + edi_document = { + "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test", + "__module": "sale", + "__model": "sale.order", + "__import_module": "purchase", + "__import_model": "purchase.order", + "__version": [6,1,0], + "name": "SO08v61", + "currency": { + "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR", + "__module": "base", + "__model": "res.currency", + "code": "EUR", + "symbol": "€", + }, + "date_order": "2011-09-13", + "partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"], + "partner_address": { + "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh", + "__module": "base", + "__model": "res.partner.address", + "phone": "(+32).81.81.37.00", + "street": "Chaussee de Namur 40", + "city": "Gerompont", + "zip": "1367", + "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"], + }, + "company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."], + "company_address": { + "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address", + "__module": "base", + "__model": "res.partner.address", + "city": "Gerompont", + "zip": "1367", + "country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"], + "phone": "(+32).81.81.37.00", + "street": "Chaussee de Namur 40", + "street2": "mailbox 34", + "bank_ids": [ + ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Another bank: 123477700-156113"] + ], + }, + "order_line": [{ + "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0", + "__module": "sale", + "__model": "sale.order.line", + "__import_module": "purchase", + "__import_model": "purchase.order.line", + "name": "Basic PC", + "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"], + "product_qty": 1.0, + "date_planned": "2011-09-30", + "sequence": 10, + "price_unit": 150.0, + "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc1", "[PC1] Basic PC"], + }, + { + "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad", + "__module": "sale", + "__model": "sale.order.line", + "__import_module": "purchase", + "__import_model": "purchase.order.line", + "name": "Medium PC", + "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"], + "product_qty": 10.0, + "sequence": 11, + "date_planned": "2011-09-15", + "price_unit": 20.0, + "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc3", "[PC3] Medium PC"], + }], + } + new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context) + assert new_purchase_order_id, 'Purchase order import failed' + order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id) + + # check bank info on partner + assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order" + assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" + bank_info = order_new.partner_id.bank_ids[0] + assert bank_info.acc_number == "Another bank: 123477700-156113", 'Expected "Another bank: 123477700-156113", got %s' % bank_info.acc_number + + assert order_new.pricelist_id.name == 'Default Purchase Pricelist' , "Default Purchase Pricelist was not automatically assigned" + assert order_new.amount_total == 350, "Amount total is not same" + assert order_new.amount_untaxed == 350, "untaxed amount is not same" + assert len(order_new.order_line) == 2, "Purchase order lines number mismatch" + for purchase_line in order_new.order_line: + if purchase_line.name == 'Basic PC': + assert purchase_line.product_uom.name == "PCE" , "uom is not same" + assert purchase_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,) + assert purchase_line.product_qty == 1 , "product qty is not same" + elif purchase_line.name == 'Medium PC': + assert purchase_line.product_uom.name == "PCE" , "uom is not same" + assert purchase_line.price_unit == 20 , "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,) + assert purchase_line.product_qty == 10 , "product qty is not same" + else: + raise AssertionError('unknown order line: %s' % purchase_line) \ No newline at end of file diff --git a/addons/sale/edi/sale_order.py b/addons/sale/edi/sale_order.py index 0ca1b55898c..15389f1481c 100644 --- a/addons/sale/edi/sale_order.py +++ b/addons/sale/edi/sale_order.py @@ -88,34 +88,36 @@ class sale_order(osv.osv, EDIMixin): edi_doc_list.append(edi_doc) return edi_doc_list - def _edi_import_company(self, cr, uid, edi_document, context=None): # TODO: for multi-company setups, we currently import the document in the # user's current company, but we should perhaps foresee a way to select # the desired company among the user's allowed companies self._edi_requires_attributes(('company_id','company_address'), edi_document) - res_partner_obj = self.pool.get('res.partner') + res_partner = self.pool.get('res.partner') - # imported company_address = new partner address - _, src_company_name = edi_document.pop('company_id') + xid, company_name = edi_document.pop('company_id') + # Retrofit address info into a unified partner info (changed in v7 - used to keep them separate) + company_address_edi = edi_document.pop('company_address') + company_address_edi['name'] = company_name + company_address_edi['is_company'] = True + company_address_edi['__import_model'] = 'res.partner' + company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway + if company_address_edi.get('logo'): + company_address_edi['image'] = company_address_edi.pop('logo') + company_address_edi['customer'] = True + partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context) - address_info = edi_document.pop('company_address') - address_info['supplier'] = True - if 'name' not in address_info: - address_info['name'] = src_company_name + # modify edi_document to refer to new partner + partner = res_partner.browse(cr, uid, partner_id, context=context) + partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context) + edi_document['partner_id'] = partner_edi_m2o + edi_document['partner_invoice_id'] = partner_edi_m2o + edi_document['partner_shipping_id'] = partner_edi_m2o - address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context) + edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address! + return partner_id - # modify edi_document to refer to new partner/address - partner_address = res_partner_obj.browse(cr, uid, address_id, context=context) - edi_document.pop('partner_address', False) # ignored - address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context) - edi_document['partner_id'] = address_edi_m2o - edi_document['partner_invoice_id'] = address_edi_m2o - edi_document['partner_shipping_id'] = address_edi_m2o - - return address_id def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None): # TODO: refactor into common place for purchase/sale, e.g. into product module diff --git a/addons/sale/test/edi_sale_order.yml b/addons/sale/test/edi_sale_order.yml index 65a5887fe40..2db03cadf59 100644 --- a/addons/sale/test/edi_sale_order.yml +++ b/addons/sale/test/edi_sale_order.yml @@ -32,64 +32,64 @@ edi_doc = self.generate_edi(cr, uid, [so]) assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict' - - Then I import a sample EDI document of a purchase order + "Then I import a sample EDI document of a purchase order (v7.0)" - !python {model: edi.edi}: | sale_order_pool = self.pool.get('sale.order') edi_document = { - "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test", + "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_test", "__module": "purchase", "__model": "purchase.order", "__import_module": "sale", "__import_model": "sale.order", - "__version": [6,1,0], + "__version": [7,0,0], "name": "PO00011", "date_order": "2011-09-12", "currency": { - "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR", + "__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.EUR", "__module": "base", "__model": "res.currency", "code": "EUR", "symbol": "€", }, - "company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."], + "company_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.main_company", "Client S.A."], "company_address": { - "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address", + "__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.some_address", "__module": "base", "__model": "res.partner", "phone": "(+32).81.81.37.00", "street": "Chaussee de Namur 40", "city": "Gerompont", "zip": "1367", - "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"], + "country_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.be", "Belgium"], "bank_ids": [ - ["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"] + ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"] ], }, - "partner_id": ["purchase:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_test20", "jones white"], + "partner_id": ["purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_test20", "jones white"], "order_line": [{ - "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ", + "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-AlhsVDZGoKvJ", "__module": "purchase", "__model": "purchase.order.line", "__import_module": "sale", "__import_model": "sale.order.line", "name": "PC Assemble SC234", "price_unit": 150.0, - "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"], + "product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"], "product_qty": 1.0, - "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"], + "product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"], }, { - "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e", + "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-Alsads33e", "__module": "purchase", "__model": "purchase.order.line", "__import_module": "sale", "__import_model": "sale.order.line", "name": "PC on Demand", "price_unit": 100.0, - "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"], + "product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"], "product_qty": 2.0, - "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"], + "product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"], }], } new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context) @@ -97,6 +97,7 @@ order_new = sale_order_pool.browse(cr, uid, new_sale_order_id) # check bank info on partner + assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order" assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" bank_info = order_new.partner_id.bank_ids[0] assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number @@ -116,3 +117,103 @@ assert sale_line.product_uom_qty == 2 , "product qty is not same" else: raise AssertionError('unknown order line: %s' % sale_line) +- + "Then I import a sample EDI document of a purchase order (v6.1 - to test backwards compatibility)" +- + !python {model: edi.document}: | + sale_order_pool = self.pool.get('sale.order') + edi_document = { + "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test", + "__module": "purchase", + "__model": "purchase.order", + "__import_module": "sale", + "__import_model": "sale.order", + "__version": [6,1,0], + "name": "PO00011-v61", + "date_order": "2011-09-12", + "currency": { + "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR", + "__module": "base", + "__model": "res.currency", + "code": "EUR", + "symbol": "€", + }, + "company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."], + "company_address": { + "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address", + "__module": "base", + "__model": "res.partner.address", + "phone": "(+32).81.81.37.00", + "street": "Chaussee de Namur 40", + "city": "Gerompont", + "zip": "1367", + "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"], + "bank_ids": [ + ["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"] + ], + }, + "partner_id": ["purchase:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_test20", "jones white"], + "partner_address": { + "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh", + "__module": "base", + "__model": "res.partner.address", + "phone": "(+32).81.81.37.00", + "street": "Chaussee de Namur 40", + "city": "Gerompont", + "zip": "1367", + "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"], + }, + "order_line": [{ + "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ", + "__module": "purchase", + "__model": "purchase.order.line", + "__import_module": "sale", + "__import_model": "sale.order.line", + "name": "Basic PC", + "date_planned": "2011-09-30", + "price_unit": 150.0, + "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_pc1", "[PC1] Basic PC"], + "product_qty": 1.0, + "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "PCE"], + }, + { + "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e", + "__module": "purchase", + "__model": "purchase.order.line", + "__import_module": "sale", + "__import_model": "sale.order.line", + "name": "Medium PC", + "date_planned": "2011-09-15", + "price_unit": 100.0, + "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_pc3", "[PC3] Medium PC"], + "product_qty": 2.0, + "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "PCE"], + }], + } + new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context) + assert new_sale_order_id, 'Sale order import failed' + order_new = sale_order_pool.browse(cr, uid, new_sale_order_id) + + # check bank info on partner + assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order" + assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" + bank_info = order_new.partner_id.bank_ids[0] + assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number + + assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned" + assert order_new.amount_total == 350, "Amount total is wrong" + assert order_new.amount_untaxed == 350, "Untaxed amount is wrong" + assert len(order_new.order_line) == 2, "Sale order lines mismatch" + for sale_line in order_new.order_line: + if sale_line.name == 'Basic PC': + assert sale_line.delay == 18 , "incorrect delay: got %s, expected 18"%(sale_line.delay,) + assert sale_line.product_uom.name == "PCE" , "uom is not same" + assert sale_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(sale_line.price_unit,) + assert sale_line.product_uom_qty == 1 , "product qty is not same" + elif sale_line.name == 'Medium PC': + assert sale_line.delay == 3 , "incorrect delay: got %s, expected 3"%(sale_line.delay,) + assert sale_line.product_uom.name == "PCE" , "uom is not same" + assert sale_line.price_unit == 100 , "unit price is not same, got %s, expected 100"%(sale_line.price_unit,) + assert sale_line.product_uom_qty == 2 , "product qty is not same" + else: + raise AssertionError('unknown order line: %s' % sale_line) \ No newline at end of file From 46b2a78ea0fb825c3874080614f267c305fffc94 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 19 Oct 2012 12:39:45 +0200 Subject: [PATCH 008/153] [FIX] sale/edi: typo when adding 6.1 EDI testcase bzr revid: odo@openerp.com-20121019103945-mor902q1qzyszhn0 --- addons/sale/test/edi_sale_order.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/test/edi_sale_order.yml b/addons/sale/test/edi_sale_order.yml index 2db03cadf59..78ff1c24da3 100644 --- a/addons/sale/test/edi_sale_order.yml +++ b/addons/sale/test/edi_sale_order.yml @@ -120,7 +120,7 @@ - "Then I import a sample EDI document of a purchase order (v6.1 - to test backwards compatibility)" - - !python {model: edi.document}: | + !python {model: edi.edi}: | sale_order_pool = self.pool.get('sale.order') edi_document = { "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test", From ce1226a4a8ebb7e197214be54a8c3c8815e72d6b Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 19 Oct 2012 13:11:00 +0200 Subject: [PATCH 009/153] [FIX] sale,edi: fix sale EDI import test + better debug - `delay` field on SO line moved to sale_stock, ignoring it for now - avoid reusing the same bank account in 6.1 test, would cause a test failure because the import would be skipped and the new partner would have no bank account. Bank accounts should be unique in the real world. - log in DEBUG when a relationship record is skipped during EDI import bzr revid: odo@openerp.com-20121019111100-dwf0crocqv43m3ij --- addons/edi/models/edi.py | 3 +++ addons/sale/test/edi_sale_order.yml | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/addons/edi/models/edi.py b/addons/edi/models/edi.py index 5edbca11d13..48a04c2a94d 100644 --- a/addons/edi/models/edi.py +++ b/addons/edi/models/edi.py @@ -525,6 +525,9 @@ class EDIMixin(object): model = self.pool.get(model) res_id, _ = model.name_create(cr, uid, value, context=context) target = model.browse(cr, uid, res_id, context=context) + else: + _logger.debug("%s: Importing EDI relationship [%r,%r] - record already exists with ID %s, using it", + self._name, external_id, value, target.id) if need_new_ext_id: ext_id_members = split_external_id(external_id) # module name is never used bare when creating ir.model.data entries, in order diff --git a/addons/sale/test/edi_sale_order.yml b/addons/sale/test/edi_sale_order.yml index 78ff1c24da3..24fdfdf2786 100644 --- a/addons/sale/test/edi_sale_order.yml +++ b/addons/sale/test/edi_sale_order.yml @@ -63,7 +63,7 @@ "zip": "1367", "country_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.be", "Belgium"], "bank_ids": [ - ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"] + ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_bank-adaWadsadasdDJzGbp","Another bank: 032465700-156700"] ], }, "partner_id": ["purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_test20", "jones white"], @@ -100,7 +100,7 @@ assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order" assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner" bank_info = order_new.partner_id.bank_ids[0] - assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number + assert bank_info.acc_number == "Another bank: 032465700-156700", 'Expected "Another bank: 032465700-156700", got %s' % bank_info.acc_number assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned" assert order_new.amount_total == 350, "Amount total is wrong" @@ -206,12 +206,10 @@ assert len(order_new.order_line) == 2, "Sale order lines mismatch" for sale_line in order_new.order_line: if sale_line.name == 'Basic PC': - assert sale_line.delay == 18 , "incorrect delay: got %s, expected 18"%(sale_line.delay,) assert sale_line.product_uom.name == "PCE" , "uom is not same" assert sale_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(sale_line.price_unit,) assert sale_line.product_uom_qty == 1 , "product qty is not same" elif sale_line.name == 'Medium PC': - assert sale_line.delay == 3 , "incorrect delay: got %s, expected 3"%(sale_line.delay,) assert sale_line.product_uom.name == "PCE" , "uom is not same" assert sale_line.price_unit == 100 , "unit price is not same, got %s, expected 100"%(sale_line.price_unit,) assert sale_line.product_uom_qty == 2 , "product qty is not same" From 67d4646d092c93f9f78d38f8364242ffacef5415 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 23 Oct 2012 13:42:14 +0200 Subject: [PATCH 010/153] [IMP] sale,purchase,invoice: attach PDF report to `Send by mail` template This is quite useful in general, and partly compensates for partial EDI integration, e.g. when the sender company does not have a portal or any way for customers to see the document. bzr revid: odo@openerp.com-20121023114214-8zpl96jxqvqxv4xw --- addons/account/edi/invoice_action_data.xml | 4 ++++ addons/purchase/edi/purchase_order_action_data.xml | 4 ++++ addons/sale/edi/sale_order_action_data.xml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml index 1ad023c3a60..b4fb6d07fcd 100644 --- a/addons/account/edi/invoice_action_data.xml +++ b/addons/account/edi/invoice_action_data.xml @@ -27,6 +27,8 @@ ${object.partner_id.email or ''} + + Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''} @@ -42,7 +44,9 @@ % if object.origin:   Order reference: ${object.origin}
    % endif + % if object.user_id:   Your contact: ${object.user_id.name} + % endif

    % if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'): diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml index 02e7a1c1ae1..fa043b72ce9 100644 --- a/addons/purchase/edi/purchase_order_action_data.xml +++ b/addons/purchase/edi/purchase_order_action_data.xml @@ -24,6 +24,8 @@ ${object.partner_id.email} + + RFQ_${(object.name or '').replace('/','_')} @@ -42,7 +44,9 @@ % if object.partner_ref:   Your reference: ${object.partner_ref}
    % endif + % if object.user_id:   Your contact: ${object.validator.name} + % endif


    diff --git a/addons/sale/edi/sale_order_action_data.xml b/addons/sale/edi/sale_order_action_data.xml index 5ce35bf497d..009f575f00d 100644 --- a/addons/sale/edi/sale_order_action_data.xml +++ b/addons/sale/edi/sale_order_action_data.xml @@ -26,6 +26,8 @@ ${object.partner_invoice_id.email} + + ${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''} @@ -44,7 +46,9 @@ % if object.client_order_ref:   Your reference: ${object.client_order_ref}
    % endif + % if object.user_id:   Your contact: ${object.user_id.name} + % endif

    % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'): From 6fadb453259697c10cf2aed0be33c41760dc246b Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 23 Oct 2012 13:56:28 +0200 Subject: [PATCH 011/153] [IMP] sale,purchase,invoice: make `Send by mail` action more robust to template/view deletion bzr revid: odo@openerp.com-20121023115628-3wvxcrkgazhi9p75 --- addons/account/account_invoice.py | 24 ++++++++++++++---------- addons/account/edi/invoice.py | 1 - addons/purchase/purchase.py | 25 ++++++++++++++----------- addons/sale/sale.py | 23 +++++++++++++---------- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 57f16473b82..15f000cb90e 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -390,28 +390,32 @@ class account_invoice(osv.osv): ''' This function opens a window to compose an email, with the edi invoice template message loaded by default ''' - mod_obj = self.pool.get('ir.model.data') - template = mod_obj.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice') - template_id = template and template[1] or False - res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form') - res_id = res and res[1] or False + assert len(ids) == 1, 'This option should only be used for a single id at a time.' + ir_model_data = self.pool.get('ir.model.data') + try: + template_id = ir_model_data.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1] + except ValueError: + template_id = False + try: + compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1] + except ValueError: + compose_form_id = False ctx = dict(context) ctx.update({ 'default_model': 'account.invoice', 'default_res_id': ids[0], - 'default_use_template': True, + 'default_use_template': bool(template_id), 'default_template_id': template_id, }) return { + 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', - 'views': [(res_id, 'form')], - 'view_id': res_id, - 'type': 'ir.actions.act_window', + 'views': [(compose_form_id, 'form')], + 'view_id': compose_form_id, 'target': 'new', 'context': ctx, - 'nodestroy': True, } def confirm_paid(self, cr, uid, ids, context=None): diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py index 80a4b145054..3e27bc5ea99 100644 --- a/addons/account/edi/invoice.py +++ b/addons/account/edi/invoice.py @@ -148,7 +148,6 @@ class account_invoice(osv.osv, EDIMixin): edi_document['partner_id'] = partner_edi_m2o edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address! - return partner_id def edi_import(self, cr, uid, edi_document, context=None): diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index db6ed8b80be..ed8cdb63cef 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -349,28 +349,31 @@ class purchase_order(osv.osv): ''' This function opens a window to compose an email, with the edi purchase template message loaded by default ''' - mod_obj = self.pool.get('ir.model.data') - template = mod_obj.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase') - template_id = template and template[1] or False - res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form') - res_id = res and res[1] or False + ir_model_data = self.pool.get('ir.model.data') + try: + template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1] + except ValueError: + template_id = False + try: + compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1] + except ValueError: + compose_form_id = False ctx = dict(context) ctx.update({ 'default_model': 'purchase.order', 'default_res_id': ids[0], - 'default_use_template': True, + 'default_use_template': bool(template_id), 'default_template_id': template_id, - }) + }) return { + 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', - 'views': [(res_id, 'form')], - 'view_id': res_id, - 'type': 'ir.actions.act_window', + 'views': [(compose_form_id, 'form')], + 'view_id': compose_form_id, 'target': 'new', 'context': ctx, - 'nodestroy': True, } #TODO: implement messages system diff --git a/addons/sale/sale.py b/addons/sale/sale.py index a2ee88ab860..3c984b64349 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -618,29 +618,32 @@ class sale_order(osv.osv): This function opens a window to compose an email, with the edi sale template message loaded by default ''' assert len(ids) == 1, 'This option should only be used for a single id at a time.' - mod_obj = self.pool.get('ir.model.data') - template = mod_obj.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale') - template_id = template and template[1] or False - res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form') - res_id = res and res[1] or False + ir_model_data = self.pool.get('ir.model.data') + try: + template_id = ir_model_data.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')[1] + except ValueError: + template_id = False + try: + compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1] + except ValueError: + compose_form_id = False ctx = dict(context) ctx.update({ 'default_model': 'sale.order', 'default_res_id': ids[0], - 'default_use_template': True, + 'default_use_template': bool(template_id), 'default_template_id': template_id, 'mark_so_as_sent': True }) return { + 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', - 'views': [(res_id, 'form')], - 'view_id': res_id, - 'type': 'ir.actions.act_window', + 'views': [(compose_form_id, 'form')], + 'view_id': compose_form_id, 'target': 'new', 'context': ctx, - 'nodestroy': True, } def action_done(self, cr, uid, ids, context=None): From 21b56902b7eb7ad4eabb2b3be97ca42384d12fdc Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Oct 2012 14:59:57 +0200 Subject: [PATCH 012/153] [FIX] sale/edi: paypal payment should be possible as soon as SO is not draft Most companies won't mind if customers pay too early. Even if the amount is not 100% correct, it can be adjusted later, and it's better than missing a revenue opportunity. bzr revid: odo@openerp.com-20121024125957-oo8r9zmazzdonhd7 --- addons/sale/edi/sale_order_action_data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/edi/sale_order_action_data.xml b/addons/sale/edi/sale_order_action_data.xml index 009f575f00d..cfe5277d3ce 100644 --- a/addons/sale/edi/sale_order_action_data.xml +++ b/addons/sale/edi/sale_order_action_data.xml @@ -51,7 +51,7 @@ % endif

    - % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'): + % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state != 'draft': <% comp_name = quote(object.company_id.name) order_name = quote(object.name) From 20c81b5e2fea00643f234ac83814ce73bae14d12 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Oct 2012 15:15:54 +0200 Subject: [PATCH 013/153] [ADD] portal: added portal.payment.acquirer model portal.payment.acquirer stores payment processor options (formally called payment acquirers). Each acquirer is just a name and an HTML form template, used to render an HTML snippet that can be included in views where a payment option should be displayed. The aquirer model has a generic method for rendering a complete block of HTML to be included in form views directly (with matching CSS): render_payment_block(). This method takes a few parameters to figure out the name/reference, amount, currency, etc. to pay. bzr revid: odo@openerp.com-20121024131554-j01ucniecjmoz3jq --- addons/portal/__init__.py | 2 +- addons/portal/__openerp__.py | 2 + addons/portal/acquirer.py | 96 ++++++++++++++++++++++ addons/portal/acquirer_view.xml | 67 +++++++++++++++ addons/portal/portal_data.xml | 24 ++++++ addons/portal/security/ir.model.access.csv | 2 + addons/portal/static/src/css/portal.css | 60 ++++++++++++++ 7 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 addons/portal/acquirer.py create mode 100644 addons/portal/acquirer_view.xml create mode 100644 addons/portal/static/src/css/portal.css diff --git a/addons/portal/__init__.py b/addons/portal/__init__.py index 2bfc6df0d96..0045a557a49 100644 --- a/addons/portal/__init__.py +++ b/addons/portal/__init__.py @@ -22,6 +22,6 @@ import portal import mail_mail import wizard - +import acquirer # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/portal/__openerp__.py b/addons/portal/__openerp__.py index 66a736c6048..4f37a2205d7 100644 --- a/addons/portal/__openerp__.py +++ b/addons/portal/__openerp__.py @@ -49,8 +49,10 @@ very handy when used in combination with the module 'share'. 'portal_view.xml', 'wizard/portal_wizard_view.xml', 'wizard/share_wizard_view.xml', + 'acquirer_view.xml', ], 'demo': ['portal_demo.xml'], + 'css': ['static/src/css/portal.css'], 'installable': True, } diff --git a/addons/portal/acquirer.py b/addons/portal/acquirer.py new file mode 100644 index 00000000000..922a341b679 --- /dev/null +++ b/addons/portal/acquirer.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import logging +from urllib import quote as quote + +from openerp.osv import osv, fields +from openerp.tools import ustr +from openerp.tools.translate import _ + +_logger = logging.getLogger(__name__) +try: + from mako.template import Template as MakoTemplate +except ImportError: + _logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!") + + +class acquirer(osv.Model): + _name = 'portal.payment.acquirer' + _description = 'Online Payment Acquirer' + + _columns = { + 'name': fields.char('Name', required=True), + 'form_template': fields.text('Payment form template (HTML)', translate=True), + 'visible': fields.boolean('Visible', help="Whether this payment acquirer is currently displayed in portal forms"), + } + + _default = { + 'visible': True, + } + + def render(self, cr, uid, id, object, reference, currency, amount, context=None, **kwargs): + """ Renders the form template of the given acquirer as a mako template """ + if not isinstance(id, (int,long)): + id = id[0] + this = self.browse(cr, uid, id) + if context is None: + context = {} + try: + i18n_kind = _(object._description) # may fail to translate, but at least we try + template = ustr(this.form_template) + result = MakoTemplate(template).render_unicode(object=object, + reference=reference, + currency=currency, + amount=amount, + kind=i18n_kind, + quote=quote, + # context kw would clash with mako internals + ctx=context, + format_exceptions=True) + result = result.strip() + if result == u'False': + result = u'' + return result + except Exception: + _logger.exception("failed to render mako template value for payment.acquirer %s: %r", this.name, template) + return + + def _wrap_payment_block(self, cr, uid, html_block, context=None): + payment_header = _('Pay safely online:') + result = """
    + %s + %%s +
    """ % payment_header + return result % html_block + + def render_payment_block(self, cr, uid, object, reference, currency, amount, context=None, **kwargs): + """ Renders all visible payment acquirer forms for the given rendering context, and + return them wrapped in an appropriate HTML block, ready for direct inclusion + in an OpenERP v7 form view """ + acquirer_ids = self.search(cr, uid, [('visible', '=', True)]) + if not acquirer_ids: + return + html_forms = [] + for this in self.browse(cr, uid, acquirer_ids): + html_forms.append(this.render(object, reference, currency, amount, context=context, **kwargs)) + html_block = '\n'.join(html_forms) + return self._wrap_payment_block(cr, uid, html_block, context=context) diff --git a/addons/portal/acquirer_view.xml b/addons/portal/acquirer_view.xml new file mode 100644 index 00000000000..0e88d47f758 --- /dev/null +++ b/addons/portal/acquirer_view.xml @@ -0,0 +1,67 @@ + + + + + portal.payment.acquirer + +
    + +
    +
    + +
    +

    + This is an HTML form template to submit a payment through this acquirer. + The template will be rendered through mako, so it may use normal mako expressions. + The mako evaluation context provides: +

      +
    • reference: the reference number of the document to pay
    • +
    • kind: the kind of document on which the payment form is rendered (translated to user language, e.g. "Invoice")
    • +
    • currency: the currency record in which the document is issued (e.g. currency.name could be EUR)
    • +
    • amount: the total amount to pay, as a float
    • +
    • object: the document on which the payment form is rendered (usually an invoice or sale order record)
    • +
    • quote(): a method to quote special string character to make them suitable for inclusion in a URL
    • +
    • cr: the current database cursor
    • +
    • uid: the current user id
    • +
    • ctx: the current context dictionary
    • +
    + If the template renders to an empty result in a certain context it will be ignored, as if it was inactive. +

    +
    + +
    +
    +
    +
    +
    + + portal.payment.acquirer + + + + + + + + + portal.payment.acquirer + + + + + + + + + Payment Acquirers + portal.payment.acquirer + + + + + + +
    +
    diff --git a/addons/portal/portal_data.xml b/addons/portal/portal_data.xml index 90a570bc1d5..89e62c8d48e 100644 --- a/addons/portal/portal_data.xml +++ b/addons/portal/portal_data.xml @@ -27,6 +27,30 @@ form + + + + Paypal + + + + +% endif + ]]> + diff --git a/addons/portal/security/ir.model.access.csv b/addons/portal/security/ir.model.access.csv index 9202c7f4087..b6d932bf7ee 100644 --- a/addons/portal/security/ir.model.access.csv +++ b/addons/portal/security/ir.model.access.csv @@ -4,3 +4,5 @@ access_res_partner,res.partner,base.model_res_partner,portal.group_portal,1,0,0, access_res_partner_address,res.partner_address,base.model_res_partner_address,portal.group_portal,1,0,0,0 access_res_partner_category,res.partner_category,base.model_res_partner_category,portal.group_portal,1,0,0,0 access_res_partner_title,res.partner_title,base.model_res_partner_title,portal.group_portal,1,0,0,0 +access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0 +access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1 diff --git a/addons/portal/static/src/css/portal.css b/addons/portal/static/src/css/portal.css new file mode 100644 index 00000000000..02d0d44e6b3 --- /dev/null +++ b/addons/portal/static/src/css/portal.css @@ -0,0 +1,60 @@ + +.openerp .oe_application .oe_form_sheetbg { + /* Establish a stacking context on top of which the + payment_acquirers::before element can be positioned */ + position: relative; + z-index: 0; +} + +.openerp .payment_acquirers { + margin: -40px 0 -32px -24px; + position: relative; + padding: 10px 15px; + right: -153px; + + background: #729FCF; + background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4)); + background-image: -webkit-linear-gradient(top, #729FCF, #3465A4); + background-image: -moz-linear-gradient(top, #729FCF, #3465A4); + background-image: -ms-linear-gradient(top, #729FCF, #3465A4); + background-image: -o-linear-gradient(top, #729FCF, #3465A4); + background-image: linear-gradient(to bottom, #729FCF, #3465A4); + border-bottom: 1px solid #043574; + + -webkit-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45); +} + +.openerp .payment_acquirers::after { + content: " "; + display: block; + width: 10px; + height: 20px; + position: absolute; + bottom: 0; + right: 1px; + + margin-bottom: -6px; + background: #043574; + + -webkit-transform: skewY(-45deg); + -moz-transform: skewY(-45deg); + -ms-transform: skewY(-45deg); + -o-transform: skewY(-45deg); + transform: skewY(-45deg); + + -webkit-box-shadow: inset 1px -1px 2px black, -1px 1px 3px black; + box-shadow: inset 1px -1px 2px black, -1px 1px 3px black; + + /* push it under all its siblings, just on top of its root + in the z-index stack: div.oe_form_sheetbg */ + z-index: -1; +} + +.openerp .payment_acquirers .payment_header { + font-weight: bold; + font-size: 110%; + padding-right: 15px; + color: white; + text-shadow: 0 1px 1px #729FCF, 0 -1px 1px #3465A4; +} \ No newline at end of file From 21e48c3b96f12878f84909cf09a734eb7032657c Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Oct 2012 15:16:47 +0200 Subject: [PATCH 014/153] [ADD] portal_sale: integrate payment acquirer options in sale.order forms bzr revid: odo@openerp.com-20121024131647-hfqgjpftwgygx976 --- addons/portal_sale/__init__.py | 1 + addons/portal_sale/portal_sale.py | 40 +++++++++++++++++++++++++ addons/portal_sale/portal_sale_view.xml | 12 ++++++++ 3 files changed, 53 insertions(+) create mode 100644 addons/portal_sale/portal_sale.py diff --git a/addons/portal_sale/__init__.py b/addons/portal_sale/__init__.py index 26c654db9dd..8cc32ba971e 100644 --- a/addons/portal_sale/__init__.py +++ b/addons/portal_sale/__init__.py @@ -19,3 +19,4 @@ # ############################################################################## +import portal_sale \ No newline at end of file diff --git a/addons/portal_sale/portal_sale.py b/addons/portal_sale/portal_sale.py new file mode 100644 index 00000000000..4e41fd53332 --- /dev/null +++ b/addons/portal_sale/portal_sale.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012 OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import osv, fields + +class sale_order(osv.Model): + _inherit = 'sale.order' + + _payment_block_proxy = lambda self,*a,**kw: self._portal_payment_block(*a, **kw) + + _columns = { + 'portal_payment_options': fields.function(_payment_block_proxy, type="html", string="Portal Payment Options"), + } + + def _portal_payment_block(self, cr, uid, ids, fieldname, arg, context=None): + result = dict.fromkeys(ids, False) + payment_acquirer = self.pool.get('portal.payment.acquirer') + for this in self.browse(cr, uid, ids, context=context): + if this.state != 'draft': + result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.name, + this.pricelist_id.currency_id, this.amount_total, context=context) + return result \ No newline at end of file diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index 6b25d87b791..f4607590717 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -2,6 +2,18 @@ + + sale.order.form.payment + sale.order + + + + + + + + + + + + + Portal Personal Quotations/Sales Orders From 52ee9769f69144ab8050ed70572ddcaffe0dd6c5 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 26 Oct 2012 18:48:03 +0200 Subject: [PATCH 018/153] [IMP] sale_portal: add payment option on invoices, fix state check to display it on sale.order bzr revid: odo@openerp.com-20121026164803-625usw34bnyzmxqu --- addons/portal_sale/portal_sale.py | 21 +++++++++++++++- addons/portal_sale/portal_sale_view.xml | 33 +++++++++++++++++++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/addons/portal_sale/portal_sale.py b/addons/portal_sale/portal_sale.py index d45653bd817..09d71fc1d2b 100644 --- a/addons/portal_sale/portal_sale.py +++ b/addons/portal_sale/portal_sale.py @@ -34,7 +34,26 @@ class sale_order(osv.Model): result = dict.fromkeys(ids, False) payment_acquirer = self.pool.get('portal.payment.acquirer') for this in self.browse(cr, uid, ids, context=context): - if this.state != 'draft' and not this.invoiced: + if this.state not in ('draft','cancel') and not this.invoiced: result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.name, this.pricelist_id.currency_id, this.amount_total, context=context) + return result + + +class account_invoice(osv.Model): + _inherit = 'account.invoice' + + _payment_block_proxy = lambda self,*a,**kw: self._portal_payment_block(*a, **kw) + + _columns = { + 'portal_payment_options': fields.function(_payment_block_proxy, type="html", string="Portal Payment Options"), + } + + def _portal_payment_block(self, cr, uid, ids, fieldname, arg, context=None): + result = dict.fromkeys(ids, False) + payment_acquirer = self.pool.get('portal.payment.acquirer') + for this in self.browse(cr, uid, ids, context=context): + if this.state not in ('draft','done') and not this.reconciled: + result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.number, + this.currency_id, this.residual, context=context) return result \ No newline at end of file diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index e1c5a233eb9..f01b35eff12 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -1,7 +1,8 @@ - + + sale.order.form.payment sale.order @@ -12,6 +13,16 @@ + + account.invoice.form.payment + account.invoice + + + + + + + + + account.config.settings + + + +
    + +
    +
    +
    +
    + + + \ No newline at end of file From 1b3d045ceed3fad80bc5740c41731791ec09b6cd Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Wed, 31 Oct 2012 15:37:37 +0530 Subject: [PATCH 020/153] [FIX] added *.id* field manually. lp bug: https://launchpad.net/bugs/1071794 fixed bzr revid: vme@tinyerp.com-20121031100737-u0r015etp6jilo80 --- addons/web/controllers/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 72ffd9567f6..4dabc4c2846 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1767,7 +1767,9 @@ class Export(View): def fields_info(self, req, model, export_fields): info = {} fields = self.fields_get(req, model) - + if ".id" in export_fields: + fields['.id'] = fields.pop('id', {'string': 'ID'}) + # To make fields retrieval more efficient, fetch all sub-fields of a # given field at the same time. Because the order in the export list is # arbitrary, this requires ordering all sub-fields of a given field From 6e27ee461b41688d375a43c3ec58c84149cede86 Mon Sep 17 00:00:00 2001 From: "ajay javiya (OpenERP)" Date: Fri, 9 Nov 2012 17:43:58 +0530 Subject: [PATCH 021/153] [FIX]: Fix issue in warehouse module bzr revid: aja@tinyerp.com-20121109121358-4ws1vlcrteq0ckv4 --- addons/stock/stock_view.xml | 6 +++--- addons/stock/wizard/stock_fill_inventory.py | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 9c2a672caf7..d548495d972 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -114,7 +114,7 @@
    -
    @@ -1234,8 +1234,8 @@ - - + + diff --git a/addons/stock/wizard/stock_fill_inventory.py b/addons/stock/wizard/stock_fill_inventory.py index 11907b674a1..1d3b8dd450d 100644 --- a/addons/stock/wizard/stock_fill_inventory.py +++ b/addons/stock/wizard/stock_fill_inventory.py @@ -60,9 +60,6 @@ class stock_fill_inventory(osv.osv_memory): if context.get('active_id', False): stock = self.pool.get('stock.inventory').browse(cr, uid, context.get('active_id', False)) - - if stock.state == 'done': - raise osv.except_osv(_('Warning!'), _('Stock Inventory is already Validated.')) return True def fill_inventory(self, cr, uid, ids, context=None): @@ -126,22 +123,23 @@ class stock_fill_inventory(osv.osv_memory): res[location] = datas if not flag: - raise osv.except_osv(_('Warning!'), _('No product in this location.')) + raise osv.except_osv(_('Warning!'), _('No product in this location. Please select a location in the product form.')) for stock_move in res.values(): for stock_move_details in stock_move.values(): stock_move_details.update({'inventory_id': context['active_ids'][0]}) domain = [] - - if fill_inventory.set_stock_zero: - stock_move_details.update({'product_qty': 0}) - for field, value in stock_move_details.items(): + if field == 'product_qty' and fill_inventory.set_stock_zero: + domain.append((field, 'in', [value,'0'])) + continue domain.append((field, '=', value)) line_ids = inventory_line_obj.search(cr, uid, domain, context=context) if not line_ids: + if fill_inventory.set_stock_zero: + stock_move_details.update({'product_qty': 0}) inventory_line_obj.create(cr, uid, stock_move_details, context=context) return {'type': 'ir.actions.act_window_close'} From ba2f569d6debac69000c10ee8b6db5a9985abd39 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 9 Nov 2012 17:46:06 +0530 Subject: [PATCH 022/153] [IMP] add placeholder in procurement order. bzr revid: tpa@tinyerp.com-20121109121606-vku2c5zzukv185b1 --- addons/procurement/procurement_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml index 42179b17f92..cc922f70dc5 100644 --- a/addons/procurement/procurement_view.xml +++ b/addons/procurement/procurement_view.xml @@ -56,7 +56,7 @@

    From 269117635f746bed544275f32fbcef50e39c82ea Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 12 Nov 2012 11:44:01 +0530 Subject: [PATCH 023/153] [FIX] stock: meke type field to selection from related field. bzr revid: tpa@tinyerp.com-20121112061401-jcqef7rxk0rw54g0 --- addons/stock/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 606cb9247d5..6a5b5745313 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1725,7 +1725,7 @@ class stock_move(osv.osv): # used for colors in tree views: 'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True), - 'type': fields.related('picking_id', 'type', type='selection', selection=[('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal')], string='Shipping Type'), + 'type': fields.selection( (('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal')), 'Shipping Type', select=True), } def _check_location(self, cr, uid, ids, context=None): From 8ed98ddec39fd774892420e40d15823f2baa4747 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 12 Nov 2012 12:32:24 +0530 Subject: [PATCH 024/153] [IMP] procurement: apply group on product_uom field. bzr revid: tpa@tinyerp.com-20121112070224-md1utssp0qd99x69 --- addons/procurement/procurement_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml index cc922f70dc5..a0d51c87713 100644 --- a/addons/procurement/procurement_view.xml +++ b/addons/procurement/procurement_view.xml @@ -77,7 +77,7 @@
    From 3f655858d89b6910c24fa3dbc8a85adcc20a8046 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 15 Nov 2012 11:05:42 +0100 Subject: [PATCH 056/153] [IMP] mail: the placeholder of textarea on compose message can be custom with an attribute placeholder on the field bzr revid: chm@openerp.com-20121115100542-7gigxjnxjloqt8ue --- addons/mail/static/src/js/mail.js | 7 ++++++- addons/mail/static/src/xml/mail.xml | 13 ++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 006f9529272..7d874a8d1fe 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -1231,6 +1231,7 @@ openerp.mail = function (session) { 'default_parent_id': self.id, }}); } else { + data.record_name= (data.record_name != '' && data.record_name) || (self.parent_message && self.parent_message.record_name); var message = new mail.ThreadMessage(self, data, {'context':{ 'default_model': data.model, 'default_res_id': data.res_id, @@ -1447,6 +1448,7 @@ openerp.mail = function (session) { 'show_record_name' : false, 'show_compose_message' : false, 'show_compact_message' : false, + 'compose_placeholder': false, 'view_inbox': false, 'message_ids': undefined, }, this.action.params); @@ -1511,7 +1513,6 @@ openerp.mail = function (session) { init: function (parent, node) { this._super.apply(this, arguments); this.node = _.clone(node); - this.node.params = _.extend({ 'display_indented_thread': -1, 'show_reply_button': false, @@ -1520,6 +1521,10 @@ openerp.mail = function (session) { 'show_compact_message': 1, }, this.node.params); + if (this.node.attrs.placeholder) { + this.node.params.compose_placeholder = this.node.attrs.placeholder; + } + this.domain = this.node.params && this.node.params.domain || []; }, diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index deab58eca00..f4518dc6fa7 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -39,8 +39,9 @@
    -