diff --git a/addons/account/__init__.py b/addons/account/__init__.py
index 36cc1c85b5a..a243b94c2da 100644
--- a/addons/account/__init__.py
+++ b/addons/account/__init__.py
@@ -35,5 +35,5 @@ import product
import ir_sequence
import company
import res_currency
-
+import edi
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py
index 9445688ec6d..81d09b30ae9 100644
--- a/addons/account/__openerp__.py
+++ b/addons/account/__openerp__.py
@@ -53,7 +53,7 @@ module named account_voucher.
'website': 'http://www.openerp.com',
'images' : ['images/accounts.jpeg','images/bank_statement.jpeg','images/cash_register.jpeg','images/chart_of_accounts.jpeg','images/customer_invoice.jpeg','images/journal_entries.jpeg'],
'init_xml': [],
- "depends" : ["base_setup", "product", "analytic", "process","board"],
+ "depends" : ["base_setup", "product", "analytic", "process", "board", "edi"],
'update_xml': [
'security/account_security.xml',
'security/ir.model.access.csv',
@@ -123,7 +123,8 @@ module named account_voucher.
'board_account_view.xml',
"wizard/account_report_profit_loss_view.xml",
"wizard/account_report_balance_sheet_view.xml",
- "account_bank_view.xml"
+ "edi/invoice_action_data.xml",
+ "account_bank_view.xml",
],
'demo_xml': [
'demo/account_demo.xml',
@@ -145,10 +146,9 @@ module named account_voucher.
'test/account_fiscalyear_close.yml',
'test/account_bank_statement.yml',
'test/account_cash_statement.yml',
+ 'test/test_edi_invoice.yml',
'test/account_report.yml',
-
-
- ],
+ ],
'installable': True,
'active': False,
'certificate': '0080331923549',
diff --git a/addons/account/account_report.xml b/addons/account/account_report.xml
index 32d10ba0153..9bb40a7a503 100644
--- a/addons/account/account_report.xml
+++ b/addons/account/account_report.xml
@@ -19,6 +19,7 @@
rml="account/report/account_print_invoice.rml"
string="Invoices"
attachment="(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/',''))"
+ usage="default"
multi="True"/>
diff --git a/addons/account/company.py b/addons/account/company.py
index c903b6d4a71..4ad4d1226d7 100644
--- a/addons/account/company.py
+++ b/addons/account/company.py
@@ -24,6 +24,7 @@ from osv import fields, osv
class res_company(osv.osv):
_inherit = "res.company"
_columns = {
+ 'paypal_account': fields.char("Paypal Account", size=128, help="Paypal username (usually email) for receiving online payments."),
'overdue_msg': fields.text('Overdue Payments Message', translate=True),
'property_reserve_and_surplus_account': fields.property(
'account.account',
diff --git a/addons/account/company_view.xml b/addons/account/company_view.xml
index 0664fadd761..0cc67f23cfe 100644
--- a/addons/account/company_view.xml
+++ b/addons/account/company_view.xml
@@ -24,6 +24,7 @@
+
diff --git a/addons/account/edi/__init__.py b/addons/account/edi/__init__.py
new file mode 100644
index 00000000000..d86e8a91bf3
--- /dev/null
+++ b/addons/account/edi/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 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 invoice
diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py
new file mode 100644
index 00000000000..061bf8ee97d
--- /dev/null
+++ b/addons/account/edi/invoice.py
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import fields, osv, orm
+from edi import EDIMixin
+
+INVOICE_LINE_EDI_STRUCT = {
+ 'name': True,
+ 'origin': True,
+ 'uos_id': True,
+ 'product_id': True,
+ 'price_unit': True,
+ 'quantity': True,
+ 'discount': True,
+ 'note': True,
+
+ # fields used for web preview only - discarded on import
+ 'price_subtotal': True,
+}
+
+INVOICE_TAX_LINE_EDI_STRUCT = {
+ 'name': True,
+ 'base': True,
+ 'amount': True,
+ 'manual': True,
+ 'sequence': True,
+ 'base_amount': True,
+ 'tax_amount': True,
+}
+
+INVOICE_EDI_STRUCT = {
+ 'name': True,
+ 'origin': True,
+ 'company_id': True, # -> to be changed into partner
+ 'type': True, # -> reversed at import
+ 'internal_number': True, # -> reference at import
+ 'comment': True,
+ 'date_invoice': True,
+ 'date_due': True,
+ 'partner_id': True,
+ 'payment_term': True,
+ #custom: currency_id
+ 'invoice_line': INVOICE_LINE_EDI_STRUCT,
+ 'tax_line': INVOICE_TAX_LINE_EDI_STRUCT,
+
+ # fields used for web preview only - discarded on import
+ #custom: 'partner_ref'
+ 'amount_total': True,
+ 'amount_untaxed': True,
+ 'amount_tax': True,
+}
+
+class account_invoice(osv.osv, EDIMixin):
+ _inherit = 'account.invoice'
+
+ 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)
+ res_company = self.pool.get('res.company')
+ res_partner_address = self.pool.get('res.partner.address')
+ edi_doc_list = []
+ for invoice in records:
+ # generate the main report
+ self._edi_generate_report_attachment(cr, uid, invoice, context=context)
+ edi_doc = super(account_invoice,self).edi_export(cr, uid, [invoice], edi_struct, context)[0]
+ edi_doc.update({
+ 'company_address': res_company.edi_export_address(cr, uid, invoice.company_id, context=context),
+ 'company_paypal_account': invoice.company_id.paypal_account,
+ 'partner_address': res_partner_address.edi_export(cr, uid, [invoice.address_invoice_id], context=context)[0],
+
+ 'currency': self.pool.get('res.currency').edi_export(cr, uid, [invoice.currency_id], context=context)[0],
+ 'partner_ref': invoice.reference or False,
+ })
+ edi_doc_list.append(edi_doc)
+ return edi_doc_list
+
+ def _edi_tax_account(self, cr, uid, invoice_type='out_invoice', context=None):
+ #TODO/FIXME: should select proper Tax Account
+ account_pool = self.pool.get('account.account')
+ account_ids = account_pool.search(cr, uid, [('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')])
+ tax_account = False
+ if account_ids:
+ tax_account = account_pool.browse(cr, uid, account_ids[0])
+ 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)
+ if invoice_type in ('out_invoice', 'out_refund'):
+ invoice_account = partner.property_account_receivable
+ else:
+ invoice_account = partner.property_account_payable
+ return invoice_account
+
+ def _edi_product_account(self, cr, uid, product_id, invoice_type, context=None):
+ product_pool = self.pool.get('product.product')
+ product = product_pool.browse(cr, uid, product_id, context=context)
+ if invoice_type in ('out_invoice','out_refund'):
+ account = product.property_account_income or product.categ_id.property_account_income_categ
+ else:
+ account = product.property_account_expense or product.categ_id.property_account_expense_categ
+ return account
+
+ 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','type'), edi_document)
+ res_partner_address = self.pool.get('res.partner.address')
+ res_partner = self.pool.get('res.partner')
+
+ # imported company = new partner
+ src_company_id, src_company_name = edi_document.pop('company_id')
+ partner_id = self.edi_import_relation(cr, uid, 'res.partner', src_company_name,
+ src_company_id, context=context)
+ 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})
+ res_partner.write(cr, uid, [partner_id], partner_value, context=context)
+
+ # imported company_address = new partner address
+ address_info = edi_document.pop('company_address')
+ address_info['partner_id'] = (src_company_id, src_company_name)
+ address_info['type'] = 'invoice'
+ address_id = res_partner_address.edi_import(cr, uid, address_info, context=context)
+
+ # modify edi_document to refer to new partner
+ partner_address = res_partner_address.browse(cr, uid, address_id, context=context)
+ edi_document['partner_id'] = (src_company_id, src_company_name)
+ edi_document.pop('partner_address', False) # ignored
+ edi_document['address_invoice_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
+
+ 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
+ a new partner (e.g. supplier company for a customer invoice will be come a supplier
+ record for the new invoice.
+ Summary of tasks that need to be done:
+ - import company as a new partner, if type==in then supplier=1, else customer=1
+ - partner_id field is modified to point to the new partner
+ - company_address data used to add address to new partner
+ - change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
+ - reference: should contain the value of the 'internal_number'
+ - reference_type: 'none'
+ - internal number: reset to False, auto-generated
+ - journal_id: should be selected based on type: simply put the 'type'
+ in the context when calling create(), will be selected correctly
+ - payment_term: if set, create a default one based on name...
+ - for invoice lines, the account_id value should be taken from the
+ product's default, i.e. from the default category, as it will not
+ be provided.
+ - for tax lines, we disconnect from the invoice.line, so all tax lines
+ will be of type 'manual', and default accounts should be picked based
+ on the tax config of the DB where it is imported.
+ """
+ if context is None:
+ context = {}
+ self._edi_requires_attributes(('company_id','company_address','type','invoice_line','currency'), edi_document)
+
+ # extract currency info
+ res_currency = self.pool.get('res.currency')
+ currency_info = edi_document.pop('currency')
+ currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
+ currency = res_currency.browse(cr, uid, currency_id)
+ edi_document['currency_id'] = self.edi_m2o(cr, uid, currency, context=context)
+
+ # change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
+ invoice_type = edi_document['type']
+ invoice_type = invoice_type.startswith('in_') and invoice_type.replace('in_','out_') or invoice_type.replace('out_','in_')
+ edi_document['type'] = invoice_type
+
+ #import company as a new partner
+ partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
+
+ # Set Account
+ invoice_account = self._edi_invoice_account(cr, uid, partner_id, invoice_type, context=context)
+ edi_document['account_id'] = invoice_account and self.edi_m2o(cr, uid, invoice_account, context=context) or False
+
+ # reference: should contain the value of the 'internal_number'
+ edi_document['reference'] = edi_document.get('internal_number', False)
+ # reference_type: 'none'
+ edi_document['reference_type'] = 'none'
+
+ # internal number: reset to False, auto-generated
+ edi_document['internal_number'] = False
+
+ # discard web preview fields, if present
+ edi_document.pop('partner_ref', None)
+
+ # journal_id: should be selected based on type: simply put the 'type' in the context when calling create(), will be selected correctly
+ context.update(type=invoice_type)
+
+ # for invoice lines, the account_id value should be taken from the product's default, i.e. from the default category, as it will not be provided.
+ for edi_invoice_line in edi_document['invoice_line']:
+ product_info = edi_invoice_line['product_id']
+ product_id = self.edi_import_relation(cr, uid, 'product.product', product_info[1],
+ product_info[0], context=context)
+ account = self._edi_product_account(cr, uid, product_id, invoice_type, context=context)
+ # TODO: could be improved with fiscal positions perhaps
+ # account = fpos_obj.map_account(cr, uid, fiscal_position_id, account.id)
+ edi_invoice_line['account_id'] = self.edi_m2o(cr, uid, account, context=context) if account else False
+
+ # discard web preview fields, if present
+ edi_invoice_line.pop('price_subtotal', None)
+
+ # for tax lines, we disconnect from the invoice.line, so all tax lines will be of type 'manual', and default accounts should be picked based
+ # on the tax config of the DB where it is imported.
+ tax_account = self._edi_tax_account(cr, uid, context=context)
+ tax_account_info = self.edi_m2o(cr, uid, tax_account, context=context)
+ for edi_tax_line in edi_document.get('tax_line', []):
+ edi_tax_line['account_id'] = tax_account_info
+ edi_tax_line['manual'] = True
+
+ return super(account_invoice,self).edi_import(cr, uid, edi_document, context=context)
+
+
+ def _edi_record_display_action(self, cr, uid, id, context=None):
+ """Returns an appropriate action definition dict for displaying
+ the record with ID ``rec_id``.
+
+ :param int id: database ID of record to display
+ :return: action definition dict
+ """
+ action = super(account_invoice,self)._edi_record_display_action(cr, uid, id, context=context)
+ try:
+ invoice = self.browse(cr, uid, id, context=context)
+ if 'out_' in invoice.type:
+ view_ext_id = 'invoice_form'
+ journal_type = 'sale'
+ else:
+ view_ext_id = 'invoice_supplier_form'
+ journal_type = 'purchase'
+ ctx = "{'type': '%s', 'journal_type': '%s'}" % (invoice.type, journal_type)
+ action.update(context=ctx)
+ view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', view_ext_id)[1]
+ action.update(views=[(view_id,'form'), (False, 'tree')])
+ except ValueError:
+ # ignore if views are missing
+ pass
+ return action
+
+
+class account_invoice_line(osv.osv, EDIMixin):
+ _inherit='account.invoice.line'
+
+class account_invoice_tax(osv.osv, EDIMixin):
+ _inherit = "account.invoice.tax"
+
+
diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml
new file mode 100644
index 00000000000..0904ec6fd54
--- /dev/null
+++ b/addons/account/edi/invoice_action_data.xml
@@ -0,0 +1,186 @@
+
+
+
+
+
+ if (object.type in ('out_invoice', 'out_refund')) and not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='account.email_template_edi_invoice', context=context)
+
+ code
+ ir.actions.server
+
+ True
+ Auto-email confirmed invoices
+
+
+
+
+ Email Templates
+ email.template
+ form
+ form,tree
+
+
+ {'search_default_model_id':'account.invoice'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Automated Invoice Notification Mail
+ ${object.user_id.user_email or object.company_id.email or 'noreply@localhost'}
+ ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' })
+ ${object.address_invoice_id.email or ''}
+
+
+
+
+
Hello${object.address_invoice_id.name and ' ' or ''}${object.address_invoice_id.name or ''},
+
+
A new invoice is available for ${object.partner_id.name}:
+ 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.
+
+
+
importurllib2, 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:
+
+
+
+ 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:
+
+ 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:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/edi/test/edi_partner_test.yml b/addons/edi/test/edi_partner_test.yml
new file mode 100644
index 00000000000..cca39417927
--- /dev/null
+++ b/addons/edi/test/edi_partner_test.yml
@@ -0,0 +1,44 @@
+-
+ In order to test the basic EDI system, I will export a partner,
+ modify the exported EDI document to add an attachment and change
+ the data, and then re-import it and re-export it.
+
+ with an attached file, check the result, the alter the data
+ and reimport it.
+-
+ !python {model: edi.document}: |
+ import json
+ partner_obj = self.pool.get('res.partner')
+ tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_agrolait'))])
+ doc = self.get_document(cr, uid, tokens[0], context=context)
+ edi_doc, = json.loads(doc)
+
+ # check content of the document
+ assert edi_doc.get('__id').endswith('.res_partner_agrolait'), 'Incorrect external ID'
+ assert edi_doc.get('__model') == 'res.partner', 'Incorrect/Missing __model'
+ assert edi_doc.get('__module') == 'base', 'Incorrect/Missing __module'
+ assert edi_doc.get('__last_update'), 'Missing __last_update'
+
+ # try to import the document after changing the name and id, and attaching a
+ # file, and check that a new partner is returned
+ edi_doc['__id'] = 'base:xxd37f8a-xx55-11e0-xxdd-xx81124c8b50.res_partner_xxx'
+ edi_doc['name'] = 'AgroMilk'
+ attachment = {
+ 'name': 'Test file',
+ 'file_name': 'test.png',
+ # base64 standard requires blocks of 57bytes=76chars, NL-separated
+ 'content': 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAKCAYAAACE2W/HAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIDQ84BkjLWAYAAACNSURBVCjP\njZKxDUJBDEOf/wbUbEHDhkxAzRjUDECBhMQYjIApuPBNpK+PpUinOPYll5Nt1iCJXifbSPpJZtHg\nXLUdu0FWpMEt87a/xtsmqjjNetPFyhuWYFuj7RcgQBNwXNGdw2CqY85yXWi5z7YBnmRyEHvg0YSX\nrLE9P30nwugOHHr+s5va4x+fofAGm1+JjnJICm0AAAAASUVORK5CYII=\n',
+ }
+ edi_doc['__attachments'] = [attachment]
+ doc = json.dumps([edi_doc])
+ result, = self.import_edi(cr, uid, edi_document=doc)
+ assert result[0] == 'res.partner' and result[1] > ref('base.res_partner_agrolait'),\
+ "Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_agrolait'), 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)
+ 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), \
+ 'Incorrect value for %s, expected %r, got %r' % (attribute, edi_doc.get(attribute), edi_doc_output.get(attribute))
\ No newline at end of file
diff --git a/addons/email_template/__init__.py b/addons/email_template/__init__.py
index 97931ea8bd5..d1cf5b28431 100644
--- a/addons/email_template/__init__.py
+++ b/addons/email_template/__init__.py
@@ -21,5 +21,6 @@
##############################################################################
import email_template
import wizard
+import res_partner
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/__openerp__.py b/addons/email_template/__openerp__.py
index 06fa969c0c2..2ab7f194d18 100644
--- a/addons/email_template/__openerp__.py
+++ b/addons/email_template/__openerp__.py
@@ -61,9 +61,13 @@ Openlabs was kept
"data": [
'wizard/email_template_preview_view.xml',
'email_template_view.xml',
+ 'res_partner_view.xml',
'wizard/email_compose_message_view.xml',
'security/ir.model.access.csv'
],
+ "demo": [
+ 'res_partner_demo.yml',
+ ],
"installable": True,
"active": False,
"certificate" : "00817073628967384349",
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
index 3917b3f2ddb..237279c16aa 100644
--- a/addons/email_template/email_template.py
+++ b/addons/email_template/email_template.py
@@ -28,6 +28,7 @@ from osv import osv
from osv import fields
import tools
from tools.translate import _
+from urllib import quote as quote
try:
from mako.template import Template as MakoTemplate
@@ -66,6 +67,7 @@ class email_template(osv.osv):
user=user,
# context kw would clash with mako internals
ctx=context,
+ quote=quote,
format_exceptions=True)
if result == u'False':
result = u''
@@ -305,6 +307,7 @@ class email_template(osv.osv):
'attachment_ids': False,
'message_id': False,
'state': 'outgoing',
+ 'subtype': 'plain',
}
if not template_id:
return values
@@ -319,6 +322,9 @@ class email_template(osv.osv):
template.model, res_id, context=context) \
or False
+ if values['body_html']:
+ values.update(subtype='html')
+
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_text'] += '\n\n' + signature
@@ -355,20 +361,24 @@ class email_template(osv.osv):
values['attachments'] = attachments
return values
- def send_mail(self, cr, uid, template_id, res_id, context=None):
+ def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None):
"""Generates a new mail message for the given template and record,
- and schedule it for delivery through the ``mail`` module's scheduler.
+ and schedules it for delivery through the ``mail`` module's scheduler.
:param int template_id: id of the template to render
:param int res_id: id of the record to render the template with
(model is taken from the template)
+ :param bool force_send: if True, the generated mail.message is
+ immediately sent after being created, as if the scheduler
+ was executed for this message only.
+ :returns: id of the mail.message that was created
"""
mail_message = self.pool.get('mail.message')
ir_attachment = self.pool.get('ir.attachment')
template = self.browse(cr, uid, template_id, context)
values = self.generate_email(cr, uid, template_id, res_id, context=context)
attachments = values.pop('attachments') or {}
- message_id = mail_message.create(cr, uid, values, context=context)
+ msg_id = mail_message.create(cr, uid, values, context=context)
# link attachments
attachment_ids = []
for fname, fcontent in attachments.iteritems():
@@ -377,10 +387,13 @@ class email_template(osv.osv):
'datas_fname': fname,
'datas': fcontent,
'res_model': mail_message._name,
- 'res_id': message_id,
+ 'res_id': msg_id,
}
if context.has_key('default_type'):
del context['default_type']
- attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context))
+ attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
+ if force_send:
+ mail_message.send(cr, uid, [msg_id], context=context)
+ return msg_id
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml
index d6a651f0bc2..507dae3cd25 100644
--- a/addons/email_template/email_template_view.xml
+++ b/addons/email_template/email_template_view.xml
@@ -155,9 +155,8 @@
-
-
+
diff --git a/addons/email_template/res_partner.py b/addons/email_template/res_partner.py
new file mode 100644
index 00000000000..74d8b4e0693
--- /dev/null
+++ b/addons/email_template/res_partner.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import fields,osv
+
+class res_partner(osv.osv):
+ """Inherit res.partner to add a generic opt-out field that can be used
+ to restrict usage of automatic email templates.
+ This field is unused by default. """
+ _inherit = 'res.partner'
+
+ _columns = {
+ 'opt_out': fields.boolean('Opt-out', help="If checked, this partner will not receive any automated email " \
+ "notifications, such as the availability of invoices."),
+ }
+
+ _defaults = {
+ 'opt_out': False,
+ }
\ No newline at end of file
diff --git a/addons/email_template/res_partner_demo.yml b/addons/email_template/res_partner_demo.yml
new file mode 100644
index 00000000000..8eaf48f9e87
--- /dev/null
+++ b/addons/email_template/res_partner_demo.yml
@@ -0,0 +1,9 @@
+-
+ Set opt-out to True on all demo partners
+-
+ !python {model: res.partner}: |
+ partner_ids = self.search(cr, uid, [])
+ # assume partners with an external ID come from demo data
+ ext_ids = self._get_external_ids(cr, uid, partner_ids)
+ ids_to_update = [k for (k,v) in ext_ids.iteritems() if v]
+ self.write(cr, uid, ids_to_update, {'opt_out': True})
\ No newline at end of file
diff --git a/addons/email_template/res_partner_view.xml b/addons/email_template/res_partner_view.xml
new file mode 100644
index 00000000000..0d2f919a96e
--- /dev/null
+++ b/addons/email_template/res_partner_view.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ res.partner.opt_out.form
+ res.partner
+ form
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/email_template/wizard/email_template_preview.py b/addons/email_template/wizard/email_template_preview.py
index 650b714866d..4ef0e9fa156 100644
--- a/addons/email_template/wizard/email_template_preview.py
+++ b/addons/email_template/wizard/email_template_preview.py
@@ -87,6 +87,8 @@ class email_template_preview(osv.osv_memory):
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
description += '\n' + signature
vals['body_text'] = description
+ if template.body_html:
+ vals['body_html'] = self.render_template(cr, uid, template.body_html, model, res_id, context) or ''
vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
return {'value': vals}
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 9c535966e17..159259f151d 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -479,11 +479,22 @@ class mail_message(osv.osv):
attachments = []
for attach in message.attachment_ids:
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
+
+ body = message.body_html if message.subtype == 'html' else message.body_text
+ body_alternative = None
+ subtype_alternative = None
+ if message.subtype == 'html' and message.body_text:
+ # we have a plain text alternative prepared, pass it to
+ # build_message instead of letting it build one
+ body_alternative = message.body_text
+ subtype_alternative = 'plain'
+
msg = ir_mail_server.build_email(
email_from=message.email_from,
email_to=to_email(message.email_to),
subject=message.subject,
- body=message.body_html if message.subtype == 'html' else message.body_text,
+ body=body,
+ body_alternative=body_alternative,
email_cc=to_email(message.email_cc),
email_bcc=to_email(message.email_bcc),
reply_to=message.reply_to,
@@ -491,6 +502,7 @@ class mail_message(osv.osv):
references = message.references,
object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
subtype=message.subtype,
+ subtype_alternative=subtype_alternative,
headers=message.headers and literal_eval(message.headers))
res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=message.mail_server_id.id,
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index 96268435810..3169bca76c2 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -1,9 +1,6 @@
-
-
-
mail.message.formmail.message
@@ -144,14 +141,10 @@
src_model="res.partner"
view_id="view_email_message_tree"/>
-
-
-
-
diff --git a/addons/membership/test/test_membership.yml b/addons/membership/test/test_membership.yml
index 75e0f887cfe..27b755f156b 100644
--- a/addons/membership/test/test_membership.yml
+++ b/addons/membership/test/test_membership.yml
@@ -13,36 +13,22 @@
list_price: 80.00
- |
- "Mark Johnson" want to join "Gold Membership".
-- |
- I'm creating new member "Mark Johnson".
+ "Seagate" want to join "Gold Membership", so I'm checking "Current Membership State" of "Seagate". It is an "Non Member" or not.
-
- !record {model: res.partner, id: res_partner_markjohnson0}:
- address:
- - city: paris
- country_id: base.fr
- name: Mark Johnson
- street: 1 rue Rockfeller
- type: invoice
- zip: '75016'
- name: Mark Johnson
-- |
- I'm checking "Current Membership State" of "Mark Johnson". It is an "Non Member" or not.
--
- !assert {model: res.partner, id: res_partner_markjohnson0}:
+ !assert {model: res.partner, id: base.res_partner_seagate}:
- membership_state == 'none', 'Member should be has "Current Membership State" in "Non Member".'
- |
- I'm doing to make membership invoice for "Mark Johnson" on joining "Gold Membership".
+ I'm doing to make membership invoice for "Seagate" on joining "Gold Membership".
-
!python {model: res.partner}: |
- self.create_membership_invoice(cr, uid, [ref("res_partner_markjohnson0")], product_id=ref("product_product_membershipproduct0"), datas={"amount":80.00})
+ self.create_membership_invoice(cr, uid, [ref("base.res_partner_seagate")], product_id=ref("product_product_membershipproduct0"), datas={"amount":80.00})
- |
- I'm checking "Current Membership State" of "Mark Johnson". It is an "Waiting Member" or not.
+ I'm checking "Current Membership State" of "Seagate". It is an "Waiting Member" or not.
-
- !assert {model: res.partner, id: res_partner_markjohnson0}:
+ !assert {model: res.partner, id: base.res_partner_seagate}:
- membership_state == 'waiting', 'Member should be has "Current Membership State" in "Waiting Member".'
- |
- I'm Opening that Invoice which is created for "Mark Johnson".
+ I'm Opening that Invoice which is created for "Seagate".
-
!python {model: res.partner}: |
import netsvc
@@ -52,16 +38,16 @@
membership_line_pool = self.pool.get('membership.membership_line')
membership_pool = self.pool.get('product.product')
- membership_line_ids = membership_line_pool.search(cr, uid, [('membership_id','=',ref('product_product_membershipproduct0')),('partner','=',ref('res_partner_markjohnson0'))])
+ membership_line_ids = membership_line_pool.search(cr, uid, [('membership_id','=',ref('product_product_membershipproduct0')),('partner','=',ref('base.res_partner_seagate'))])
membership_lines = membership_line_pool.browse(cr, uid, membership_line_ids)
assert membership_lines, _('Membership is not registrated.')
membership_line = membership_lines[0]
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'account.invoice', membership_line.account_invoice_id.id, 'invoice_open', cr)
- |
- I'm checking "Current membership state" of "Mark Johnson". It is an "Invoiced Member" or not.
+ I'm checking "Current membership state" of "Seagate". It is an "Invoiced Member" or not.
-
- !assert {model: res.partner, id: res_partner_markjohnson0}:
+ !assert {model: res.partner, id: base.res_partner_seagate}:
- membership_state == 'invoiced', 'Member should be has "Current Membership State" in "Invoiced Member".'
- |
@@ -85,10 +71,10 @@
- membership_state == 'free', 'Member should be has "Current Membership State" in "Free Member".'
- |
- I'm set "Mark Johnson" as a associated member of "Ms. Johnson" and also set Non free member.
+ I'm set "Seagate" as a associated member of "Ms. Johnson" and also set Non free member.
-
!python {model: res.partner}: |
- self.write(cr, uid, [ref("res_partner_msjohnson0")], {'free_member': False, 'associate_member': ref("res_partner_markjohnson0")})
+ self.write(cr, uid, [ref("res_partner_msjohnson0")], {'free_member': False, 'associate_member': ref("base.res_partner_seagate")})
- |
I'm checking "Current membership state" of "Ms. Johnson". It is an "Paid Member" or not.
@@ -108,17 +94,17 @@
type: service
list_price: 50.00
- |
- I'm making invoice of "Mark Johnson" member on joining new membership "Regular Membership".
+ I'm making invoice of "Seagate" member on joining new membership "Regular Membership".
-
!python {model: res.partner}: |
- self.create_membership_invoice(cr, uid, [ref("res_partner_markjohnson0")], product_id=ref("product_product_membershipproduct1"), datas={"amount":50.00})
+ self.create_membership_invoice(cr, uid, [ref("base.res_partner_seagate")], product_id=ref("product_product_membershipproduct1"), datas={"amount":50.00})
- |
- I'm checking "Current membership state" of "Mark Johnson". It is an "Old Member" or not.
+ I'm checking "Current membership state" of "Seagate". It is an "Old Member" or not.
-
- !assert {model: res.partner, id: res_partner_markjohnson0}:
+ !assert {model: res.partner, id: base.res_partner_seagate}:
- membership_state == 'old', 'Member should be has "Current Membership State" in "Old Member".'
- |
- I'm doing to make credit note of invoice which is paid by "Mark Johnson" to cancel membership.
+ I'm doing to make credit note of invoice which is paid by "Seagate" to cancel membership.
-
!python {model: account.invoice}: |
from tools.translate import _
@@ -128,15 +114,15 @@
membership_pool = self.pool.get('product.product')
invoice_refund_pool = self.pool.get('account.invoice.refund')
- membership_line_ids = membership_line_pool.search(cr, uid, [('membership_id','=',ref('product_product_membershipproduct0')),('partner','=',ref('res_partner_markjohnson0'))])
+ membership_line_ids = membership_line_pool.search(cr, uid, [('membership_id','=',ref('product_product_membershipproduct0')),('partner','=',ref('base.res_partner_seagate'))])
membership_lines = membership_line_pool.browse(cr, uid, membership_line_ids)
assert membership_lines, _('Membership is not registrated.')
membership_line = membership_lines[0]
refund_id = invoice_refund_pool.create(cr, uid, {'description': 'Refund of Membership', 'filter_refund': 'refund'}, {'active_id': membership_line.account_invoice_id.id})
invoice_refund_pool.invoice_refund(cr, uid, [refund_id], {'active_id': membership_line.account_invoice_id.id, 'active_ids': [membership_line.account_invoice_id.id]})
- |
- I'm checking "Current membership state" of "Mark Johnson". It is an "Cancelled Member" or not.
+ I'm checking "Current membership state" of "Seagate". It is an "Cancelled Member" or not.
-
- !assert {model: res.partner, id: res_partner_markjohnson0}:
+ !assert {model: res.partner, id: base.res_partner_seagate}:
- membership_state == 'canceled', 'Member should be has "Current Membership State" in "Cancelled Member".'
diff --git a/addons/purchase/__init__.py b/addons/purchase/__init__.py
index 301e32ebca9..66f92a60831 100644
--- a/addons/purchase/__init__.py
+++ b/addons/purchase/__init__.py
@@ -26,6 +26,7 @@ import wizard
import report
import stock
import company
+import edi
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py
index 901fb6d3e98..b4f3a9adf30 100644
--- a/addons/purchase/__openerp__.py
+++ b/addons/purchase/__openerp__.py
@@ -40,7 +40,7 @@ Dashboard for purchase management that includes:
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images' : ['images/purchase_order.jpeg', 'images/purchase_analysis.jpeg', 'images/request_for_quotation.jpeg'],
- 'depends': ['base', 'account', 'stock', 'process', 'procurement'],
+ 'depends': ['stock', 'process', 'procurement'],
'data': [
'security/purchase_security.xml',
'security/ir.model.access.csv',
@@ -58,15 +58,17 @@ Dashboard for purchase management that includes:
'process/purchase_process.xml',
'report/purchase_report_view.xml',
'board_purchase_view.xml',
+ 'edi/purchase_order_action_data.xml',
],
'test': [
- 'test/process/cancel_order.yml',
+ 'test/process/cancel_order.yml',
'test/process/rfq2order2done.yml',
'test/process/generate_invoice_from_reception.yml',
- 'test/process/run_scheduler.yml',
- 'test/process/merge_order.yml',
+ 'test/process/run_scheduler.yml',
+ 'test/process/merge_order.yml',
+ 'test/process/edi_purchase_order.yml',
'test/ui/print_report.yml',
- 'test/ui/duplicate_order.yml',
+ 'test/ui/duplicate_order.yml',
'test/ui/delete_order.yml',
],
'demo': [
diff --git a/addons/purchase/edi/__init__.py b/addons/purchase/edi/__init__.py
new file mode 100644
index 00000000000..cfac0182d76
--- /dev/null
+++ b/addons/purchase/edi/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 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 purchase_order
\ No newline at end of file
diff --git a/addons/purchase/edi/purchase_order.py b/addons/purchase/edi/purchase_order.py
new file mode 100644
index 00000000000..235b02645ed
--- /dev/null
+++ b/addons/purchase/edi/purchase_order.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from datetime import datetime, timedelta
+from dateutil.relativedelta import relativedelta
+
+from osv import fields, osv, orm
+from edi import EDIMixin
+from tools import DEFAULT_SERVER_DATE_FORMAT
+from tools.translate import _
+
+PURCHASE_ORDER_LINE_EDI_STRUCT = {
+ 'name': True,
+ 'date_planned': True,
+ 'product_id': True,
+ 'product_uom': True,
+ 'price_unit': True,
+ 'product_qty': True,
+ 'notes': True,
+
+ # fields used for web preview only - discarded on import
+ 'price_subtotal': True,
+}
+
+PURCHASE_ORDER_EDI_STRUCT = {
+ 'company_id': True, # -> to be changed into partner
+ 'name': True,
+ 'partner_ref': True,
+ 'origin': True,
+ 'date_order': True,
+ 'partner_id': True,
+ #custom: 'partner_address',
+ 'notes': True,
+ 'order_line': PURCHASE_ORDER_LINE_EDI_STRUCT,
+ #custom: currency_id
+
+ # fields used for web preview only - discarded on import
+ 'amount_total': True,
+ 'amount_untaxed': True,
+ 'amount_tax': True,
+}
+
+class purchase_order(osv.osv, EDIMixin):
+ _inherit = 'purchase.order'
+
+ 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)
+ res_company = self.pool.get('res.company')
+ res_partner_address = self.pool.get('res.partner.address')
+ edi_doc_list = []
+ for order in records:
+ # generate the main report
+ self._edi_generate_report_attachment(cr, uid, order, context=context)
+
+ # Get EDI doc based on struct. The result will also contain all metadata fields and attachments.
+ edi_doc = super(purchase_order,self).edi_export(cr, uid, [order], edi_struct, context)[0]
+ edi_doc.update({
+ # force trans-typing to purchase.order upon import
+ '__import_model': 'sale.order',
+ '__import_module': 'sale',
+
+ 'company_address': res_company.edi_export_address(cr, uid, order.company_id, context=context),
+ 'partner_address': res_partner_address.edi_export(cr, uid, [order.partner_address_id], context=context)[0],
+ 'currency': self.pool.get('res.currency').edi_export(cr, uid, [order.pricelist_id.currency_id],
+ context=context)[0],
+ })
+ if edi_doc.get('order_line'):
+ for line in edi_doc['order_line']:
+ line['__import_model'] = 'sale.order.line'
+ 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_address = self.pool.get('res.partner.address')
+ res_partner = self.pool.get('res.partner')
+
+ # imported company = as a new partner
+ src_company_id, src_company_name = edi_document.pop('company_id')
+ partner_id = self.edi_import_relation(cr, uid, 'res.partner', src_company_name,
+ src_company_id, context=context)
+ partner_value = {'customer': True}
+ res_partner.write(cr, uid, [partner_id], partner_value, context=context)
+
+ # imported company_address = new partner address
+ address_info = edi_document.pop('company_address')
+ address_info['partner_id'] = (src_company_id, src_company_name)
+ address_info['type'] = 'default'
+ address_id = res_partner_address.edi_import(cr, uid, address_info, context=context)
+
+ # modify edi_document to refer to new partner/address
+ partner_address = res_partner_address.browse(cr, uid, address_id, context=context)
+ edi_document['partner_id'] = (src_company_id, src_company_name)
+ edi_document.pop('partner_address', False) # ignored
+ edi_document['partner_address_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
+
+ 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
+ partner_model = self.pool.get('res.partner')
+ partner = partner_model.browse(cr, uid, partner_id, context=context)
+ pricelist = partner.property_product_pricelist_purchase
+ if not pricelist:
+ pricelist = self.pool.get('ir.model.data').get_object(cr, uid, 'purchase', 'list0', context=context)
+
+ if not pricelist.currency_id == currency:
+ # look for a pricelist with the right type and currency, or make a new one
+ pricelist_type = 'purchase'
+ product_pricelist = self.pool.get('product.pricelist')
+ match_pricelist_ids = product_pricelist.search(cr, uid,[('type','=',pricelist_type),
+ ('currency_id','=',currency.id)])
+ if match_pricelist_ids:
+ pricelist_id = match_pricelist_ids[0]
+ else:
+ pricelist_name = _('EDI Pricelist (%s)') % (currency.name,)
+ pricelist_id = product_pricelist.create(cr, uid, {'name': pricelist_name,
+ 'type': pricelist_type,
+ 'currency_id': currency.id,
+ })
+ self.pool.get('product.pricelist.version').create(cr, uid, {'name': pricelist_name,
+ 'pricelist_id': pricelist_id})
+ pricelist = product_pricelist.browse(cr, uid, pricelist_id)
+
+ return self.edi_m2o(cr, uid, pricelist, context=context)
+
+ def _edi_get_location(self, cr, uid, partner_id, context=None):
+ partner_model = self.pool.get('res.partner')
+ partner = partner_model.browse(cr, uid, partner_id, context=context)
+ location = partner.property_stock_customer
+ if not location:
+ location = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'stock_location_stock', context=context)
+ return self.edi_m2o(cr, uid, location, context=context)
+
+ def edi_import(self, cr, uid, edi_document, context=None):
+ self._edi_requires_attributes(('company_id','company_address','order_line','date_order','currency'), edi_document)
+
+ #import company as a new partner
+ partner_id = self.edi_import_company(cr, uid, edi_document, context=context)
+
+ # currency for rounding the discount calculations and for the pricelist
+ res_currency = self.pool.get('res.currency')
+ currency_info = edi_document.pop('currency')
+ currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
+ order_currency = res_currency.browse(cr, uid, currency_id)
+
+ partner_ref = edi_document.pop('partner_ref', False)
+ edi_document['partner_ref'] = edi_document['name']
+ edi_document['name'] = partner_ref or edi_document['name']
+ edi_document['pricelist_id'] = self._edi_get_pricelist(cr, uid, partner_id, order_currency, context=context)
+ edi_document['location_id'] = self._edi_get_location(cr, uid, partner_id, context=context)
+
+ # discard web preview fields, if present
+ edi_document.pop('amount_total', None)
+ edi_document.pop('amount_tax', None)
+ edi_document.pop('amount_untaxed', None)
+ edi_document.pop('payment_term', None)
+ edi_document.pop('order_policy', None)
+ edi_document.pop('user_id', None)
+
+ for order_line in edi_document['order_line']:
+ self._edi_requires_attributes(('date_planned', 'product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
+ # original sale order contains unit price and discount, but not final line price
+ discount = order_line.pop('discount', 0.0)
+ if discount:
+ order_line['price_unit'] = res_currency.round(cr, uid, order_currency,
+ (order_line['price_unit'] * (1 - (discount or 0.0) / 100.0)))
+ # sale order lines have sequence numbers, not purchase order lines
+ order_line.pop('sequence', None)
+
+ # discard web preview fields, if present
+ order_line.pop('price_subtotal', None)
+ return super(purchase_order,self).edi_import(cr, uid, edi_document, context=context)
+
+class purchase_order_line(osv.osv, EDIMixin):
+ _inherit='purchase.order.line'
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml
new file mode 100644
index 00000000000..f3f610b8e75
--- /dev/null
+++ b/addons/purchase/edi/purchase_order_action_data.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+ if not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='purchase.email_template_edi_purchase', context=context)
+ code
+ ir.actions.server
+
+ True
+ Auto-email confirmed purchase orders
+
+
+
+
+ Email Templates
+ email.template
+ form
+ form,tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Automated Purchase Order Notification Mail
+ ${object.validator.user_email or ''}
+ ${object.company_id.name} Order (Ref ${object.name or 'n/a' })
+ ${object.partner_address_id.email}
+
+
+
+
+
Hello${object.partner_address_id.name and ' ' or ''}${object.partner_address_id.name or ''},
+
+
Here is a purchase order confirmation from ${object.company_id.name}:
+
+
+ REFERENCES
+ Order number: ${object.name}
+ Order total: ${object.amount_total} ${object.pricelist_id.currency_id.name}
+ Order date: ${object.date_order}
+ % if object.origin:
+ Order reference: ${object.origin}
+ % endif
+ % if object.partner_ref:
+ Your reference: ${object.partner_ref}
+ % endif
+ Your contact: ${object.validator.name}
+
+
+
+ You can view the order confirmation document and download it using the following link:
+