From eab2e225a223d0919ee4dde65f4dfe7e269fa01c Mon Sep 17 00:00:00 2001
From: "Nimesh (Open ERP)"
- 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.- 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.- 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 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
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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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.
-
-
- - import urllib2, simplejson
- - edi_document = urllib2.urlopen('
').read()
- - document_data = simplejson.loads(edi_document)[0]
- - print "Amount: ", document_data['amount_total']
-
-
- 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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Invoice :
-
-
- Description
- Date
- Your Reference
-
-
-
-
-
-
-
-
-
-
- Product Description
- Quantity
- Unit Price
- Discount
- Price
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Net Total:
-
-
-
-
-
-
-
-
-
-
- Taxes:
-
-
-
-
-
-
-
-
-
-
- Total:
-
-
-
-
-
-
-
-
-
-
- Tax
- Base Amount
- Amount
-
-
-
-
-
-
-
-
-
-
-
-
- Notes:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You may directly pay this invoice online via Paypal's secure payment gateway:
-
-
-
-
-
-
-
-
-
-
-
- Please transfer to
- (postal address on the invoice header)
- using one of the following bank accounts. Be sure to mention the invoice
- reference on the transfer:
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Quotation :
-
-
- Request for Quotation :
-
-
- Order :
-
-
-
-
- Your Reference
- Date
- Salesperson
- Payment terms
-
-
-
-
-
-
-
-
-
-
-
-
-
- Product Description
- Quantity
- Unit Price
- Discount(%)
- Price
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Net Total:
-
-
-
-
-
-
-
-
-
-
- Taxes:
-
-
-
-
-
-
-
-
-
-
- Total:
-
-
-
-
-
-
-
-
- Notes:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You may directly pay this order online via Paypal's secure payment gateway:
-
-
-
-
-
-
-
-
-
-
-
- Please transfer to
- (postal address on the order header)
- using one of the following bank accounts. Be sure to mention the document
- reference on the transfer:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From b42801595e48ab266a60ae6b9e77d73a1adb98fe Mon Sep 17 00:00:00 2001
From: Olivier Dony
Date: Wed, 17 Oct 2012 18:28:55 +0200
Subject: [PATCH 004/106] [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 005/106] [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 006/106] [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 007/106] [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 008/106] [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 009/106] [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 010/106] [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 011/106] [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 012/106] [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
+
+
+
+
+
+ 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
+
+% endif
+ ]]>The top menu bar contains all applications you installed. You can use this <i>Settings</i> menu to install more applications, activate others features or give access to new users.
+To setup your preferences (name, email signature, avatar), click on the top right corner.
]]>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: + The template will be rendered with Mako, so it may use Mako expressions. + The Mako evaluation context provides:
+ No message in this group. +
+- No message found. -
-- No message sent yet. + No message found and no message sent yet.
Click on the top-right icon to compose a message. This
message will be sent by email if it's an internal contact.
@@ -150,11 +132,5 @@
Pigs rules' + _body = 'Pigs rule' + _reply_subject = 'Re: Pigs' _attachments = [ {'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')}, {'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')} @@ -462,9 +461,9 @@ class test_mail(test_mail_mockup.TestMailMockups): # 1. Comment group_pigs with body_text and subject compose_id = mail_compose.create(cr, uid, - {'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]}, + {'subject': _subject, 'body': _body, 'partner_ids': [(4, p_c_id), (4, p_d_id)]}, {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id, - 'default_content_subtype': 'plaintext'}) + 'default_content_subtype': 'plaintext'}) compose = mail_compose.browse(cr, uid, compose_id) # Test: mail.compose.message: composition_mode, model, res_id self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode') @@ -476,8 +475,8 @@ class test_mail(test_mail_mockup.TestMailMockups): group_pigs.refresh() message = group_pigs.message_ids[0] # Test: mail.message: subject, body inside pre - self.assertEqual(message.subject, False, 'mail.message incorrect subject') - self.assertEqual(message.body, _msg_body, 'mail.message incorrect body') + self.assertEqual(message.subject, _subject, 'mail.message incorrect subject') + self.assertEqual(message.body, _body, 'mail.message incorrect body') # Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d) msg_pids = [partner.id for partner in message.notified_partner_ids] test_pids = [p_b_id, p_c_id, p_d_id] @@ -495,13 +494,12 @@ class test_mail(test_mail_mockup.TestMailMockups): {'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]}, {'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id}) compose = mail_compose.browse(cr, uid, compose_id) - # Test: model, res_id, parent_id, content_subtype + # Test: model, res_id, parent_id self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model') self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id') self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id') - self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype') # Test: mail.message: subject as Re:.., body in html, parent_id - self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject') + self.assertEqual(compose.subject, _reply_subject, 'mail.message incorrect subject') # self.assertIn('Administrator wrote:
', compose.body, 'mail.message body is incorrect') self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect') # Test: mail.message: attachments @@ -520,8 +518,6 @@ class test_mail(test_mail_mockup.TestMailMockups): {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False, 'active_ids': [self.group_pigs_id, group_bird_id]}) compose = mail_compose.browse(cr, uid, compose_id) - # Test: content_subtype is html - self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect') # 2. Post the comment, get created message for each group mail_compose.send_mail(cr, uid, [compose_id], diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 839a3a6970e..694dc082bfe 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -81,7 +81,7 @@ class mail_compose_message(osv.TransientModel): elif composition_mode == 'comment' and model and res_id: vals = self.get_record_data(cr, uid, model, res_id, context=context) elif composition_mode == 'mass_mail' and model and active_ids: - vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'} + vals = {'model': model, 'res_id': res_id} else: vals = {'model': model, 'res_id': res_id} if composition_mode: @@ -106,16 +106,10 @@ class mail_compose_message(osv.TransientModel): domain=lambda self: [('res_model', '=', self._name)], string='Attachments'), 'filter_id': fields.many2one('ir.filters', 'Filters'), - 'body_text': fields.text('Plain-text Contents'), - 'content_subtype': fields.char('Message content subtype', size=32, readonly=1, - help="Type of message, usually 'html' or 'plain', used to select "\ - "plain-text or rich-text contents accordingly"), } _defaults = { 'composition_mode': 'comment', - 'content_subtype': lambda self, cr, uid, ctx={}: 'html', - 'body_text': lambda self, cr, uid, ctx={}: False, 'body': lambda self, cr, uid, ctx={}: '', 'subject': lambda self, cr, uid, ctx={}: False, 'partner_ids': lambda self, cr, uid, ctx={}: [], @@ -172,32 +166,9 @@ class mail_compose_message(osv.TransientModel): 'parent_id': message_data.id, 'subject': reply_subject, 'partner_ids': partner_ids, - 'content_subtype': 'html', } return result - def toggle_content_subtype(self, cr, uid, ids, context=None): - """ toggle content_subtype: calls onchange_formatting to emulate an - on_change, then writes the value to update the form. """ - for record in self.browse(cr, uid, ids, context=context): - content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html' - onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context) - self.write(cr, uid, [record.id], onchange_res['value'], context=context) - return True - - def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None): - """ This onchange allows to have some specific behavior when switching - between text or html mode. This method can be overridden. - :param values: 'plain' or 'html' - """ - return {'value': {'content_subtype': value}} - - def dummy(self, cr, uid, ids, context=None): - """ TDE: defined to have buttons that do basically nothing. It is - currently impossible to have buttons that do nothing special - in views (if type not specified, considered as 'object'). """ - return True - #------------------------------------------------------ # Wizard validation and send #------------------------------------------------------ @@ -218,8 +189,8 @@ class mail_compose_message(osv.TransientModel): for res_id in res_ids: # default values, according to the wizard options post_values = { - 'subject': wizard.subject if wizard.content_subtype == 'html' else False, - 'body': wizard.body if wizard.content_subtype == 'html' else 'Pigs rules
%s' % tools.ustr(wizard.body_text), + 'subject': wizard.subject, + 'body': wizard.body, 'parent_id': wizard.parent_id and wizard.parent_id.id, 'partner_ids': [(4, partner.id) for partner in wizard.partner_ids], 'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids], diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 86b1be6562f..1711e09b2b7 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -12,7 +12,6 @@
Hello,
+I am interested in your company's products and I plan to buy a new laptop having latest technologies as well as an affordable price.
+Could you please send me the product catalog?
]]>Any advice on this? How do you organize your activities with more than 50 opportunities?
]]>I send an email to create a sense of emergency, like "can I call you this week about our quote?" and I call only those that answer this email.
You can use the email template feature of OpenERP to automate email composition.
]]>+ The main events of the month of october are: +
++ Sales: +
++ Finance : +
++ Partenaire et clients: +
++ Lancement de la nouvelle version : +
++ Nous sommes dans la phase active du lancement de la nouvelle release +
++Nicolas, can you setup a conf call for our meeting of friday ? +
++ Regards. +
+]]>Epic Technologies is a small company specialized in big data software.
I saw you have interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need?]]>- Sales: + Sales:
Finance :
- Partenaire et clients: + Resellers and Customers:
- Lancement de la nouvelle version : + Launch of the new release:
- Nous sommes dans la phase active du lancement de la nouvelle release + We are working actively on the new release which is scheduled for the end of november.
-Nicolas, can you setup a conf call for our meeting of friday ? + Nicolas, can you book a meeting room for our meeting of friday 2pm?
Regards.
From fa93a1d65e5848131e89b0a01974837163c83e0b Mon Sep 17 00:00:00 2001
From: Fabien Pinckaers
- I'd like to install two backup servers in my company. Do you have a simple solution to manage backups of Linux computers?
- Best regards,
We need to deploy new backup servers, with the following requirements:
++ Do you have a simple solution with servers running on Redhat Linux? +
+Best regards,
]]>I have a friend working at Epic Technologies. He told me they plan to upgrade their backup servers within the next 3 months.
I think that someone should contact them and check if there is an opportunity.
]]>Epic Technologies is a small company specialized in big data software.
I saw you have interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need?]]>Epic Technologies is a small company specialized in big data software.
I saw you have interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need: +Thanks,
+ ]]>Best regards,
]]>After our phonecall and discussion with our technical experts, here is the offer of YourCompany. We believe it will meet every requirement you had in mind. Please feel free to contact me for any detail or technical detail that is not clear enough for you.
+Notice that as agreed on phone, we offer you a 10% discount on the hardware!
+Best regards,
]]>We have a lot of inquiries about our now solution using RedHat servers. However I do not have the updated specification ready at hand.
+Could you please send me the last version of the file asap?
+Thanks,
]]>When it's done, put it on the internal document management system.
Thanks,
]]>We need to deploy new backup servers, with the following requirements:
The next version of our products catalog is scheduled for next month. Our product team send me their updated info about the prices and costs, and I updated our catalog.
+You will find it in attachment, as well as a comparative benchmark of the different solutions currently existing on the market.
Have a nice reading!
+ Sincerely,
I have a friend working at Epic Technologies. He told me they plan to upgrade their backup servers within the next 3 months.
I think that someone should contact them and check if there is an opportunity.
]]>Epic Technologies is a small company specialized in big data software.
I saw you have interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need: +Epic Technologies is a small company specialized in software managing huge volume of data. Having an efficient and reliable backup system is very important to us, and critical for our customers. I eared you have some interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need? Here is a first list of requirements:
I am glad you are interested in our products. Indeed, we are have several backup solutions that should meet your requirements. In order to prepare a detailed offer, we will have to discuss several technical points about your needs and the context of your data management.
+I propose to have a meeting tomorrow at 2 PM. Does it seem suitable for you ?
Best regards,
-The top menu bar contains all applications you installed. You can use the Settings menu to install more applications, activate others features or give access to new users.
-To setup your preferences (name, email signature, avatar), click on the top right corner.
]]>The top menu bar contains all applications you installed. You can use the Settings menu to install more applications, activate others features or give access to new users.
+To setup your preferences (name, email signature, avatar), click on the top right corner.
]]>- The main events of the month of october are: + The main events of the month of October are:
Sales:
Resellers and Customers:
Launch of the new release:
- We are working actively on the new release which is scheduled for the end of november. + We are working actively on the new release which is scheduled for the end of November.
- Nicolas, can you book a meeting room for our meeting of friday 2pm? + Nicolas, can you book a meeting room for our meeting of Friday 2pm?
Regards.
@@ -174,13 +174,13 @@
We have a lot of inquiries about our now solution using RedHat servers. However I do not have the updated specification ready at hand.
Could you please send me the last version of the file asap?
Thanks,
]]>I writing to you about our On Site Assistance Service that we delivered to Agrolait last week. Do you have any feedback or remark about our service? I noticed you requested new IP phones. Will it be used for new employees, or did you have any issue with the ones we provided?
Best regards,
Glad to hearing from you! Everything is perfect, thanks for asking. Concerning the order of 2 IP phones, I ordered them for new employees. We are satisfied with the products of YourCompany, and we plan to fit out each new employee with one of your phone this year.
Regards,
A small email to inform you that we will have a meeting with Mr Demo next Tuesday. Everything is under control!
Regards,
We have a lot of inquiries about our now solution using RedHat servers. However I do not have the updated specification ready at hand.
+We have a lot of inquiries about our now solution based on RedHat servers. However I do not have the updated specification ready at hand.
Could you please send me the last version of the file asap?
Thanks,
]]>When it's done, put it on the internal document management system.
Thanks,
]]>When it's done, put it on the internal document management system.
Thanks,
]]>We need to deploy new backup servers, with the following requirements:
-- Do you have a simple solution with servers running on Redhat Linux? -
-Best regards,
+We need to deploy new backup servers, with the following requirements:
++ Do you have a simple solution with servers running on Redhat Linux? +
+Best regards,
]]>The next version of our products catalog is scheduled for next month. Our product team send me their updated info about the prices and costs, and I updated our catalog.
+The next version of our products catalog is scheduled for next month. Our product team send me their updated document holding the prices and costs, and I updated our catalog.
You will find it in attachment, as well as a comparative benchmark of the different solutions currently existing on the market.
Have a nice reading!
Sincerely,
Sincerely,
]]>Epic Technologies is a small company specialized in software managing huge volume of data. Having an efficient and reliable backup system is very important to us, and critical for our customers. I eared you have some interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need? Here is a first list of requirements:
+Epic Technologies is a small company specialized in software managing huge volume of data. Having an efficient and reliable backup system is very important for us, and critical for our customers. I eared you have some interesting solutions to manage our backups. Could we meet each other as soon as possible to discuss our need? Here is a first list of requirements: