[IMP] edi: work-in-progress: remove permanent storage of edi.document
The EDI documents will now be generated on demand and available from the Portal view of each document. Instead of getting a link to a statically generated EDI document, customers will receive a link to the portal access to the document. They will be able to signup on the portal the first time as well, provided they are using the secure token that was sent to them (i.e. the right link). The link to pay online will be available in the portal as well. Still much to do, this is a small first step, with edi.document renamed to edi.edi for consistency, as it will not persist any edi.document anymore. bzr revid: odo@openerp.com-20121011152008-bht7ub6woaex0a7u
This commit is contained in:
parent
d1698d2b11
commit
5f24594223
|
@ -71,16 +71,6 @@ INVOICE_EDI_STRUCT = {
|
|||
class account_invoice(osv.osv, EDIMixin):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
def action_invoice_sent(self, cr, uid, ids, context=None):
|
||||
""""Override this method to add a link to mail"""
|
||||
if context is None:
|
||||
context = {}
|
||||
invoice_objs = self.browse(cr, uid, ids, context=context)
|
||||
edi_token = self.pool.get('edi.document').export_edi(cr, uid, invoice_objs, context = context)[0]
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
|
||||
return super(account_invoice, self).action_invoice_sent(cr, uid, ids, context=ctx)
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Exports a supplier or customer invoice"""
|
||||
edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT)
|
||||
|
@ -136,7 +126,7 @@ class account_invoice(osv.osv, EDIMixin):
|
|||
self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
|
||||
res_partner = self.pool.get('res.partner')
|
||||
|
||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
||||
_, src_company_name = edi_document.pop('company_id')
|
||||
|
||||
invoice_type = edi_document['type']
|
||||
partner_value = {}
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- EDI Export + Send email Action -->
|
||||
<record id="ir_actions_server_edi_invoice" model="ir.actions.server">
|
||||
<field name="code">if (object.type in ('out_invoice', 'out_refund')) and not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='account.email_template_edi_invoice', context=context)</field>
|
||||
<field eval="6" name="sequence"/>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="account.model_account_invoice"/>
|
||||
<field name="condition">True</field>
|
||||
<field name="name">Auto-email confirmed invoices</field>
|
||||
</record>
|
||||
|
||||
<!-- EDI related Email Templates menu -->
|
||||
<record model="ir.actions.act_window" id="action_email_templates">
|
||||
<field name="name">Email Templates</field>
|
||||
|
@ -27,17 +16,12 @@
|
|||
|
||||
</data>
|
||||
|
||||
<!-- Mail template and workflow bindings are done in a NOUPDATE block
|
||||
<!-- Mail template are declared in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
<!-- bind the mailing server action to invoice open activity -->
|
||||
<record id="account.act_open" model="workflow.activity">
|
||||
<field name="action_id" ref="ir_actions_server_edi_invoice"/>
|
||||
</record>
|
||||
|
||||
<!--Email template -->
|
||||
<record id="email_template_edi_invoice" model="email.template">
|
||||
<field name="name">Automated Invoice Notification Mail</field>
|
||||
<field name="name">Invoice - Send by mail</field>
|
||||
<field name="email_from">${object.user_id.email or object.company_id.email or 'noreply@localhost'}</field>
|
||||
<field name="subject">${object.company_id.name} Invoice (Ref ${object.number or 'n/a' })</field>
|
||||
<field name="email_to">${object.partner_id.email or ''}</field>
|
||||
|
@ -61,12 +45,6 @@
|
|||
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Invoice%20${object.number}">${object.user_id.name}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can view the invoice document, download it and pay online using the following link:
|
||||
</p>
|
||||
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #FFF; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
|
||||
href="${ctx.get('edi_web_url_view') or ''}">View Invoice</a>
|
||||
|
||||
% if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
|
||||
<%
|
||||
comp_name = quote(object.company_id.name)
|
||||
|
|
|
@ -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
|
||||
-
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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('<script type="text/javascript" src="%s"></script>'%i for i in webmain.manifest_list(req, mods, 'js'))
|
||||
d["css"] = "\n".join('<link rel="stylesheet" href="%s">'%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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
|
||||
class res_company(osv.osv):
|
||||
"""Helper subclass for res.company providing util methods for working with
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -20,10 +20,10 @@
|
|||
##############################################################################
|
||||
import logging
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from openerp import SUPERUSER_ID
|
||||
from tools.translate import _
|
||||
from openerp.tools.translate import _
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
RES_PARTNER_EDI_STRUCT = {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_ir_edi_all_read,access_ir_edi_all_read,model_edi_document,,1,0,0,0
|
||||
access_ir_edi_employee_create,access_ir_edi_employee_create,model_edi_document,base.group_user,1,0,1,0
|
|
|
@ -6,11 +6,10 @@
|
|||
with an attached file, check the result, the alter the data
|
||||
and reimport it.
|
||||
-
|
||||
!python {model: edi.document}: |
|
||||
!python {model: edi.edi}: |
|
||||
import json
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_2'))])
|
||||
doc = self.get_document(cr, uid, tokens[0], context=context)
|
||||
res_partner = self.pool.get('res.partner')
|
||||
doc = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, ref('base.res_partner_2'))])
|
||||
edi_doc, = json.loads(doc)
|
||||
|
||||
# check content of the document
|
||||
|
@ -36,8 +35,7 @@
|
|||
"Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_2'), result)
|
||||
|
||||
# export the same partner we just created, and see if the output matches the input
|
||||
tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])])
|
||||
doc_output = self.get_document(cr, uid, tokens[0], context=context)
|
||||
doc_output = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, result[1])])
|
||||
edi_doc_output, = json.loads(doc_output)
|
||||
for attribute in ('__model', '__module', '__id', 'name', '__attachments'):
|
||||
assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \
|
||||
|
|
|
@ -83,6 +83,7 @@ class mail_compose_message(osv.osv_memory):
|
|||
'datas_fname': attach_fname,
|
||||
'res_model': model,
|
||||
'res_id': res_id,
|
||||
'type': 'binary', # override default_type from context, possibly meant for another model!
|
||||
}
|
||||
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
||||
else:
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<li>
|
||||
<span t-if="(attachment.upload or attachment.percent_loaded<100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
|
||||
<div class="oe_upload_in_process">
|
||||
<span>...wait upload...</span>
|
||||
<span>Upload in progress...</span>
|
||||
<div t-attf-style="{attachment.percent_loaded>0?'':'display:none;'}"/>
|
||||
<div t-attf-style="{attachment.percent_loaded>20?'':'display:none;'}"/>
|
||||
<div t-attf-style="{attachment.percent_loaded>40?'':'display:none;'}"/>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<t t-raw="attachment.name || attachment.filename"/>
|
||||
</a>
|
||||
<t t-if="widget.options.thread.show_attachment_delete and (!attachment.upload or attachment.percent_loaded>=100)">
|
||||
<a class="oe_right oe_mail_attachment_delete" title="Delete this attachmentt" t-attf-data-id="{attachment.id}">x</a>
|
||||
<a class="oe_right oe_mail_attachment_delete" title="Delete this attachment" t-attf-data-id="{attachment.id}">x</a>
|
||||
</t>
|
||||
|
||||
</li>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,13 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from osv import fields, osv, orm
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from edi.models import edi
|
||||
from tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from tools.translate import _
|
||||
|
||||
PURCHASE_ORDER_LINE_EDI_STRUCT = {
|
||||
|
@ -62,16 +57,6 @@ PURCHASE_ORDER_EDI_STRUCT = {
|
|||
class purchase_order(osv.osv, EDIMixin):
|
||||
_inherit = 'purchase.order'
|
||||
|
||||
def wkf_send_rfq(self, cr, uid, ids, context=None):
|
||||
""""Override this method to add a link to mail"""
|
||||
if context is None:
|
||||
context = {}
|
||||
purchase_objs = self.browse(cr, uid, ids, context=context)
|
||||
edi_token = self.pool.get('edi.document').export_edi(cr, uid, purchase_objs, context = context)[0]
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
|
||||
return super(purchase_order, self).wkf_send_rfq(cr, uid, ids, context=ctx)
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Exports a purchase order"""
|
||||
edi_struct = dict(edi_struct or PURCHASE_ORDER_EDI_STRUCT)
|
||||
|
@ -109,7 +94,7 @@ class purchase_order(osv.osv, EDIMixin):
|
|||
res_partner_obj = self.pool.get('res.partner')
|
||||
|
||||
# imported company_address = new partner address
|
||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
||||
_, src_company_name = edi_document.pop('company_id')
|
||||
address_info = edi_document.pop('company_address')
|
||||
address_info['customer'] = True
|
||||
if 'name' not in address_info:
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!--Export edi document -->
|
||||
<!--
|
||||
<record id="ir_actions_server_edi_purchase" model="ir.actions.server">
|
||||
<field name="code">if not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='purchase.email_template_edi_purchase', context=context)</field>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||
<field name="condition">True</field>
|
||||
<field name="name">Auto-email confirmed purchase orders</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- EDI related Email Templates menu -->
|
||||
<record model="ir.actions.act_window" id="action_email_templates">
|
||||
<field name="name">Email Templates</field>
|
||||
|
@ -25,19 +13,12 @@
|
|||
</record>
|
||||
</data>
|
||||
|
||||
<!-- Mail template and workflow bindings are done in a NOUPDATE block
|
||||
<!-- Mail template are declared in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
<!-- bind the mailing server action to purchase.order confirmed activity -->
|
||||
<!--
|
||||
<record id="purchase.act_confirmed" model="workflow.activity">
|
||||
<field name="action_id" ref="ir_actions_server_edi_purchase"/>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!--Email template -->
|
||||
<record id="email_template_edi_purchase" model="email.template">
|
||||
<field name="name">Automated Purchase Order Notification Mail</field>
|
||||
<field name="name">Purchase Order - Send by mail</field>
|
||||
<field name="email_from">${object.validator.email or ''}</field>
|
||||
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
||||
<field name="email_to">${object.partner_id.email}</field>
|
||||
|
@ -64,12 +45,6 @@
|
|||
Your contact: <a href="mailto:${object.validator.email or ''}?subject=Order%20${object.name}">${object.validator.name}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can view the ${object.state in ('draft', 'sent') and 'request for quotation' or 'order confirmation'} document and download it using the following link:
|
||||
</p>
|
||||
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #FFF; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
|
||||
href="${ctx.get('edi_web_url_view') or ''}">View Order</a>
|
||||
|
||||
<br/>
|
||||
<p>If you have any question, do not hesitate to contact us.</p>
|
||||
<p>Thank you!</p>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,13 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from osv import fields, osv, orm
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from edi.models import edi
|
||||
from tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
SALE_ORDER_LINE_EDI_STRUCT = {
|
||||
'sequence': True,
|
||||
|
@ -65,15 +60,6 @@ SALE_ORDER_EDI_STRUCT = {
|
|||
class sale_order(osv.osv, EDIMixin):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
def action_quotation_send(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
sale_objs = self.browse(cr, uid, ids, context=context)
|
||||
edi_token = self.pool.get('edi.document').export_edi(cr, uid, sale_objs, context = context)[0]
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
|
||||
return super(sale_order, self).action_quotation_send(cr, uid, ids, context=ctx)
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Exports a Sale order"""
|
||||
edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT)
|
||||
|
@ -112,7 +98,7 @@ class sale_order(osv.osv, EDIMixin):
|
|||
res_partner_obj = self.pool.get('res.partner')
|
||||
|
||||
# imported company_address = new partner address
|
||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
||||
_, src_company_name = edi_document.pop('company_id')
|
||||
|
||||
address_info = edi_document.pop('company_address')
|
||||
address_info['supplier'] = True
|
||||
|
@ -171,7 +157,6 @@ class sale_order(osv.osv, EDIMixin):
|
|||
currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
|
||||
order_currency = res_currency.browse(cr, uid, currency_id)
|
||||
|
||||
date_order = edi_document['date_order']
|
||||
partner_ref = edi_document.pop('partner_ref', False)
|
||||
edi_document['client_order_ref'] = edi_document['name']
|
||||
edi_document['name'] = partner_ref or edi_document['name']
|
||||
|
@ -185,7 +170,7 @@ class sale_order(osv.osv, EDIMixin):
|
|||
|
||||
order_lines = edi_document['order_line']
|
||||
for order_line in order_lines:
|
||||
self._edi_requires_attributes(( 'product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
|
||||
self._edi_requires_attributes(('product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
|
||||
order_line['product_uom_qty'] = order_line['product_qty']
|
||||
del order_line['product_qty']
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- EDI related Email Templates menu -->
|
||||
<record model="ir.actions.act_window" id="action_email_templates">
|
||||
<field name="name">Email Templates</field>
|
||||
|
@ -15,14 +14,13 @@
|
|||
<menuitem id="base.menu_sales_configuration_misc" name="Miscellaneous" parent="base.menu_base_config" sequence="75"/>
|
||||
</data>
|
||||
|
||||
|
||||
<!-- Mail template is done in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
|
||||
<!--Email template -->
|
||||
<record id="email_template_edi_sale" model="email.template">
|
||||
<field name="name">Automated Sale Order Notification Mail</field>
|
||||
<field name="name">Sale Order - Send by mail</field>
|
||||
<field name="email_from">${object.user_id.email or ''}</field>
|
||||
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
||||
<field name="email_to">${object.partner_invoice_id.email}</field>
|
||||
|
@ -49,12 +47,6 @@
|
|||
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Order%20${object.name}">${object.user_id.name}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can view the ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} document, download it and pay online using the following link:
|
||||
</p>
|
||||
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #FFF; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
|
||||
href="${ctx.get('edi_web_url_view') or ''}">View Order</a>
|
||||
|
||||
% 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)
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue