[MERGE] Review/Update EDI features for 7.0:
Some EDI features replaced by the improved 7.0 portal (with auth_signup) ------------------------------------------------------------------------ - remove the static export of EDI document and the persistence of these EDI blobs, replaced by the live portal access to the document (via portal module) - remove all the qweb templates and web controllers that were previously used to give access to the EDI document preview - preserve the web controllers/URLs that were use for the direct EDI import (so that a document from a 6.1 system can still be imported in a 7.0 system) - temporarily remove the EDI export UI, replaced for the time being by the portal view. => It is planned in 7.1 to provide a new EDI export system via email notification and attaching JSON EDI blobs to outgoing emails. Thanks to the mail gateway integration and a whitelist/certificate system it will be possible to setup end-to-end B2B EDI automation. - EDI email notifications now integrated in Sales/Invoicing flows via an explicit "Send by Email" step - PDF version of the document now sent along with the notification (instead of a simple link to view it) - Portal signup link integrated in this notification when the portal module is installed (via glue module sale_portal) Online Payment (Paypal) features preserved in a different form -------------------------------------------------------------- - as the EDI preview was removed, the payment button was moved to a new banner in the portal view of relevant documents, as follows: - addition of a new portal.payment_acquirer model to define online payment gateways such as Paypal, Google Checkout, etc. (Paypal included by default) - new payment banner added to the portal form view of Sale Orders and Customer Invoices, allowing online payment via defined payment_acquirers - new settings item in Settings>Invoicing>Banks&Cash to make the payment banner visible to all employees as well (normally it's only visible in the portal) + minor cleanup and removal of deprecated fields bzr revid: odo@openerp.com-20121120170915-cmb77iubyrwmib2l
This commit is contained in:
commit
437833ba43
|
@ -391,29 +391,33 @@ class account_invoice(osv.osv):
|
||||||
'''
|
'''
|
||||||
This function opens a window to compose an email, with the edi invoice template message loaded by default
|
This function opens a window to compose an email, with the edi invoice template message loaded by default
|
||||||
'''
|
'''
|
||||||
mod_obj = self.pool.get('ir.model.data')
|
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
|
||||||
template = mod_obj.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')
|
ir_model_data = self.pool.get('ir.model.data')
|
||||||
template_id = template and template[1] or False
|
try:
|
||||||
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
|
template_id = ir_model_data.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1]
|
||||||
res_id = res and res[1] or False
|
except ValueError:
|
||||||
|
template_id = False
|
||||||
|
try:
|
||||||
|
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
|
||||||
|
except ValueError:
|
||||||
|
compose_form_id = False
|
||||||
ctx = dict(context)
|
ctx = dict(context)
|
||||||
ctx.update({
|
ctx.update({
|
||||||
'default_model': 'account.invoice',
|
'default_model': 'account.invoice',
|
||||||
'default_res_id': ids[0],
|
'default_res_id': ids[0],
|
||||||
'default_use_template': True,
|
'default_use_template': bool(template_id),
|
||||||
'default_template_id': template_id,
|
'default_template_id': template_id,
|
||||||
'default_composition_mode': 'comment',
|
'default_composition_mode': 'comment',
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
'view_type': 'form',
|
'view_type': 'form',
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'res_model': 'mail.compose.message',
|
'res_model': 'mail.compose.message',
|
||||||
'views': [(res_id, 'form')],
|
'views': [(compose_form_id, 'form')],
|
||||||
'view_id': res_id,
|
'view_id': compose_form_id,
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'context': ctx,
|
'context': ctx,
|
||||||
'nodestroy': True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def confirm_paid(self, cr, uid, ids, context=None):
|
def confirm_paid(self, cr, uid, ids, context=None):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -19,9 +19,8 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from osv import fields, osv, orm
|
from openerp.osv import osv
|
||||||
from edi import EDIMixin
|
from edi import EDIMixin
|
||||||
from edi.models import edi
|
|
||||||
|
|
||||||
INVOICE_LINE_EDI_STRUCT = {
|
INVOICE_LINE_EDI_STRUCT = {
|
||||||
'name': True,
|
'name': True,
|
||||||
|
@ -71,16 +70,6 @@ INVOICE_EDI_STRUCT = {
|
||||||
class account_invoice(osv.osv, EDIMixin):
|
class account_invoice(osv.osv, EDIMixin):
|
||||||
_inherit = 'account.invoice'
|
_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):
|
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||||
"""Exports a supplier or customer invoice"""
|
"""Exports a supplier or customer invoice"""
|
||||||
edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT)
|
edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT)
|
||||||
|
@ -111,8 +100,8 @@ class account_invoice(osv.osv, EDIMixin):
|
||||||
return tax_account
|
return tax_account
|
||||||
|
|
||||||
def _edi_invoice_account(self, cr, uid, partner_id, invoice_type, context=None):
|
def _edi_invoice_account(self, cr, uid, partner_id, invoice_type, context=None):
|
||||||
partner_pool = self.pool.get('res.partner')
|
res_partner = self.pool.get('res.partner')
|
||||||
partner = partner_pool.browse(cr, uid, partner_id, context=context)
|
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||||
if invoice_type in ('out_invoice', 'out_refund'):
|
if invoice_type in ('out_invoice', 'out_refund'):
|
||||||
invoice_account = partner.property_account_receivable
|
invoice_account = partner.property_account_receivable
|
||||||
else:
|
else:
|
||||||
|
@ -136,31 +125,30 @@ class account_invoice(osv.osv, EDIMixin):
|
||||||
self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
|
self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
|
||||||
res_partner = self.pool.get('res.partner')
|
res_partner = self.pool.get('res.partner')
|
||||||
|
|
||||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
xid, company_name = edi_document.pop('company_id')
|
||||||
|
# Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
|
||||||
|
company_address_edi = edi_document.pop('company_address')
|
||||||
|
company_address_edi['name'] = company_name
|
||||||
|
company_address_edi['is_company'] = True
|
||||||
|
company_address_edi['__import_model'] = 'res.partner'
|
||||||
|
company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
|
||||||
|
if company_address_edi.get('logo'):
|
||||||
|
company_address_edi['image'] = company_address_edi.pop('logo')
|
||||||
|
|
||||||
invoice_type = edi_document['type']
|
invoice_type = edi_document['type']
|
||||||
partner_value = {}
|
if invoice_type.startswith('out_'):
|
||||||
if invoice_type in ('out_invoice', 'out_refund'):
|
company_address_edi['customer'] = True
|
||||||
partner_value.update({'customer': True})
|
else:
|
||||||
if invoice_type in ('in_invoice', 'in_refund'):
|
company_address_edi['supplier'] = True
|
||||||
partner_value.update({'supplier': True})
|
partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
|
||||||
|
|
||||||
# imported company_address = new partner address
|
|
||||||
address_info = edi_document.pop('company_address')
|
|
||||||
if 'name' not in address_info:
|
|
||||||
address_info['name'] = src_company_name
|
|
||||||
address_info['type'] = 'invoice'
|
|
||||||
address_info.update(partner_value)
|
|
||||||
address_id = res_partner.edi_import(cr, uid, address_info, context=context)
|
|
||||||
|
|
||||||
# modify edi_document to refer to new partner
|
# modify edi_document to refer to new partner
|
||||||
partner_address = res_partner.browse(cr, uid, address_id, context=context)
|
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||||
address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context)
|
partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
|
||||||
edi_document['partner_id'] = address_edi_m2o
|
edi_document['partner_id'] = partner_edi_m2o
|
||||||
edi_document.pop('partner_address', False) # ignored
|
edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
|
||||||
|
|
||||||
return address_id
|
|
||||||
|
|
||||||
|
return partner_id
|
||||||
|
|
||||||
def edi_import(self, cr, uid, edi_document, context=None):
|
def edi_import(self, cr, uid, edi_document, context=None):
|
||||||
""" During import, invoices will import the company that is provided in the invoice as
|
""" During import, invoices will import the company that is provided in the invoice as
|
||||||
|
@ -200,7 +188,7 @@ class account_invoice(osv.osv, EDIMixin):
|
||||||
invoice_type = invoice_type.startswith('in_') and invoice_type.replace('in_','out_') or invoice_type.replace('out_','in_')
|
invoice_type = invoice_type.startswith('in_') and invoice_type.replace('in_','out_') or invoice_type.replace('out_','in_')
|
||||||
edi_document['type'] = invoice_type
|
edi_document['type'] = invoice_type
|
||||||
|
|
||||||
#import company as a new partner
|
# import company as a new partner
|
||||||
partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
|
partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
|
||||||
|
|
||||||
# Set Account
|
# Set Account
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<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 -->
|
<!-- EDI related Email Templates menu -->
|
||||||
<record model="ir.actions.act_window" id="action_email_templates">
|
<record model="ir.actions.act_window" id="action_email_templates">
|
||||||
<field name="name">Email Templates</field>
|
<field name="name">Email Templates</field>
|
||||||
|
@ -27,22 +16,19 @@
|
||||||
|
|
||||||
</data>
|
</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 -->
|
so users can freely customize/delete them -->
|
||||||
<data noupdate="1">
|
<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 -->
|
<!--Email template -->
|
||||||
<record id="email_template_edi_invoice" model="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 Email</field>
|
||||||
<field name="email_from">${object.user_id.email or object.company_id.email or 'noreply@localhost'}</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="subject">${object.company_id.name} Invoice (Ref ${object.number or 'n/a' })</field>
|
||||||
<field name="email_recipients">${object.partner_id.id}</field>
|
<field name="email_recipients">${object.partner_id.id}</field>
|
||||||
<field name="model_id" ref="account.model_account_invoice"/>
|
<field name="model_id" ref="account.model_account_invoice"/>
|
||||||
<field name="auto_delete" eval="True"/>
|
<field name="auto_delete" eval="True"/>
|
||||||
|
<field name="report_template" ref="account_invoices"/>
|
||||||
|
<field name="report_name">Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||||
<field name="body_html"><![CDATA[
|
<field name="body_html"><![CDATA[
|
||||||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||||
|
|
||||||
|
@ -58,15 +44,11 @@
|
||||||
% if object.origin:
|
% if object.origin:
|
||||||
Order reference: ${object.origin}<br />
|
Order reference: ${object.origin}<br />
|
||||||
% endif
|
% endif
|
||||||
|
% if object.user_id:
|
||||||
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Invoice%20${object.number}">${object.user_id.name}</a>
|
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Invoice%20${object.number}">${object.user_id.name}</a>
|
||||||
|
% endif
|
||||||
</p>
|
</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'):
|
% if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
|
||||||
<%
|
<%
|
||||||
comp_name = quote(object.company_id.name)
|
comp_name = quote(object.company_id.name)
|
||||||
|
|
|
@ -227,7 +227,7 @@
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
<separator string="Bank & Cash"/>
|
<separator string="Bank & Cash"/>
|
||||||
<group>
|
<group name="bank_cash">
|
||||||
<label for="id" string="Configuration"/>
|
<label for="id" string="Configuration"/>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -38,45 +38,46 @@
|
||||||
-
|
-
|
||||||
Then I export the customer invoice
|
Then I export the customer invoice
|
||||||
-
|
-
|
||||||
!python {model: edi.document}: |
|
!python {model: edi.edi}: |
|
||||||
|
import json
|
||||||
invoice_pool = self.pool.get('account.invoice')
|
invoice_pool = self.pool.get('account.invoice')
|
||||||
invoice = invoice_pool.browse(cr, uid, ref("invoice_edi_1"))
|
invoice = invoice_pool.browse(cr, uid, ref("invoice_edi_1"))
|
||||||
token = self.export_edi(cr, uid, [invoice])
|
edi_doc = self.generate_edi(cr, uid, [invoice])
|
||||||
assert token, 'Invalid EDI Token'
|
assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
|
||||||
-
|
-
|
||||||
Then I import a sample EDI document of another customer invoice
|
Then I import a sample EDI document of another customer invoice from OpenERP 7.0
|
||||||
-
|
-
|
||||||
!python {model: account.invoice}: |
|
!python {model: account.invoice}: |
|
||||||
import time
|
import time
|
||||||
edi_document = {
|
edi_document = {
|
||||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms",
|
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.random_invoice_763jsms",
|
||||||
"__module": "account",
|
"__module": "account",
|
||||||
"__model": "account.invoice",
|
"__model": "account.invoice",
|
||||||
"__version": [6,1,0],
|
"__version": [7,0,0],
|
||||||
"internal_number": time.strftime("SAJ/%Y/002"),
|
"internal_number": time.strftime("SAJ/%Y/070"),
|
||||||
"company_address": {
|
"company_address": {
|
||||||
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.main_address",
|
"__id": "base:b33adf8a-decd-11f0-a4de-702a04e25700.main_address",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.partner",
|
"__model": "res.partner",
|
||||||
"city": "Gerompont",
|
"city": "Gerompont",
|
||||||
"name": "Company main address",
|
"name": "Company main address",
|
||||||
"zip": "1367",
|
"zip": "1367",
|
||||||
"country_id": ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.be", "Belgium"],
|
"country_id": ["base:b33adf8a-decd-11f0-a4de-702a04e25700.be", "Belgium"],
|
||||||
"phone": "(+32).81.81.37.00",
|
"phone": "(+32).81.81.37.00",
|
||||||
"street": "Chaussee de Namur 40",
|
"street": "Chaussee de Namur 40",
|
||||||
"bank_ids": [
|
"bank_ids": [
|
||||||
["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 123465789-156113"]
|
["base:b33adf8a-decd-11f0-a4de-702a04e25700.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 70-123465789-156113"]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"company_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_company_test11", "Thomson pvt. ltd."],
|
"company_id": ["account:b33adf8a-decd-11f0-a4de-702a04e25700.res_company_test11", "Thomson pvt. ltd."],
|
||||||
"currency": {
|
"currency": {
|
||||||
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.EUR",
|
"__id": "base:b33adf8a-decd-11f0-a4de-702a04e25700.EUR",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.currency",
|
"__model": "res.currency",
|
||||||
"code": "EUR",
|
"code": "EUR",
|
||||||
"symbol": "€",
|
"symbol": "€",
|
||||||
},
|
},
|
||||||
"partner_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_test20", "Junjun wala"],
|
"partner_id": ["account:b33adf8a-decd-11f0-a4de-702a04e25700.res_partner_test20", "Junjun wala"],
|
||||||
"partner_address": {
|
"partner_address": {
|
||||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
|
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
|
@ -91,7 +92,7 @@
|
||||||
"date_invoice": time.strftime('%Y-%m-%d'),
|
"date_invoice": time.strftime('%Y-%m-%d'),
|
||||||
"name": "sample invoice",
|
"name": "sample invoice",
|
||||||
"tax_line": [{
|
"tax_line": [{
|
||||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_tax-4g4EutbiEMVl",
|
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_tax-4g4EutbiEMVl",
|
||||||
"__module": "account",
|
"__module": "account",
|
||||||
"__model": "account.invoice.tax",
|
"__model": "account.invoice.tax",
|
||||||
"amount": 1000.0,
|
"amount": 1000.0,
|
||||||
|
@ -102,21 +103,21 @@
|
||||||
"invoice_line": [{
|
"invoice_line": [{
|
||||||
"__module": "account",
|
"__module": "account",
|
||||||
"__model": "account.invoice.line",
|
"__model": "account.invoice.line",
|
||||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so",
|
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_line-1RP3so",
|
||||||
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
|
"uos_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_uom_unit", "Unit"],
|
||||||
"name": "PC Assemble SC234",
|
"name": "PC Assemble SC234",
|
||||||
"price_unit": 10.0,
|
"price_unit": 10.0,
|
||||||
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
|
"product_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||||
"quantity": 1.0
|
"quantity": 1.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__module": "account",
|
"__module": "account",
|
||||||
"__model": "account.invoice.line",
|
"__model": "account.invoice.line",
|
||||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5",
|
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_line-u2XV5",
|
||||||
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
|
"uos_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_uom_unit", "Unit"],
|
||||||
"name": "PC on Demand",
|
"name": "PC on Demand",
|
||||||
"price_unit": 100.0,
|
"price_unit": 100.0,
|
||||||
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
|
"product_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_product_5", "[PC-DEM] PC on Demand"],
|
||||||
"quantity": 5.0
|
"quantity": 5.0
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@ -125,12 +126,13 @@
|
||||||
invoice_new = self.browse(cr, uid, invoice_id)
|
invoice_new = self.browse(cr, uid, invoice_id)
|
||||||
|
|
||||||
# check bank info on partner
|
# check bank info on partner
|
||||||
|
assert invoice_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a supplier invoice"
|
||||||
assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||||
bank_info = invoice_new.partner_id.bank_ids[0]
|
bank_info = invoice_new.partner_id.bank_ids[0]
|
||||||
assert bank_info.acc_number == "Sample bank: 123465789-156113", 'Expected "Sample bank: 123465789-156113", got %s' % bank_info.acc_number
|
assert bank_info.acc_number == "Sample bank: 70-123465789-156113", 'Expected "Sample bank: 70-123465789-156113", got %s' % bank_info.acc_number
|
||||||
|
|
||||||
assert invoice_new.partner_id.supplier, 'Imported Partner is not marked as supplier'
|
assert invoice_new.partner_id.supplier, 'Imported Partner is not marked as supplier'
|
||||||
assert invoice_new.reference == time.strftime("SAJ/%Y/002"), "internal number is not stored in reference"
|
assert invoice_new.reference == time.strftime("SAJ/%Y/070"), "internal number is not stored in reference"
|
||||||
assert invoice_new.reference_type == 'none', "reference type is not set to 'none'"
|
assert invoice_new.reference_type == 'none', "reference type is not set to 'none'"
|
||||||
assert invoice_new.internal_number == False, "internal number is not reset"
|
assert invoice_new.internal_number == False, "internal number is not reset"
|
||||||
assert invoice_new.journal_id.id, "journal id is not selected"
|
assert invoice_new.journal_id.id, "journal id is not selected"
|
||||||
|
@ -152,3 +154,111 @@
|
||||||
for inv_tax in invoice_new.tax_line:
|
for inv_tax in invoice_new.tax_line:
|
||||||
assert inv_tax.manual, "tax line not set to manual"
|
assert inv_tax.manual, "tax line not set to manual"
|
||||||
assert inv_tax.account_id, "missing tax line account"
|
assert inv_tax.account_id, "missing tax line account"
|
||||||
|
-
|
||||||
|
Then I import a sample EDI document of another customer invoice from OpenERP 6.1 (to test backwards compatibility)
|
||||||
|
-
|
||||||
|
!python {model: account.invoice}: |
|
||||||
|
import time
|
||||||
|
edi_document = {
|
||||||
|
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms",
|
||||||
|
"__module": "account",
|
||||||
|
"__model": "account.invoice",
|
||||||
|
"__version": [6,1,0],
|
||||||
|
"internal_number": time.strftime("SAJ/%Y/061"),
|
||||||
|
"company_address": {
|
||||||
|
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.main_address",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.partner.address",
|
||||||
|
"city": "Gerompont",
|
||||||
|
"zip": "1367",
|
||||||
|
"country_id": ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.be", "Belgium"],
|
||||||
|
"phone": "(+32).81.81.37.00",
|
||||||
|
"street": "Chaussee de Namur 40",
|
||||||
|
"bank_ids": [
|
||||||
|
["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 123465789-156113"]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"company_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_company_test11", "Thomson pvt. ltd."],
|
||||||
|
"currency": {
|
||||||
|
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.EUR",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.currency",
|
||||||
|
"code": "EUR",
|
||||||
|
"symbol": "€",
|
||||||
|
},
|
||||||
|
"partner_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_test20", "Junjun wala"],
|
||||||
|
"partner_address": {
|
||||||
|
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.partner.address",
|
||||||
|
"phone": "(+32).81.81.37.00",
|
||||||
|
"street": "Chaussee de Namur 40",
|
||||||
|
"city": "Gerompont",
|
||||||
|
"zip": "1367",
|
||||||
|
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||||
|
},
|
||||||
|
"date_invoice": time.strftime('%Y-%m-%d'),
|
||||||
|
"name": "sample invoice",
|
||||||
|
"tax_line": [{
|
||||||
|
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_tax-4g4EutbiEMVl",
|
||||||
|
"__module": "account",
|
||||||
|
"__model": "account.invoice.tax",
|
||||||
|
"amount": 1000.0,
|
||||||
|
"manual": True,
|
||||||
|
"name": "sale tax",
|
||||||
|
}],
|
||||||
|
"type": "out_invoice",
|
||||||
|
"invoice_line": [{
|
||||||
|
"__module": "account",
|
||||||
|
"__model": "account.invoice.line",
|
||||||
|
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so",
|
||||||
|
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "PCE"],
|
||||||
|
"name": "Basic PC",
|
||||||
|
"price_unit": 10.0,
|
||||||
|
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
|
||||||
|
"quantity": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__module": "account",
|
||||||
|
"__model": "account.invoice.line",
|
||||||
|
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5",
|
||||||
|
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "PCE"],
|
||||||
|
"name": "Medium PC",
|
||||||
|
"price_unit": 100.0,
|
||||||
|
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
|
||||||
|
"quantity": 5.0
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
invoice_id = self.edi_import(cr, uid, edi_document, context=context)
|
||||||
|
assert invoice_id, 'EDI import failed'
|
||||||
|
invoice_new = self.browse(cr, uid, invoice_id)
|
||||||
|
|
||||||
|
# check bank info on partner
|
||||||
|
assert invoice_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a supplier invoice"
|
||||||
|
assert len(invoice_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||||
|
bank_info = invoice_new.partner_id.bank_ids[0]
|
||||||
|
assert bank_info.acc_number == "Sample bank: 123465789-156113", 'Expected "Sample bank: 123465789-156113", got %s' % bank_info.acc_number
|
||||||
|
|
||||||
|
assert invoice_new.partner_id.supplier, 'Imported Partner is not marked as supplier'
|
||||||
|
assert invoice_new.reference == time.strftime("SAJ/%Y/061"), "internal number is not stored in reference"
|
||||||
|
assert invoice_new.reference_type == 'none', "reference type is not set to 'none'"
|
||||||
|
assert invoice_new.internal_number == False, "internal number is not reset"
|
||||||
|
assert invoice_new.journal_id.id, "journal id is not selected"
|
||||||
|
assert invoice_new.type == 'in_invoice', "Invoice type was not set properly"
|
||||||
|
assert len(invoice_new.invoice_line) == 2, "invoice lines are not same"
|
||||||
|
for inv_line in invoice_new.invoice_line:
|
||||||
|
if inv_line.name == 'Basic PC':
|
||||||
|
assert inv_line.uos_id.name == "PCE" , "uom is not same"
|
||||||
|
assert inv_line.price_unit == 10 , "price unit is not same"
|
||||||
|
assert inv_line.quantity == 1 , "product qty is not same"
|
||||||
|
assert inv_line.price_subtotal == 10, "price sub total is not same"
|
||||||
|
elif inv_line.name == 'Medium PC':
|
||||||
|
assert inv_line.uos_id.name == "PCE" , "uom is not same"
|
||||||
|
assert inv_line.price_unit == 100 , "price unit is not same"
|
||||||
|
assert inv_line.quantity == 5 , "product qty is not same"
|
||||||
|
assert inv_line.price_subtotal == 500, "price sub total is not same"
|
||||||
|
else:
|
||||||
|
raise AssertionError('unknown invoice line: %s' % inv_line)
|
||||||
|
for inv_tax in invoice_new.tax_line:
|
||||||
|
assert inv_tax.manual, "tax line not set to manual"
|
||||||
|
assert inv_tax.account_id, "missing tax line account"
|
||||||
|
|
|
@ -34,7 +34,6 @@ class Controller(openerp.addons.web.http.Controller):
|
||||||
def retrieve(self, req, dbname, token):
|
def retrieve(self, req, dbname, token):
|
||||||
""" retrieve the user info (name, login or email) corresponding to a signup token """
|
""" retrieve the user info (name, login or email) corresponding to a signup token """
|
||||||
registry = RegistryManager.get(dbname)
|
registry = RegistryManager.get(dbname)
|
||||||
user_info = None
|
|
||||||
with registry.cursor() as cr:
|
with registry.cursor() as cr:
|
||||||
res_partner = registry.get('res.partner')
|
res_partner = registry.get('res.partner')
|
||||||
user_info = res_partner.signup_retrieve_info(cr, openerp.SUPERUSER_ID, token)
|
user_info = res_partner.signup_retrieve_info(cr, openerp.SUPERUSER_ID, token)
|
||||||
|
|
|
@ -23,9 +23,7 @@ import time
|
||||||
import urllib
|
import urllib
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
import openerp
|
|
||||||
from openerp.osv import osv, fields
|
from openerp.osv import osv, fields
|
||||||
from openerp import SUPERUSER_ID
|
|
||||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
from openerp.tools.safe_eval import safe_eval
|
from openerp.tools.safe_eval import safe_eval
|
||||||
|
|
||||||
|
@ -35,7 +33,7 @@ class SignupError(Exception):
|
||||||
def random_token():
|
def random_token():
|
||||||
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
|
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
|
||||||
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
return ''.join(random.choice(chars) for i in xrange(20))
|
return ''.join(random.choice(chars) for _ in xrange(20))
|
||||||
|
|
||||||
def now():
|
def now():
|
||||||
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
@ -51,29 +49,44 @@ class res_partner(osv.Model):
|
||||||
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
|
def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, context=None):
|
||||||
""" determine a signup url for a given partner """
|
""" generate a signup url for the given partner ids and action, possibly overriding
|
||||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
the url state components (menu_id, id, view_type) """
|
||||||
|
|
||||||
# if required, make sure that every partner without user has a valid signup token
|
|
||||||
if context and context.get('signup_valid'):
|
|
||||||
unsigned_ids = [p.id for p in self.browse(cr, uid, ids, context) if not p.user_ids]
|
|
||||||
self.signup_prepare(cr, uid, unsigned_ids, context=context)
|
|
||||||
|
|
||||||
res = dict.fromkeys(ids, False)
|
res = dict.fromkeys(ids, False)
|
||||||
|
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||||
for partner in self.browse(cr, uid, ids, context):
|
for partner in self.browse(cr, uid, ids, context):
|
||||||
|
# when required, make sure the partner has a valid signup token
|
||||||
|
if context and context.get('signup_valid') and not partner.user_ids:
|
||||||
|
self.signup_prepare(cr, uid, [partner.id], context=context)
|
||||||
|
|
||||||
|
action_template = None
|
||||||
|
params = {
|
||||||
|
'action': urllib.quote(action),
|
||||||
|
'db': urllib.quote(cr.dbname),
|
||||||
|
}
|
||||||
if partner.signup_token:
|
if partner.signup_token:
|
||||||
params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token))
|
action_template = "?db=%(db)s#action=%(action)s&token=%(token)s"
|
||||||
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params)
|
params['token'] = urllib.quote(partner.signup_token)
|
||||||
elif partner.user_ids:
|
elif partner.user_ids:
|
||||||
user = partner.user_ids[0]
|
action_template = "?db=%(db)s#action=%(action)s&db=%(db)s&login=%(login)s"
|
||||||
params = (urllib.quote(cr.dbname), urllib.quote(user.login))
|
params['login'] = urllib.quote(partner.user_ids[0].login)
|
||||||
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params)
|
if action_template:
|
||||||
|
if view_type:
|
||||||
|
action_template += '&view_type=%s' % urllib.quote(view_type)
|
||||||
|
if menu_id:
|
||||||
|
action_template += '&menu_id=%s' % urllib.quote(str(menu_id))
|
||||||
|
if res_id:
|
||||||
|
action_template += '&id=%s' % urllib.quote(str(res_id))
|
||||||
|
res[partner.id] = urlparse.urljoin(base_url, action_template % params)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
|
||||||
|
""" proxy for function field towards actual implementation """
|
||||||
|
return self._get_signup_url_for_action(cr, uid, ids, context=context)
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'signup_token': fields.char(size=24, string='Signup Token'),
|
'signup_token': fields.char('Signup Token'),
|
||||||
'signup_expiration': fields.datetime(string='Signup Expiration'),
|
'signup_expiration': fields.datetime('Signup Expiration'),
|
||||||
'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
|
'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
|
||||||
'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
|
'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,6 @@ class crm_lead_forward_to_partner(osv.osv_memory):
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'history_mode': 'latest',
|
'history_mode': 'latest',
|
||||||
'content_subtype': lambda self,cr, uid, context={}: 'html',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_record_data(self, cr, uid, model, res_id, context=None):
|
def get_record_data(self, cr, uid, model, res_id, context=None):
|
||||||
|
|
|
@ -18,20 +18,9 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
import logging
|
from . import controllers
|
||||||
|
from . import models
|
||||||
import models
|
from . import edi_service
|
||||||
import edi_service
|
from .models.edi import EDIMixin, edi
|
||||||
from models.edi import EDIMixin, edi_document
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# web
|
|
||||||
try:
|
|
||||||
import controllers
|
|
||||||
except ImportError:
|
|
||||||
_logger.warning(
|
|
||||||
"""Could not load openerp-web section of EDI, EDI will not behave correctly
|
|
||||||
|
|
||||||
To fix, launch openerp-web in embedded mode""")
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -36,12 +36,10 @@ documentation at http://doc.openerp.com.
|
||||||
'website': 'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
'depends': ['base', 'email_template'],
|
'depends': ['base', 'email_template'],
|
||||||
'icon': '/edi/static/src/img/knowledge.png',
|
'icon': '/edi/static/src/img/knowledge.png',
|
||||||
'data': ['security/ir.model.access.csv'],
|
|
||||||
'test': ['test/edi_partner_test.yml'],
|
'test': ['test/edi_partner_test.yml'],
|
||||||
'js': ['static/src/js/edi.js'],
|
'js': ['static/src/js/edi.js'],
|
||||||
'css': ['static/src/css/edi.css'],
|
'css': ['static/src/css/edi.css'],
|
||||||
'qweb': ['static/src/xml/*.xml'],
|
'qweb': ['static/src/xml/*.xml'],
|
||||||
'installable': True,
|
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,77 +1,25 @@
|
||||||
import json
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
import simplejson
|
import simplejson
|
||||||
import werkzeug.wrappers
|
|
||||||
|
|
||||||
import openerp.addons.web.http as openerpweb
|
import openerp.addons.web.http as openerpweb
|
||||||
import openerp.addons.web.controllers.main as webmain
|
import openerp.addons.web.controllers.main as webmain
|
||||||
|
|
||||||
class EDI(openerpweb.Controller):
|
class EDI(openerpweb.Controller):
|
||||||
# http://hostname:8069/edi/view?db=XXXX&token=XXXXXXXXXXX
|
|
||||||
# http://hostname:8069/edi/import_url?url=URIEncodedURL
|
# http://hostname:8069/edi/import_url?url=URIEncodedURL
|
||||||
_cp_path = "/edi"
|
_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
|
@openerpweb.httprequest
|
||||||
def import_url(self, req, url):
|
def import_url(self, req, url):
|
||||||
d = self.template(req)
|
modules = webmain.module_boot(req) + ['edi']
|
||||||
d["init"] = 's.edi.edi_import("%s");'%(url)
|
modules_str = ','.join(modules)
|
||||||
r = webmain.html_template % d
|
modules_json = simplejson.dumps(modules)
|
||||||
return r
|
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in webmain.manifest_list(req, modules_str, 'js'))
|
||||||
|
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list(req, modules_str, 'css'))
|
||||||
@openerpweb.httprequest
|
return webmain.html_template % {
|
||||||
def download(self, req, db, token):
|
'js': js,
|
||||||
result = req.session.proxy('edi').get_edi_document(db, token)
|
'css': css,
|
||||||
response = werkzeug.wrappers.Response( result, headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))])
|
'modules': modules_json,
|
||||||
return response
|
'init': 's.edi.edi_import("%s");' % url,
|
||||||
|
}
|
||||||
@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
|
@openerpweb.jsonrequest
|
||||||
def import_edi_url(self, req, url):
|
def import_edi_url(self, req, url):
|
||||||
|
@ -81,6 +29,4 @@ class EDI(openerpweb.Controller):
|
||||||
return {"action": webmain.clean_action(req, result[0][2], context)}
|
return {"action": webmain.clean_action(req, result[0][2], context)}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import netsvc
|
|
||||||
import openerp
|
import openerp
|
||||||
|
import openerp.netsvc as netsvc
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -34,10 +34,10 @@ class edi(netsvc.ExportService):
|
||||||
try:
|
try:
|
||||||
registry = openerp.modules.registry.RegistryManager.get(db_name)
|
registry = openerp.modules.registry.RegistryManager.get(db_name)
|
||||||
assert registry, 'Unknown database %s' % db_name
|
assert registry, 'Unknown database %s' % db_name
|
||||||
edi_document = registry['edi.document']
|
edi = registry['edi.edi']
|
||||||
cr = registry.db.cursor()
|
cr = registry.db.cursor()
|
||||||
res = None
|
res = None
|
||||||
res = getattr(edi_document, method_name)(cr, *method_args)
|
res = getattr(edi, method_name)(cr, *method_args)
|
||||||
cr.commit()
|
cr.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.exception('Failed to execute EDI method %s with args %r.', method_name, method_args)
|
_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()
|
cr.close()
|
||||||
return res
|
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):
|
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)
|
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']:
|
if method in ['import_edi_document', 'import_edi_url']:
|
||||||
(db, uid, passwd ) = params[0:3]
|
(db, uid, passwd ) = params[0:3]
|
||||||
openerp.service.security.check(db, uid, passwd)
|
openerp.service.security.check(db, uid, passwd)
|
||||||
elif method in ['get_edi_document']:
|
|
||||||
# No security check for these methods
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
raise KeyError("Method not found: %s." % method)
|
raise KeyError("Method not found: %s." % method)
|
||||||
fn = getattr(self, 'exp_'+method)
|
fn = getattr(self, 'exp_'+method)
|
||||||
|
|
|
@ -24,15 +24,13 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
import openerp
|
import openerp
|
||||||
import openerp.release as release
|
import openerp.release as release
|
||||||
import netsvc
|
import openerp.netsvc as netsvc
|
||||||
import pooler
|
from openerp.osv import osv, fields
|
||||||
from osv import osv,fields,orm
|
|
||||||
from tools.translate import _
|
from tools.translate import _
|
||||||
from tools.safe_eval import safe_eval as eval
|
from tools.safe_eval import safe_eval as eval
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
@ -74,16 +72,9 @@ def last_update_for(record):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class edi_document(osv.osv):
|
class edi(osv.AbstractModel):
|
||||||
_name = 'edi.document'
|
_name = 'edi.edi'
|
||||||
_description = 'EDI Document'
|
_description = 'EDI Subsystem'
|
||||||
_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!')
|
|
||||||
]
|
|
||||||
|
|
||||||
def new_edi_token(self, cr, uid, record):
|
def new_edi_token(self, cr, uid, record):
|
||||||
"""Return a new, random unique token to identify this model record,
|
"""Return a new, random unique token to identify this model record,
|
||||||
|
@ -109,7 +100,7 @@ class edi_document(osv.osv):
|
||||||
"""Generates a final EDI document containing the EDI serialization
|
"""Generates a final EDI document containing the EDI serialization
|
||||||
of the given records, which should all be instances of a Model
|
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
|
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
|
:param list(browse_record) records: records to export as EDI
|
||||||
:return: UTF-8 encoded string containing the serialized records
|
:return: UTF-8 encoded string containing the serialized records
|
||||||
|
@ -120,19 +111,6 @@ class edi_document(osv.osv):
|
||||||
edi_list += record_model_obj.edi_export(cr, uid, [record], context=context)
|
edi_list += record_model_obj.edi_export(cr, uid, [record], context=context)
|
||||||
return self.serialize(edi_list)
|
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):
|
def load_edi(self, cr, uid, edi_documents, context=None):
|
||||||
"""Import the given EDI document structures into the system, using
|
"""Import the given EDI document structures into the system, using
|
||||||
:meth:`~.import_edi`.
|
:meth:`~.import_edi`.
|
||||||
|
@ -171,38 +149,18 @@ class edi_document(osv.osv):
|
||||||
"""
|
"""
|
||||||
return json.loads(edi_documents_string)
|
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):
|
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
|
"""Import a JSON serialized EDI Document string into the system, first retrieving it
|
||||||
from the given ``edi_url`` if provided.
|
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 Document to import. Must not be provided if
|
||||||
``edi_url`` is given.
|
``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.
|
may be retrieved, without authentication.
|
||||||
"""
|
"""
|
||||||
if edi_url:
|
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()
|
edi_document = urllib2.urlopen(edi_url).read()
|
||||||
assert edi_document, 'EDI Document is empty!'
|
assert edi_document, 'EDI Document is empty!'
|
||||||
edi_documents = self.deserialize(edi_document)
|
edi_documents = self.deserialize(edi_document)
|
||||||
|
@ -215,10 +173,10 @@ class EDIMixin(object):
|
||||||
``edi_import()`` and ``edi_export()`` methods to implement their
|
``edi_import()`` and ``edi_export()`` methods to implement their
|
||||||
specific behavior, based on the primitives provided by this mixin."""
|
specific behavior, based on the primitives provided by this mixin."""
|
||||||
|
|
||||||
def _edi_requires_attributes(self, attributes, edi_document):
|
def _edi_requires_attributes(self, attributes, edi):
|
||||||
model_name = edi_document.get('__imported_model') or edi_document.get('__model') or self._name
|
model_name = edi.get('__imported_model') or edi.get('__model') or self._name
|
||||||
for attribute in attributes:
|
for attribute in attributes:
|
||||||
assert edi_document.get(attribute),\
|
assert edi.get(attribute),\
|
||||||
'Attribute `%s` is required in %s EDI documents.' % (attribute, model_name)
|
'Attribute `%s` is required in %s EDI documents.' % (attribute, model_name)
|
||||||
|
|
||||||
# private method, not RPC-exposed as it creates ir.model.data entries as
|
# private method, not RPC-exposed as it creates ir.model.data entries as
|
||||||
|
@ -318,7 +276,6 @@ class EDIMixin(object):
|
||||||
:return: list of dicts containing boilerplate EDI metadata for each record,
|
:return: list of dicts containing boilerplate EDI metadata for each record,
|
||||||
at the corresponding index from ``records``.
|
at the corresponding index from ``records``.
|
||||||
"""
|
"""
|
||||||
data_ids = []
|
|
||||||
ir_attachment = self.pool.get('ir.attachment')
|
ir_attachment = self.pool.get('ir.attachment')
|
||||||
results = []
|
results = []
|
||||||
for record in records:
|
for record in records:
|
||||||
|
@ -398,7 +355,7 @@ class EDIMixin(object):
|
||||||
return [self.edi_m2o(cr, uid, r, context=context) for r in records]
|
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):
|
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.
|
records, and matching the given ``edi_struct``, if provided.
|
||||||
|
|
||||||
:param edi_struct: if provided, edi_struct should be a dictionary
|
:param edi_struct: if provided, edi_struct should be a dictionary
|
||||||
|
@ -443,50 +400,6 @@ class EDIMixin(object):
|
||||||
results.append(edi_dict)
|
results.append(edi_dict)
|
||||||
return results
|
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):
|
def _edi_get_object_by_name(self, cr, uid, name, model_name, context=None):
|
||||||
model = self.pool.get(model_name)
|
model = self.pool.get(model_name)
|
||||||
search_results = model.name_search(cr, uid, name, operator='=', context=context)
|
search_results = model.name_search(cr, uid, name, operator='=', context=context)
|
||||||
|
@ -515,18 +428,20 @@ class EDIMixin(object):
|
||||||
file_name = record.name_get()[0][1]
|
file_name = record.name_get()[0][1]
|
||||||
file_name = re.sub(r'[^a-zA-Z0-9_-]', '_', file_name)
|
file_name = re.sub(r'[^a-zA-Z0-9_-]', '_', file_name)
|
||||||
file_name += ".pdf"
|
file_name += ".pdf"
|
||||||
ir_attachment = self.pool.get('ir.attachment').create(cr, uid,
|
self.pool.get('ir.attachment').create(cr, uid,
|
||||||
{'name': file_name,
|
{
|
||||||
'datas': result,
|
'name': file_name,
|
||||||
'datas_fname': file_name,
|
'datas': result,
|
||||||
'res_model': self._name,
|
'datas_fname': file_name,
|
||||||
'res_id': record.id,
|
'res_model': self._name,
|
||||||
'type': 'binary'},
|
'res_id': record.id,
|
||||||
context=context)
|
'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')
|
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
|
# check attachment data is non-empty and valid
|
||||||
file_data = None
|
file_data = None
|
||||||
try:
|
try:
|
||||||
|
@ -573,8 +488,10 @@ class EDIMixin(object):
|
||||||
if data_ids:
|
if data_ids:
|
||||||
model = self.pool.get(model)
|
model = self.pool.get(model)
|
||||||
data = ir_model_data.browse(cr, uid, data_ids[0], context=context)
|
data = ir_model_data.browse(cr, uid, data_ids[0], context=context)
|
||||||
result = model.browse(cr, uid, data.res_id, context=context)
|
if model.exists(cr, uid, [data.res_id]):
|
||||||
return result
|
return model.browse(cr, uid, data.res_id, context=context)
|
||||||
|
# stale external-id, cleanup to allow re-import, as the corresponding record is gone
|
||||||
|
ir_model_data.unlink(cr, 1, [data_ids[0]])
|
||||||
|
|
||||||
def edi_import_relation(self, cr, uid, model, value, external_id, context=None):
|
def edi_import_relation(self, cr, uid, model, value, external_id, context=None):
|
||||||
"""Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the
|
"""Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the
|
||||||
|
@ -588,6 +505,10 @@ class EDIMixin(object):
|
||||||
* If previous steps gave no result, create a new record with the given
|
* If previous steps gave no result, create a new record with the given
|
||||||
value in the target model, assign it the given external_id, and return
|
value in the target model, assign it the given external_id, and return
|
||||||
the new database ID
|
the new database ID
|
||||||
|
|
||||||
|
:param str value: display name of the record to import
|
||||||
|
:param str external_id: fully-qualified external ID of the record
|
||||||
|
:return: database id of newly-imported or pre-existing record
|
||||||
"""
|
"""
|
||||||
_logger.debug("%s: Importing EDI relationship [%r,%r]", model, external_id, value)
|
_logger.debug("%s: Importing EDI relationship [%r,%r]", model, external_id, value)
|
||||||
target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context)
|
target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context)
|
||||||
|
@ -602,9 +523,11 @@ class EDIMixin(object):
|
||||||
self._name, external_id, value)
|
self._name, external_id, value)
|
||||||
# also need_new_ext_id here, but already been set above
|
# also need_new_ext_id here, but already been set above
|
||||||
model = self.pool.get(model)
|
model = self.pool.get(model)
|
||||||
# should use name_create() but e.g. res.partner won't allow it at the moment
|
res_id, _ = model.name_create(cr, uid, value, context=context)
|
||||||
res_id = model.create(cr, uid, {model._rec_name: value}, context=context)
|
|
||||||
target = model.browse(cr, uid, res_id, context=context)
|
target = model.browse(cr, uid, res_id, context=context)
|
||||||
|
else:
|
||||||
|
_logger.debug("%s: Importing EDI relationship [%r,%r] - record already exists with ID %s, using it",
|
||||||
|
self._name, external_id, value, target.id)
|
||||||
if need_new_ext_id:
|
if need_new_ext_id:
|
||||||
ext_id_members = split_external_id(external_id)
|
ext_id_members = split_external_id(external_id)
|
||||||
# module name is never used bare when creating ir.model.data entries, in order
|
# module name is never used bare when creating ir.model.data entries, in order
|
||||||
|
@ -614,19 +537,19 @@ class EDIMixin(object):
|
||||||
self._edi_external_id(cr, uid, target, existing_id=ext_id_members['id'], existing_module=module, context=context)
|
self._edi_external_id(cr, uid, target, existing_id=ext_id_members['id'], existing_module=module, context=context)
|
||||||
return target.id
|
return target.id
|
||||||
|
|
||||||
def edi_import(self, cr, uid, edi_document, context=None):
|
def edi_import(self, cr, uid, edi, context=None):
|
||||||
"""Imports a dict representing an edi.document into the system.
|
"""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
|
:return: the database ID of the imported record
|
||||||
"""
|
"""
|
||||||
assert self._name == edi_document.get('__import_model') or \
|
assert self._name == edi.get('__import_model') or \
|
||||||
('__import_model' not in edi_document and self._name == edi_document.get('__model')), \
|
('__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 and current model do not match: '%s' (EDI) vs '%s' (current)." % \
|
||||||
(edi_document['__model'], self._name)
|
(edi.get('__model'), self._name)
|
||||||
|
|
||||||
# First check the record is now already known in the database, in which case it is ignored
|
# 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)
|
existing = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context)
|
||||||
if existing:
|
if existing:
|
||||||
_logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full'])
|
_logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full'])
|
||||||
|
@ -634,7 +557,7 @@ class EDIMixin(object):
|
||||||
|
|
||||||
record_values = {}
|
record_values = {}
|
||||||
o2m_todo = {} # o2m values are processed after their parent already exists
|
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
|
# skip metadata and empty fields
|
||||||
if field_name.startswith('__') or field_value is None or field_value is False:
|
if field_name.startswith('__') or field_value is None or field_value is False:
|
||||||
continue
|
continue
|
||||||
|
@ -679,7 +602,7 @@ class EDIMixin(object):
|
||||||
dest_model.edi_import(cr, uid, o2m_line, context=context)
|
dest_model.edi_import(cr, uid, o2m_line, context=context)
|
||||||
|
|
||||||
# process the attachments, if any
|
# 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
|
return record_id
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# 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):
|
class res_company(osv.osv):
|
||||||
"""Helper subclass for res.company providing util methods for working with
|
"""Helper subclass for res.company providing util methods for working with
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# 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 edi import EDIMixin
|
||||||
from openerp import SUPERUSER_ID
|
from openerp import SUPERUSER_ID
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -20,10 +20,9 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from osv import fields,osv
|
from openerp.osv import osv
|
||||||
from edi import EDIMixin
|
from edi import EDIMixin
|
||||||
from openerp import SUPERUSER_ID
|
from openerp import SUPERUSER_ID
|
||||||
from tools.translate import _
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
RES_PARTNER_EDI_STRUCT = {
|
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
|
|
|
|
@ -1,210 +0,0 @@
|
||||||
/** EDI content **/
|
|
||||||
.openerp .company_logo {
|
|
||||||
background-size: 180px 46px;
|
|
||||||
}
|
|
||||||
.oe_edi_view {
|
|
||||||
width: 65%;
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 0px 25px;
|
|
||||||
border-right: 1px solid #D2CFCF;
|
|
||||||
}
|
|
||||||
.oe_edi_sidebar_container {
|
|
||||||
width: 35%;
|
|
||||||
padding: 0px 10px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
button.oe_edi_action_print {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin-left: 35%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
button.oe_edi_action_print img {
|
|
||||||
vertical-align: bottom;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** EDI Sidebar **/
|
|
||||||
.oe_edi_sidebar_title {
|
|
||||||
border-bottom: 1px solid #D2CFCF;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.3em;
|
|
||||||
min-width: 10em;
|
|
||||||
}
|
|
||||||
.oe_edi_nested_block, .oe_edi_nested_block_import, .oe_edi_nested_block_pay {
|
|
||||||
margin: 0px 40px;
|
|
||||||
min-width: 10em;
|
|
||||||
display: none; /* made visible by click on parent input/label */
|
|
||||||
}
|
|
||||||
.oe_edi_right_top .oe_edi_nested_block label {
|
|
||||||
float: left;
|
|
||||||
text-align: right;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
line-height: 180%;
|
|
||||||
font-weight: bold;
|
|
||||||
min-width: 5em;
|
|
||||||
}
|
|
||||||
.oe_edi_option {
|
|
||||||
padding-left: 5px;
|
|
||||||
line-height: 2em;
|
|
||||||
}
|
|
||||||
.oe_edi_option:hover {
|
|
||||||
background: #e8e8e8;
|
|
||||||
}
|
|
||||||
.oe_edi_import_button {
|
|
||||||
margin: 2px 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.oe_edi_small, .oe_edi_small input {
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sidebar bottom **/
|
|
||||||
.oe_edi_paypal_button {
|
|
||||||
margin: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Paperbox, from http://www.sitepoint.com/pure-css3-paper-curl/ **/
|
|
||||||
.oe_edi_paperbox {
|
|
||||||
position: relative;
|
|
||||||
width: 700px;
|
|
||||||
padding: 30px;
|
|
||||||
padding-bottom: 50px;
|
|
||||||
margin: 20px auto;
|
|
||||||
background-color: #fff;
|
|
||||||
-webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), inset 0 0 50px rgba(0, 0, 0, 0.1);
|
|
||||||
-moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), inset 0 0 50px rgba(0, 0, 0, 0.1);
|
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2), inset 0 0 50px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.oe_edi_paperbox:before, .oe_edi_paperbox:after {
|
|
||||||
position: absolute;
|
|
||||||
width: 40%;
|
|
||||||
height: 10px;
|
|
||||||
content: ' ';
|
|
||||||
left: 12px;
|
|
||||||
bottom: 15px;
|
|
||||||
background: transparent;
|
|
||||||
-webkit-transform: skew(-5deg) rotate(-5deg);
|
|
||||||
-moz-transform: skew(-5deg) rotate(-5deg);
|
|
||||||
-ms-transform: skew(-5deg) rotate(-5deg);
|
|
||||||
-o-transform: skew(-5deg) rotate(-5deg);
|
|
||||||
transform: skew(-5deg) rotate(-5deg);
|
|
||||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
|
||||||
-moz-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
|
||||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
.oe_edi_paperbox:after {
|
|
||||||
left: auto; right: 12px;
|
|
||||||
-webkit-transform: skew(5deg) rotate(5deg);
|
|
||||||
-moz-transform: skew(5deg) rotate(5deg);
|
|
||||||
-ms-transform: skew(5deg) rotate(5deg);
|
|
||||||
-o-transform: skew(5deg) rotate(5deg);
|
|
||||||
transform: skew(5deg) rotate(5deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sale Order / Purchase Order Preview **/
|
|
||||||
table.oe_edi_data, .oe_edi_doc_title {
|
|
||||||
border-collapse: collapse;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.oe_edi_data th {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.oe_edi_data .oe_edi_floor {
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
}
|
|
||||||
.oe_edi_data .oe_edi_ceiling {
|
|
||||||
border-top: 1px solid black;
|
|
||||||
}
|
|
||||||
.oe_edi_data .oe_edi_data_row {
|
|
||||||
border-bottom: 1px solid #D2CFCF;
|
|
||||||
}
|
|
||||||
.oe_edi_data_row td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
.oe_edi_inner_note {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 95%;
|
|
||||||
padding-left: 10px;
|
|
||||||
|
|
||||||
/* prevent wide notes from disrupting layout due to <pre> styling */
|
|
||||||
white-space: pre-line;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
.oe_edi_data_row .oe_edi_inner_note {
|
|
||||||
/* prevent wide notes from disrupting layout due to <pre> styling */
|
|
||||||
width: 25em;
|
|
||||||
}
|
|
||||||
.oe_edi_shade {
|
|
||||||
background: #e8e8e8;
|
|
||||||
}
|
|
||||||
.oe_edi_company_name {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.oe_edi_address_from {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.oe_edi_address_to {
|
|
||||||
float: right;
|
|
||||||
margin-top: 25px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
.oe_edi_company_block_title {
|
|
||||||
width: 375px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 2px 14px;
|
|
||||||
background-color: #252525;
|
|
||||||
border-top-left-radius: 5px 5px;
|
|
||||||
border-top-right-radius: 5px 5px;
|
|
||||||
background-repeat: repeat no-repeat;
|
|
||||||
}
|
|
||||||
.oe_edi_company_block_title .oe_edi_company_name {
|
|
||||||
margin: 0px;
|
|
||||||
font-size: 1em;
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
.oe_edi_company_block_body {
|
|
||||||
width: 375px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 5px 14px;
|
|
||||||
line-height: 16px;
|
|
||||||
background-color: rgb(242, 242, 242);
|
|
||||||
}
|
|
||||||
.oe_edi_company_block_body p {
|
|
||||||
color: #222;
|
|
||||||
margin: 5px 0px;
|
|
||||||
}
|
|
||||||
.oe_edi_summary_label {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.oe_edi_summary_value {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Python code highlighting **/
|
|
||||||
/* GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann
|
|
||||||
(http://qbnz.com/highlighter/ and http://geshi.org/) */
|
|
||||||
.python .de1, .python .de2 {font: normal normal 1em/1.2em monospace; margin:0; padding:0; background:none; vertical-align:top;}
|
|
||||||
.python {font-family:monospace;}
|
|
||||||
.python .imp {font-weight: bold; color: red;}
|
|
||||||
.python li, .python .li1 {background: #ffffff; list-style: none;}
|
|
||||||
.python .ln {width:1px;text-align:right;margin:0;padding:0 2px;vertical-align:top;}
|
|
||||||
.python .li2 {background: #f8f8f8;}
|
|
||||||
.python .kw1 {color: #ff7700;font-weight:bold;}
|
|
||||||
.python .kw2 {color: #008000;}
|
|
||||||
.python .kw3 {color: #dc143c;}
|
|
||||||
.python .kw4 {color: #0000cd;}
|
|
||||||
.python .co1 {color: #808080; font-style: italic;}
|
|
||||||
.python .coMULTI {color: #808080; font-style: italic;}
|
|
||||||
.python .es0 {color: #000099; font-weight: bold;}
|
|
||||||
.python .br0 {color: black;}
|
|
||||||
.python .sy0 {color: #66cc66;}
|
|
||||||
.python .st0 {color: #483d8b;}
|
|
||||||
.python .nu0 {color: #ff4500;}
|
|
||||||
.python .me1 {color: black;}
|
|
||||||
.python span.xtra { display:block; }
|
|
||||||
.python ol { padding: 0px; }
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB |
|
@ -1,119 +1,9 @@
|
||||||
openerp.edi = function(openerp) {
|
openerp.edi = function(instance) {
|
||||||
openerp.edi = {}
|
var _t = instance.web._t;
|
||||||
|
instance.edi = {}
|
||||||
|
|
||||||
openerp.edi.EdiView = openerp.web.Widget.extend({
|
instance.edi.EdiImport = instance.web.Widget.extend({
|
||||||
init: function(parent, db, token) {
|
|
||||||
this._super();
|
|
||||||
this.db = db;
|
|
||||||
this.token = token;
|
|
||||||
this.session = openerp.session;
|
|
||||||
this.template = "EdiEmpty";
|
|
||||||
this.content = "";
|
|
||||||
this.sidebar = "";
|
|
||||||
},
|
|
||||||
start: function() {
|
|
||||||
this._super();
|
|
||||||
var self = this;
|
|
||||||
var param = {"db": self.db, "token": self.token};
|
|
||||||
return self.rpc('/edi/get_edi_document', param).done(this.on_document_loaded).fail(this.on_document_failed);
|
|
||||||
},
|
|
||||||
on_document_loaded: function(docs){
|
|
||||||
this.doc = docs[0];
|
|
||||||
var template_content = "Edi." + this.doc.__model + ".content";
|
|
||||||
var template_sidebar = "Edi." + this.doc.__model + ".sidebar";
|
|
||||||
var param = {"widget":this, "doc":this.doc};
|
|
||||||
if (openerp.web.qweb.templates[template_sidebar]) {
|
|
||||||
this.sidebar = openerp.web.qweb.render(template_sidebar, param);
|
|
||||||
}
|
|
||||||
if (openerp.web.qweb.templates[template_content]) {
|
|
||||||
this.content = openerp.web.qweb.render(template_content, param);
|
|
||||||
}
|
|
||||||
this.$el.html(openerp.web.qweb.render("EdiView", param));
|
|
||||||
this.$el.find('button.oe_edi_action_print').bind('click', this.do_print);
|
|
||||||
this.$el.find('button#oe_edi_import_existing').bind('click', this.do_import_existing);
|
|
||||||
this.$el.find('button#oe_edi_import_create').bind('click', this.do_import_create);
|
|
||||||
this.$el.find('button#oe_edi_download').bind('click', this.do_download);
|
|
||||||
this.$el.find('.oe_edi_import_choice, .oe_edi_import_choice_label').bind('click', this.toggle_choice('import'));
|
|
||||||
this.$el.find('.oe_edi_pay_choice, .oe_edi_pay_choice_label').bind('click', this.toggle_choice('pay'));
|
|
||||||
this.$el.find('#oe_edi_download_show_code').bind('click', this.show_code);
|
|
||||||
},
|
|
||||||
on_document_failed: function(response) {
|
|
||||||
var self = this;
|
|
||||||
var params = {
|
|
||||||
error: response,
|
|
||||||
//TODO: should this be _t() wrapped?
|
|
||||||
message: "Sorry, this document cannot be located. Perhaps the link you are using has expired?"
|
|
||||||
}
|
|
||||||
$(openerp.web.qweb.render("DialogWarning", params)).dialog({
|
|
||||||
title: "Document not found",
|
|
||||||
modal: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show_code: function($event) {
|
|
||||||
$('#oe_edi_download_code').toggle();
|
|
||||||
},
|
|
||||||
get_download_url: function() {
|
|
||||||
var l = window.location;
|
|
||||||
var url_prefix = l.protocol + '//' + l.host;
|
|
||||||
return url_prefix +'/edi/download?db=' + this.db + '&token=' + this.token;
|
|
||||||
},
|
|
||||||
get_paypal_url: function(document_type, ref_field) {
|
|
||||||
var comp_name = encodeURIComponent(this.doc.company_id[1]);
|
|
||||||
var doc_ref = encodeURIComponent(this.doc[ref_field]);
|
|
||||||
var paypal_account = encodeURIComponent(this.doc.company_address.paypal_account);
|
|
||||||
var amount = encodeURIComponent(this.doc.amount_total);
|
|
||||||
var cur_code = encodeURIComponent(this.doc.currency.code);
|
|
||||||
var paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick" +
|
|
||||||
"&business=" + paypal_account +
|
|
||||||
"&item_name=" + document_type + "%20" + comp_name + "%20" + doc_ref +
|
|
||||||
"&invoice=" + doc_ref +
|
|
||||||
"&amount=" + amount +
|
|
||||||
"¤cy_code=" + cur_code +
|
|
||||||
"&button_subtype=services&no_note=1&bn=OpenERP_PayNow_" + cur_code;
|
|
||||||
return paypal_url;
|
|
||||||
},
|
|
||||||
toggle_choice: function(mode) {
|
|
||||||
return function($e) {
|
|
||||||
$('.oe_edi_nested_block_'+mode).hide();
|
|
||||||
$('.'+$e.target.id+'_nested').show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
do_print: function(e){
|
|
||||||
var l = window.location;
|
|
||||||
window.location = l.protocol + '//' + l.host + "/edi/download_attachment?db=" + this.db + "&token=" + this.token;
|
|
||||||
},
|
|
||||||
do_import_existing: function(e) {
|
|
||||||
var url_download = this.get_download_url();
|
|
||||||
var $edi_text_server_input = this.$el.find('#oe_edi_txt_server_url');
|
|
||||||
var server_url = $edi_text_server_input.val();
|
|
||||||
$edi_text_server_input.removeClass('invalid');
|
|
||||||
if (!server_url) {
|
|
||||||
$edi_text_server_input.addClass('invalid');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var protocol = "http://";
|
|
||||||
if (server_url.toLowerCase().lastIndexOf('http', 0) == 0 ) {
|
|
||||||
protocol = '';
|
|
||||||
}
|
|
||||||
window.location = protocol + server_url + '/edi/import_url?url=' + encodeURIComponent(url_download);
|
|
||||||
},
|
|
||||||
do_import_create: function(e){
|
|
||||||
var url_download = this.get_download_url();
|
|
||||||
window.location = "https://cc.my.openerp.com/odms/create_edi?url=" + encodeURIComponent(url_download);
|
|
||||||
},
|
|
||||||
do_download: function(e){
|
|
||||||
window.location = this.get_download_url();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
openerp.edi.edi_view = function (db, token) {
|
|
||||||
openerp.session.session_bind().done(function () {
|
|
||||||
new openerp.edi.EdiView(null,db,token).appendTo($("body").addClass('openerp'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
openerp.edi.EdiImport = openerp.web.Widget.extend({
|
|
||||||
init: function(parent,url) {
|
init: function(parent,url) {
|
||||||
this._super();
|
this._super();
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
@ -137,7 +27,7 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
|
||||||
|
|
||||||
show_login: function() {
|
show_login: function() {
|
||||||
this.destroy_content();
|
this.destroy_content();
|
||||||
this.login = new openerp.web.Login(this);
|
this.login = new instance.web.Login(this);
|
||||||
this.login.appendTo(this.$el);
|
this.login.appendTo(this.$el);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -167,18 +57,18 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
|
||||||
window.location = "/";
|
window.location = "/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).html('The document has been successfully imported!');
|
}).html(_t('The document has been successfully imported!'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
on_imported_error: function(response){
|
on_imported_error: function(response){
|
||||||
var self = this;
|
var self = this;
|
||||||
var msg = "Sorry, the document could not be imported.";
|
var msg = _t("Sorry, the document could not be imported.");
|
||||||
if (response.data.fault_code) {
|
if (response.data.fault_code) {
|
||||||
msg += "\n Reason:" + response.data.fault_code;
|
msg += "\n " + _t("Reason:") + response.data.fault_code;
|
||||||
}
|
}
|
||||||
var params = {error: response, message: msg};
|
var params = {error: response, message: msg};
|
||||||
$(openerp.web.qweb.render("CrashManagerWarning", params)).dialog({
|
$(instance.web.qweb.render("CrashManager.warning", params)).dialog({
|
||||||
title: "Document Import Notification",
|
title: _t("Document Import Notification"),
|
||||||
modal: true,
|
modal: true,
|
||||||
buttons: {
|
buttons: {
|
||||||
Ok: function() { $(this).dialog("close"); }
|
Ok: function() { $(this).dialog("close"); }
|
||||||
|
@ -187,9 +77,9 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
openerp.edi.edi_import = function (url) {
|
instance.edi.edi_import = function (url) {
|
||||||
openerp.session.session_bind().done(function () {
|
instance.session.session_bind().done(function () {
|
||||||
new openerp.edi.EdiImport(null,url).appendTo($("body").addClass('openerp'));
|
new instance.edi.EdiImport(null,url).appendTo($("body").addClass('openerp'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
<template>
|
|
||||||
<t t-name="EdiEmpty">
|
|
||||||
<div style="height:100%;"></div>
|
|
||||||
</t>
|
|
||||||
<t t-name="EdiImport">
|
|
||||||
<t t-call="WebClient"/>
|
|
||||||
</t>
|
|
||||||
<t t-name="EdiView">
|
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" id="oe_app" class="oe-application oe_forms oe_semantic_html_override">
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" valign="top" id="oe_header" class="header">
|
|
||||||
<div> <a href="/" class="company_logo_link">
|
|
||||||
<div class="company_logo"
|
|
||||||
t-att-style="'background-size: 180px 46px; background: url('+ (doc.company_address ? '/edi/binary?db='+widget.db+'&token='+widget.token : '/web/static/src/img/logo.png')+')'"/></a> </div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" valign="top" height="100%">
|
|
||||||
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="100%">
|
|
||||||
<tr>
|
|
||||||
<td class="oe_edi_view">
|
|
||||||
<p class="oe_paragraph"><t t-raw="widget.content"/></p>
|
|
||||||
<button type="button" class="oe_edi_action_print">
|
|
||||||
View/Print <img src="/edi/static/src/img/pdf.png"/>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
<td class="oe_edi_sidebar_container">
|
|
||||||
<p class="oe_edi_sidebar_title">
|
|
||||||
Import this document
|
|
||||||
</p>
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_import_openerp" name="oe_edi_import" class="oe_edi_import_choice"/>
|
|
||||||
<label for="oe_edi_import_openerp" id="oe_edi_import_openerp" class="oe_edi_import_choice_label">Import it into an existing OpenERP instance</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_import oe_edi_import_openerp_nested">
|
|
||||||
<label for="oe_edi_txt_server_url">OpenERP instance address:</label>
|
|
||||||
<br/>
|
|
||||||
<input type="text" id="oe_edi_txt_server_url" placeholder="http://example.my.openerp.com/"/><br/>
|
|
||||||
<button type="button" class="oe_edi_import_button" id="oe_edi_import_existing">Import</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_import_saas" name="oe_edi_import" class="oe_edi_import_choice"/>
|
|
||||||
<label for="oe_edi_import_saas" id="oe_edi_import_saas" class="oe_edi_import_choice_label">Import it into a new OpenERP Online instance</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_import oe_edi_import_saas_nested">
|
|
||||||
<button type="button" class="oe_edi_import_button" id="oe_edi_import_create">Create my new OpenERP instance</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_import_download" name="oe_edi_import" class="oe_edi_import_choice"/>
|
|
||||||
<label for="oe_edi_import_download" id="oe_edi_import_download" class="oe_edi_import_choice_label">Import into another application</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_import oe_edi_small oe_edi_import_download_nested">
|
|
||||||
OpenERP's Electronic Data Interchange documents are based on a generic and language
|
|
||||||
independent <a href="http://json.org">JSON</a> 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 <a href="http://doc.openerp.com/search.html?q=edi">OpenERP documentation</a>.
|
|
||||||
<br/>
|
|
||||||
To get started immediately, <a href="#" id="oe_edi_download_show_code">see is all it takes to use this EDI document in Python</a>.
|
|
||||||
</p>
|
|
||||||
<div class="python oe_edi_nested_block_import oe_edi_small" id="oe_edi_download_code" t-translation="off">
|
|
||||||
<ol><li class="li1"><div class="de1"><span class="kw1">import</span> <span class="kw3">urllib2</span><span class="sy0">,</span> simplejson</div></li>
|
|
||||||
<li class="li1"><div class="de1">edi_document <span class="sy0">=</span> <span class="kw3">urllib2</span>.<span class="me1">urlopen</span><span class="br0">(</span><span class="st0">'<t t-esc="widget.get_download_url()"/>'</span><span class="br0">)</span>.<span class="me1">read</span><span class="br0">(</span><span class="br0">)</span></div></li>
|
|
||||||
<li class="li2"><div class="de2">document_data <span class="sy0">=</span> simplejson.<span class="me1">loads</span><span class="br0">(</span>edi_document<span class="br0">)</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span></div></li>
|
|
||||||
<li class="li1"><div class="de1"><span class="kw1">print</span> <span class="st0">"Amount: "</span><span class="sy0">,</span> document_data<span class="br0">[</span><span class="st0">'amount_total'</span><span class="br0">]</span></div></li>
|
|
||||||
</ol></div>
|
|
||||||
<p class="oe_edi_nested_block_import oe_edi_small oe_edi_import_download_nested">
|
|
||||||
You can download the raw EDI document here:<br/>
|
|
||||||
<input type="text" readonly="readonly" t-att-value="widget.get_download_url()"/>
|
|
||||||
<button type="button" class="oe_edi_import_button" id="oe_edi_download">Download</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="oe_edi_right_bottom">
|
|
||||||
<t t-raw="widget.sidebar"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div id="oe_footer" class="oe_footer">
|
|
||||||
<p class="oe_footer_powered">Powered by <a href="http://www.openerp.com">OpenERP</a></p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</t>
|
|
||||||
</template>
|
|
|
@ -1,163 +0,0 @@
|
||||||
<template>
|
|
||||||
<t t-name="Edi.account.invoice.content">
|
|
||||||
<div class="oe_edi_paperbox">
|
|
||||||
<div class="oe_edi_address_from">
|
|
||||||
<div class="oe_edi_company_block_title">
|
|
||||||
<span class="oe_edi_company_name"><t t-esc="doc.company_id[1]"/></span>
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_company_block_body">
|
|
||||||
<p>
|
|
||||||
<t t-if="doc.company_address">
|
|
||||||
<t t-if="doc.company_address.street" t-esc="doc.company_address.street"/><br/>
|
|
||||||
<t t-if="doc.company_address.street2"><t t-esc="doc.company_address.street2"/><br/></t>
|
|
||||||
<t t-if="doc.company_address.zip" t-esc="doc.company_address.zip"/> <t t-if="doc.company_address.city" t-esc="doc.company_address.city"/> <br/>
|
|
||||||
<t t-if="doc.company_address.country_id"><t t-esc="doc.company_address.country_id[1]"/><br/></t>
|
|
||||||
</t>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_address_to">
|
|
||||||
<div class="oe_edi_company_block_title">
|
|
||||||
<span class="oe_edi_company_name"><t t-esc="doc.partner_id[1]"/></span>
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_company_block_body">
|
|
||||||
<p>
|
|
||||||
<t t-if="doc.partner_address">
|
|
||||||
<t t-if="doc.partner_address.street" t-esc="doc.partner_address.street"/><br/>
|
|
||||||
<t t-if="doc.partner_address.street2"><t t-esc="doc.partner_address.street2"/><br/></t>
|
|
||||||
<t t-if="doc.partner_address.zip" t-esc="doc.partner_address.zip"/> <t t-if="doc.partner_address.city" t-esc="doc.partner_address.city"/> <br/>
|
|
||||||
<t t-if="doc.partner_address.country_id"><t t-esc="doc.partner_address.country_id[1]"/><br/></t>
|
|
||||||
</t>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h1 class="oe_edi_doc_title">Invoice <t t-esc="doc.internal_number"/>: <t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/></h1>
|
|
||||||
<table width="100%" class="oe_edi_data oe_edi_shade">
|
|
||||||
<tr class="oe_edi_floor">
|
|
||||||
<th align="left">Description</th>
|
|
||||||
<th align="left">Date</th>
|
|
||||||
<th align="left">Your Reference</th>
|
|
||||||
</tr>
|
|
||||||
<tr class="oe_edi_data_row">
|
|
||||||
<td align="left"><t t-if="doc.name" t-esc="doc.name"/></td>
|
|
||||||
<td align="left"><t t-if="doc.date_invoice" t-esc="doc.date_invoice"/></td>
|
|
||||||
<td align="left"><t t-if="doc.partner_ref" t-esc="doc.partner_ref"/></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<p/>
|
|
||||||
<table width="100%" class="oe_edi_data">
|
|
||||||
<tr class="oe_edi_floor">
|
|
||||||
<th align="left">Product Description</th>
|
|
||||||
<th align="right">Quantity</th>
|
|
||||||
<th align="right">Unit Price</th>
|
|
||||||
<th align="right">Discount</th>
|
|
||||||
<th align="right">Price</th>
|
|
||||||
</tr>
|
|
||||||
<t t-if="doc.invoice_line" t-foreach="doc.invoice_line" t-as="invoice_line">
|
|
||||||
<tr class="oe_edi_data_row">
|
|
||||||
<td align="left"><t t-esc="invoice_line.name"/>
|
|
||||||
<t t-if="invoice_line.note">
|
|
||||||
<pre class="oe_edi_inner_note"><t t-esc="invoice_line.note"/></pre>
|
|
||||||
</t>
|
|
||||||
</td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',invoice_line.quantity)"/> <t t-esc="invoice_line.uos_id[1]"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',invoice_line.price_unit)"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',invoice_line.discount)"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',invoice_line.price_subtotal)"/> <t t-esc="doc.currency.code"/></td>
|
|
||||||
</tr>
|
|
||||||
</t>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<td colspan="2" class="oe_edi_ceiling">
|
|
||||||
<div class="oe_edi_summary_label">
|
|
||||||
Net Total:
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_summary_value">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc.amount_untaxed)"/> <t t-esc="doc.currency.code"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<td colspan="2" class="oe_edi_floor">
|
|
||||||
<div class="oe_edi_summary_label">
|
|
||||||
Taxes:
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_summary_value">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc.amount_tax)"/> <t t-esc="doc.currency.code"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<th colspan="2" class="oe_edi_shade">
|
|
||||||
<div class="oe_edi_summary_label">
|
|
||||||
Total:
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_summary_value">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<t t-if="doc.tax_line">
|
|
||||||
<table class="oe_edi_data" width="40%">
|
|
||||||
<tr class="oe_edi_floor">
|
|
||||||
<th align="left">Tax</th>
|
|
||||||
<th align="right">Base Amount</th>
|
|
||||||
<th align="right">Amount</th>
|
|
||||||
</tr>
|
|
||||||
<t t-if="doc.tax_line"><t t-foreach="doc.tax_line" t-as="tax_line">
|
|
||||||
<tr class="oe_edi_data_row">
|
|
||||||
<td align="left"><t t-esc="tax_line.name"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',tax_line.base_amount)"/> <t t-esc="doc.currency.code"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',tax_line.amount)"/> <t t-esc="doc.currency.code"/></td>
|
|
||||||
</tr>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</table>
|
|
||||||
</t>
|
|
||||||
<t t-if="doc.comment">
|
|
||||||
<p>Notes:</p>
|
|
||||||
<pre class="oe_edi_inner_note"><t t-esc="doc.comment"/></pre>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-name="Edi.account.invoice.sidebar">
|
|
||||||
<t t-if="!doc.reconciled && (doc.type == 'out_invoice' or doc.type == 'in_refund')">
|
|
||||||
<t t-if="doc.company_address.paypal_account || doc.company_address.bank_ids">
|
|
||||||
<p class="oe_edi_sidebar_title">Pay Online</p>
|
|
||||||
<t t-if="doc.company_address.paypal_account">
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_paypal" name="oe_edi_pay" class="oe_edi_pay_choice"/>
|
|
||||||
<label for="oe_edi_paypal" id="oe_edi_paypal" class="oe_edi_pay_choice_label">Paypal</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_pay oe_edi_paypal_nested">
|
|
||||||
You may directly pay this invoice online via Paypal's secure payment gateway:<br/>
|
|
||||||
<a t-att-href="widget.get_paypal_url('Invoice','internal_number')" target="_new">
|
|
||||||
<img class="oe_edi_paypal_button" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</t>
|
|
||||||
<t t-if="doc.company_address.bank_ids">
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_pay_wire" name="oe_edi_pay" class="oe_edi_pay_choice"/>
|
|
||||||
<label for="oe_edi_pay_wire" id="oe_edi_pay_wire" class="oe_edi_pay_choice_label">Bank Wire Transfer</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_pay oe_edi_pay_wire_nested">
|
|
||||||
Please transfer <strong><t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/></strong> to
|
|
||||||
<strong><t t-esc="doc.company_id[1]"/></strong> (postal address on the invoice header)
|
|
||||||
using one of the following bank accounts. Be sure to mention the invoice
|
|
||||||
reference <strong><t t-esc="doc.internal_number"/></strong> on the transfer:
|
|
||||||
<br/><br/>
|
|
||||||
</p>
|
|
||||||
<ul class="oe_edi_nested_block_pay oe_edi_pay_wire_nested">
|
|
||||||
<t t-foreach="doc.company_address.bank_ids" t-as="bank_info">
|
|
||||||
<li><t t-esc="bank_info[1]"/></li>
|
|
||||||
</t>
|
|
||||||
</ul>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</template>
|
|
|
@ -1,169 +0,0 @@
|
||||||
<template>
|
|
||||||
<t t-name="Edi.sale.order.content">
|
|
||||||
<div class="oe_edi_paperbox">
|
|
||||||
<div class="oe_edi_address_from">
|
|
||||||
<div class="oe_edi_company_block_title">
|
|
||||||
<span class="oe_edi_company_name"><t t-esc="doc.company_id[1]"/></span>
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_company_block_body">
|
|
||||||
<p>
|
|
||||||
<t t-if="doc.company_address">
|
|
||||||
<t t-if="doc.company_address.street" t-esc="doc.company_address.street"/><br/>
|
|
||||||
<t t-if="doc.company_address.street2"><t t-esc="doc.company_address.street2"/><br/></t>
|
|
||||||
<t t-if="doc.company_address.zip" t-esc="doc.company_address.zip"/> <t t-if="doc.company_address.city" t-esc="doc.company_address.city"/> <br/>
|
|
||||||
<t t-if="doc.company_address.country_id"><t t-esc="doc.company_address.country_id[1]"/><br/></t>
|
|
||||||
</t>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="oe_edi_address_to">
|
|
||||||
<div class="oe_edi_company_block_title">
|
|
||||||
<span class="oe_edi_company_name"><t t-esc="doc.partner_id[1]"/></span>
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_company_block_body">
|
|
||||||
<p>
|
|
||||||
<t t-if="doc.partner_address">
|
|
||||||
<t t-if="doc.partner_address.street" t-esc="doc.partner_address.street"/><br/>
|
|
||||||
<t t-if="doc.partner_address.street2"><t t-esc="doc.partner_address.street2"/><br/></t>
|
|
||||||
<t t-if="doc.partner_address.zip" t-esc="doc.partner_address.zip"/> <t t-if="doc.partner_address.city" t-esc="doc.partner_address.city"/> <br/>
|
|
||||||
<t t-if="doc.partner_address.country_id"><t t-esc="doc.partner_address.country_id[1]"/><br/></t>
|
|
||||||
</t>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<t t-if="(doc.state == 'draft' or doc.state == 'sent') and doc.__model == 'sale.order'">
|
|
||||||
<h1 class="oe_edi_doc_title">Quotation <t t-esc="doc.name"/>: <t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/></h1>
|
|
||||||
</t>
|
|
||||||
<t t-if="(doc.state == 'draft' or doc.state == 'sent') and doc.__model == 'purchase.order'">
|
|
||||||
<h1 class="oe_edi_doc_title">Request for Quotation <t t-esc="doc.name"/>: <t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/></h1>
|
|
||||||
</t>
|
|
||||||
<t t-if="(doc.state != 'draft' and doc.state != 'sent')">
|
|
||||||
<h1 class="oe_edi_doc_title">Order <t t-esc="doc.name"/>: <t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/></h1>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
<table width="100%" class="oe_edi_data oe_edi_shade">
|
|
||||||
<tr class="oe_edi_floor">
|
|
||||||
<th align="left">Your Reference</th>
|
|
||||||
<th align="left">Date</th>
|
|
||||||
<th align="left">Salesperson</th>
|
|
||||||
<th align="left">Payment terms</th>
|
|
||||||
</tr>
|
|
||||||
<tr class="oe_edi_data_row">
|
|
||||||
<td align="left"><t t-if="doc.partner_ref" t-esc="doc.partner_ref"/></td>
|
|
||||||
<td align="left"><t t-esc="doc.date_order"/></td>
|
|
||||||
<td align="left"><t t-if="doc.user_id" t-esc="doc.user_id[1]"/></td>
|
|
||||||
<td align="left">
|
|
||||||
<t t-if="doc.payment_term" t-esc="doc.payment_term[1]"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<p/>
|
|
||||||
<table width="100%" class="oe_edi_data">
|
|
||||||
<tr class="oe_edi_floor">
|
|
||||||
<th align="left">Product Description</th>
|
|
||||||
<th align="right">Quantity</th>
|
|
||||||
<th align="right">Unit Price</th>
|
|
||||||
<th align="right">Discount(%)</th>
|
|
||||||
<th align="right">Price</th>
|
|
||||||
</tr>
|
|
||||||
<t t-if="doc.order_line" t-foreach="doc.order_line" t-as="doc_line">
|
|
||||||
<tr class="oe_edi_data_row">
|
|
||||||
<td align="left"><t t-esc="doc_line.name"/>
|
|
||||||
<t t-if="doc_line.notes">
|
|
||||||
<pre class="oe_edi_inner_note"><t t-esc="doc_line.notes"/></pre>
|
|
||||||
</t>
|
|
||||||
</td>
|
|
||||||
<td align="right">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc_line.product_qty)"/> <t t-esc="doc_line.product_uom[1]"/>
|
|
||||||
</td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',doc_line.price_unit)"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',doc_line.discount or 0.0)"/></td>
|
|
||||||
<td align="right"><t t-esc="_.str.sprintf('%.2f',doc_line.price_subtotal)"/> <t t-esc="doc.currency.code"/></td>
|
|
||||||
</tr>
|
|
||||||
</t>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<td colspan="2" class="oe_edi_ceiling">
|
|
||||||
<div class="oe_edi_summary_label">
|
|
||||||
Net Total:
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_summary_value">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc.amount_untaxed)"/> <t t-esc="doc.currency.code"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<td colspan="2" class="oe_edi_floor">
|
|
||||||
<div class="oe_edi_summary_label">
|
|
||||||
Taxes:
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_summary_value">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc.amount_tax)"/> <t t-esc="doc.currency.code"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3"></td>
|
|
||||||
<th colspan="2" class="oe_edi_shade">
|
|
||||||
<div class="oe_edi_summary_label">
|
|
||||||
Total:
|
|
||||||
</div>
|
|
||||||
<div class="oe_edi_summary_value">
|
|
||||||
<t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<t t-if="doc.notes">
|
|
||||||
<p>Notes:</p>
|
|
||||||
<pre class="oe_edi_inner_note"><t t-esc="doc.notes"/></pre>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-name="Edi.sale.order.sidebar">
|
|
||||||
<t t-if="doc.order_policy and (doc.order_policy == 'prepaid' || doc.order_policy == 'manual') and (doc.state != 'draft' and doc.state != 'sent')">
|
|
||||||
<t t-if="doc.company_address.paypal_account || doc.company_address.bank_ids">
|
|
||||||
<p class="oe_edi_sidebar_title">Pay Online</p>
|
|
||||||
<t t-if="doc.company_address.paypal_account">
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_paypal" name="oe_edi_pay" class="oe_edi_pay_choice"/>
|
|
||||||
<label for="oe_edi_paypal" id="oe_edi_paypal" class="oe_edi_pay_choice_label">Paypal</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_pay oe_edi_paypal_nested">
|
|
||||||
You may directly pay this order online via Paypal's secure payment gateway:<br/>
|
|
||||||
<a t-att-href="widget.get_paypal_url('Sale Order','name')" target="_new">
|
|
||||||
<img class="oe_edi_paypal_button" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</t>
|
|
||||||
<t t-if="doc.company_address.bank_ids">
|
|
||||||
<div class="oe_edi_option">
|
|
||||||
<input type="radio" id="oe_edi_pay_wire" name="oe_edi_pay" class="oe_edi_pay_choice"/>
|
|
||||||
<label for="oe_edi_pay_wire" id="oe_edi_pay_wire" class="oe_edi_pay_choice_label">Bank Wire Transfer</label>
|
|
||||||
</div>
|
|
||||||
<p class="oe_edi_nested_block_pay oe_edi_pay_wire_nested">
|
|
||||||
Please transfer <strong><t t-esc="_.str.sprintf('%.2f',doc.amount_total)"/> <t t-esc="doc.currency.code"/></strong> to
|
|
||||||
<strong><t t-esc="doc.company_id[1]"/></strong> (postal address on the order header)
|
|
||||||
using one of the following bank accounts. Be sure to mention the document
|
|
||||||
reference <strong><t t-esc="doc.name"/></strong> on the transfer:
|
|
||||||
<br/><br/>
|
|
||||||
</p>
|
|
||||||
<ul class="oe_edi_nested_block_pay oe_edi_pay_wire_nested">
|
|
||||||
<t t-foreach="doc.company_address.bank_ids" t-as="bank_info">
|
|
||||||
<li><t t-esc="bank_info[1]"/></li>
|
|
||||||
</t>
|
|
||||||
</ul>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
<t t-name="Edi.purchase.order.content">
|
|
||||||
<t t-call="Edi.sale.order.content"/>
|
|
||||||
</t>
|
|
||||||
<t t-name="Edi.purchase.order.sidebar">
|
|
||||||
<t t-call="Edi.sale.order.sidebar"/>
|
|
||||||
</t>
|
|
||||||
</template>
|
|
|
@ -6,11 +6,10 @@
|
||||||
with an attached file, check the result, the alter the data
|
with an attached file, check the result, the alter the data
|
||||||
and reimport it.
|
and reimport it.
|
||||||
-
|
-
|
||||||
!python {model: edi.document}: |
|
!python {model: edi.edi}: |
|
||||||
import json
|
import json
|
||||||
partner_obj = self.pool.get('res.partner')
|
res_partner = self.pool.get('res.partner')
|
||||||
tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_2'))])
|
doc = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, ref('base.res_partner_2'))])
|
||||||
doc = self.get_document(cr, uid, tokens[0], context=context)
|
|
||||||
edi_doc, = json.loads(doc)
|
edi_doc, = json.loads(doc)
|
||||||
|
|
||||||
# check content of the document
|
# 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)
|
"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
|
# 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.generate_edi(cr, uid, [res_partner.browse(cr, uid, result[1])])
|
||||||
doc_output = self.get_document(cr, uid, tokens[0], context=context)
|
|
||||||
edi_doc_output, = json.loads(doc_output)
|
edi_doc_output, = json.loads(doc_output)
|
||||||
for attribute in ('__model', '__module', '__id', 'name', '__attachments'):
|
for attribute in ('__model', '__module', '__id', 'name', '__attachments'):
|
||||||
assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \
|
assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \
|
||||||
|
|
|
@ -100,25 +100,27 @@ class test_message_compose(test_mail_mockup.TestMailMockups):
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
||||||
# 1. Comment on pigs
|
# 1. Comment on pigs
|
||||||
compose_id = mail_compose.create(cr, uid,
|
context = {
|
||||||
{'subject': 'Forget me subject', 'body': 'Dummy body'},
|
'default_composition_mode': 'comment',
|
||||||
{'default_composition_mode': 'comment',
|
'default_model': 'mail.group',
|
||||||
'default_model': 'mail.group',
|
'default_res_id': self.group_pigs_id,
|
||||||
'default_res_id': self.group_pigs_id,
|
'default_use_template': False,
|
||||||
'default_use_template': False,
|
'default_template_id': email_template_id,
|
||||||
'default_template_id': email_template_id,
|
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
}
|
||||||
compose = mail_compose.browse(cr, uid, compose_id)
|
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||||
|
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||||
# 2. Perform 'toggle_template', to set use_template and use template_id
|
onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
|
||||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||||
|
onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
|
||||||
|
compose.write(onchange_res)
|
||||||
compose.refresh()
|
compose.refresh()
|
||||||
|
|
||||||
message_pids = [partner.id for partner in compose.partner_ids]
|
message_pids = [partner.id for partner in compose.partner_ids]
|
||||||
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
|
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
|
||||||
# Test: mail.compose.message: subject, body, content_subtype, partner_ids
|
# Test: mail.compose.message: subject, body, partner_ids
|
||||||
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
|
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
|
||||||
self.assertEqual(compose.body, _body_html1, 'mail.compose.message body incorrect')
|
self.assertEqual(compose.body, _body_html1, 'mail.compose.message body incorrect')
|
||||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
|
|
||||||
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
||||||
# Test: mail.compose.message: attachments
|
# Test: mail.compose.message: attachments
|
||||||
# Test: mail.message: attachments
|
# Test: mail.message: attachments
|
||||||
|
@ -128,41 +130,34 @@ class test_message_compose(test_mail_mockup.TestMailMockups):
|
||||||
self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
|
self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
|
||||||
'mail.message attachment name / data incorrect')
|
'mail.message attachment name / data incorrect')
|
||||||
|
|
||||||
# 3. Perform 'toggle_template': template is not set anymore
|
|
||||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
|
||||||
compose.refresh()
|
|
||||||
# Test: subject, body, partner_ids
|
|
||||||
self.assertEqual(compose.subject, False, 'mail.compose.message subject incorrect')
|
|
||||||
self.assertEqual(compose.body, '', 'mail.compose.message body incorrect')
|
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# CASE3: mass_mail with template
|
# CASE3: mass_mail with template
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
||||||
# 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
|
# 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
|
||||||
compose_id = mail_compose.create(cr, uid,
|
context = {
|
||||||
{'subject': 'Forget me subject', 'body': 'Dummy body'},
|
'default_composition_mode': 'mass_mail',
|
||||||
{'default_composition_mode': 'mass_mail',
|
'default_model': 'mail.group',
|
||||||
'default_model': 'mail.group',
|
'default_res_id': self.group_pigs_id,
|
||||||
'default_res_id': self.group_pigs_id,
|
'default_template_id': email_template_id,
|
||||||
'default_use_template': False,
|
'default_partner_ids': [p_a_id],
|
||||||
'default_template_id': email_template_id,
|
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||||
'default_partner_ids': [p_a_id],
|
}
|
||||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||||
compose = mail_compose.browse(cr, uid, compose_id)
|
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||||
|
onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
|
||||||
# 2. Perform 'toggle_template', to set use_template and use template_id
|
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
|
||||||
|
compose.write(onchange_res)
|
||||||
compose.refresh()
|
compose.refresh()
|
||||||
|
|
||||||
message_pids = [partner.id for partner in compose.partner_ids]
|
message_pids = [partner.id for partner in compose.partner_ids]
|
||||||
partner_ids = [p_a_id]
|
partner_ids = [p_a_id]
|
||||||
# Test: mail.compose.message: subject, body, content_subtype, partner_ids
|
|
||||||
self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
|
self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
|
||||||
self.assertEqual(compose.body, '${object.description}', 'mail.compose.message body incorrect')
|
self.assertEqual(compose.body, '${object.description}', 'mail.compose.message body incorrect')
|
||||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
|
|
||||||
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
||||||
|
|
||||||
# 3. Post the comment, get created message
|
# 2. Post the comment, get created message
|
||||||
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||||
group_pigs.refresh()
|
group_pigs.refresh()
|
||||||
group_bird.refresh()
|
group_bird.refresh()
|
||||||
|
|
|
@ -19,11 +19,8 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import tools
|
from openerp import tools
|
||||||
|
from openerp.osv import osv, fields
|
||||||
from osv import osv
|
|
||||||
from osv import fields
|
|
||||||
|
|
||||||
|
|
||||||
def _reopen(self, res_id, model):
|
def _reopen(self, res_id, model):
|
||||||
return {'type': 'ir.actions.act_window',
|
return {'type': 'ir.actions.act_window',
|
||||||
|
@ -39,7 +36,6 @@ def _reopen(self, res_id, model):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class mail_compose_message(osv.TransientModel):
|
class mail_compose_message(osv.TransientModel):
|
||||||
_inherit = 'mail.compose.message'
|
_inherit = 'mail.compose.message'
|
||||||
|
|
||||||
|
@ -60,37 +56,19 @@ class mail_compose_message(osv.TransientModel):
|
||||||
record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context)
|
record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context)
|
||||||
return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')]
|
return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')]
|
||||||
|
|
||||||
def default_get(self, cr, uid, fields, context=None):
|
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
|
||||||
result['template_id'] = context.get('default_template_id', context.get('mail.compose.template_id', False))
|
|
||||||
|
|
||||||
# pre-render the template if any
|
|
||||||
if result.get('use_template') and result.get('template_id'):
|
|
||||||
onchange_res = self.onchange_use_template(cr, uid, [], result.get('use_template'), result.get('template_id'),
|
|
||||||
result.get('composition_mode'), result.get('model'), result.get('res_id'), context=context)
|
|
||||||
result.update(onchange_res['value'])
|
|
||||||
return result
|
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'use_template': fields.boolean('Use Template'),
|
|
||||||
# incredible hack of the day: size=-1 means we want an int db column instead of an str one
|
# incredible hack of the day: size=-1 means we want an int db column instead of an str one
|
||||||
'template_id': fields.selection(_get_templates, 'Template', size=-1),
|
'template_id': fields.selection(_get_templates, 'Template', size=-1),
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaults = {
|
def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None):
|
||||||
'use_template': True,
|
""" - mass_mailing: we cannot render, so return the template values
|
||||||
}
|
- normal mode: return rendered values """
|
||||||
|
if template_id and composition_mode == 'mass_mail':
|
||||||
def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
|
|
||||||
""" - use_template not set: return default_get
|
|
||||||
- use_template set in mass_mailing: we cannot render, so return the template values
|
|
||||||
- use_template set: return rendered values """
|
|
||||||
if use_template and template_id and composition_mode == 'mass_mail':
|
|
||||||
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
|
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
|
||||||
values.pop('id')
|
values.pop('id')
|
||||||
elif use_template and template_id:
|
elif template_id:
|
||||||
|
# FIXME odo: change the mail generation to avoid attachment duplication
|
||||||
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
||||||
# transform attachments into attachment_ids
|
# transform attachments into attachment_ids
|
||||||
values['attachment_ids'] = []
|
values['attachment_ids'] = []
|
||||||
|
@ -102,39 +80,16 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'datas_fname': attach_fname,
|
'datas_fname': attach_fname,
|
||||||
'res_model': model,
|
'res_model': model,
|
||||||
'res_id': res_id,
|
'res_id': res_id,
|
||||||
'type': 'binary', # overwrite the context default_value
|
'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))
|
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
||||||
else:
|
else:
|
||||||
values = self.default_get(cr, uid, ['body', 'body_html', 'subject', 'partner_ids', 'attachment_ids'], context=context)
|
values = self.default_get(cr, uid, ['body', 'subject', 'partner_ids', 'attachment_ids'], context=context)
|
||||||
|
|
||||||
if values.get('body_html'):
|
if values.get('body_html'):
|
||||||
values['body'] = values.pop('body_html')
|
values['body'] = values.pop('body_html')
|
||||||
values.update(use_template=use_template, template_id=template_id)
|
|
||||||
return {'value': values}
|
return {'value': values}
|
||||||
|
|
||||||
def toggle_template(self, cr, uid, ids, context=None):
|
|
||||||
""" hit toggle template mode button: calls onchange_use_template to
|
|
||||||
emulate an on_change, then writes the values to update the form. """
|
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
|
||||||
onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template,
|
|
||||||
record.template_id, record.composition_mode, record.model, record.res_id, context=context).get('value', {})
|
|
||||||
# update partner_ids and attachment_ids
|
|
||||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
|
||||||
onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
|
|
||||||
record.write(onchange_res)
|
|
||||||
return _reopen(self, record.id, record.model)
|
|
||||||
|
|
||||||
def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
|
|
||||||
""" onchange_use_template (values: True or False). If use_template is
|
|
||||||
False, we do as an onchange with template_id False for values """
|
|
||||||
values = self.onchange_template_id(cr, uid, ids, use_template,
|
|
||||||
template_id, composition_mode, model, res_id, context=context)
|
|
||||||
# force html when using templates
|
|
||||||
if use_template:
|
|
||||||
values['value']['content_subtype'] = 'html'
|
|
||||||
return values
|
|
||||||
|
|
||||||
def save_as_template(self, cr, uid, ids, context=None):
|
def save_as_template(self, cr, uid, ids, context=None):
|
||||||
""" hit save as template button: current form value will be a new
|
""" hit save as template button: current form value will be a new
|
||||||
template attached to the current document. """
|
template attached to the current document. """
|
||||||
|
@ -155,7 +110,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
|
'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
|
||||||
}
|
}
|
||||||
template_id = email_template.create(cr, uid, values, context=context)
|
template_id = email_template.create(cr, uid, values, context=context)
|
||||||
record.write(record.onchange_template_id(True, template_id, record.composition_mode, record.model, record.res_id)['value'])
|
record.write(record.onchange_template_id(template_id, record.composition_mode, record.model, record.res_id)['value'])
|
||||||
return _reopen(self, record.id, record.model)
|
return _reopen(self, record.id, record.model)
|
||||||
|
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
@ -167,7 +122,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||||
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
||||||
# filter template values
|
# filter template values
|
||||||
fields = ['body', 'body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachments']
|
fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachments']
|
||||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||||
values['body'] = values.pop('body_html', '')
|
values['body'] = values.pop('body_html', '')
|
||||||
# transform email_to, email_cc into partner_ids
|
# transform email_to, email_cc into partner_ids
|
||||||
|
|
|
@ -7,25 +7,17 @@
|
||||||
<field name="model">mail.compose.message</field>
|
<field name="model">mail.compose.message</field>
|
||||||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<data>
|
<xpath expr="//footer" position="inside">
|
||||||
<xpath expr="//field[@name='content_subtype']" position="after">
|
<group class="oe_right" col="1">
|
||||||
<field name="use_template" invisible="1"
|
<div>Use template
|
||||||
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
|
<field name="template_id" nolabel="1"
|
||||||
</xpath>
|
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)"/>
|
||||||
<xpath expr="//footer" position="inside">
|
</div>
|
||||||
<group class="oe_right" col="1">
|
<button icon="/email_template/static/src/img/email_template_save.png"
|
||||||
<div attrs="{'invisible':[('use_template','=',False)]}">Use template
|
type="object" name="save_as_template" string="Save as new template" class="oe_link"
|
||||||
<field name="template_id" attrs="{'invisible':[('use_template','=',False)]}"
|
help="Save as a new template"/>
|
||||||
nolabel="1"
|
</group>
|
||||||
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
|
</xpath>
|
||||||
</div>
|
|
||||||
<button icon="/email_template/static/src/img/email_template_save.png"
|
|
||||||
type="object" name="save_as_template" string="Save as new template" class="oe_link"
|
|
||||||
help="Save as a new template"
|
|
||||||
attrs="{'invisible':[('content_subtype','!=','html')]}"/>
|
|
||||||
</group>
|
|
||||||
</xpath>
|
|
||||||
</data>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
@ -437,9 +437,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
||||||
|
|
||||||
# Mail data
|
# Mail data
|
||||||
_subject = 'Pigs'
|
_subject = 'Pigs'
|
||||||
_body_text = 'Pigs rules'
|
_body = 'Pigs <b>rule</b>'
|
||||||
_msg_reply = 'Re: Pigs'
|
_reply_subject = 'Re: Pigs'
|
||||||
_msg_body = '<pre>Pigs rules</pre>'
|
|
||||||
_attachments = [
|
_attachments = [
|
||||||
{'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
|
{'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
|
||||||
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
|
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
|
||||||
|
@ -462,9 +461,9 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
||||||
|
|
||||||
# 1. Comment group_pigs with body_text and subject
|
# 1. Comment group_pigs with body_text and subject
|
||||||
compose_id = mail_compose.create(cr, uid,
|
compose_id = mail_compose.create(cr, uid,
|
||||||
{'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
{'subject': _subject, 'body': _body, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
||||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id,
|
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id,
|
||||||
'default_content_subtype': 'plaintext'})
|
'default_content_subtype': 'plaintext'})
|
||||||
compose = mail_compose.browse(cr, uid, compose_id)
|
compose = mail_compose.browse(cr, uid, compose_id)
|
||||||
# Test: mail.compose.message: composition_mode, model, res_id
|
# Test: mail.compose.message: composition_mode, model, res_id
|
||||||
self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
|
self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
|
||||||
|
@ -476,8 +475,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
||||||
group_pigs.refresh()
|
group_pigs.refresh()
|
||||||
message = group_pigs.message_ids[0]
|
message = group_pigs.message_ids[0]
|
||||||
# Test: mail.message: subject, body inside pre
|
# Test: mail.message: subject, body inside pre
|
||||||
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
|
self.assertEqual(message.subject, _subject, 'mail.message incorrect subject')
|
||||||
self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
|
self.assertEqual(message.body, _body, 'mail.message incorrect body')
|
||||||
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
|
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
|
||||||
msg_pids = [partner.id for partner in message.notified_partner_ids]
|
msg_pids = [partner.id for partner in message.notified_partner_ids]
|
||||||
test_pids = [p_b_id, p_c_id, p_d_id]
|
test_pids = [p_b_id, p_c_id, p_d_id]
|
||||||
|
@ -495,13 +494,12 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
||||||
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
|
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
|
||||||
{'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
|
{'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
|
||||||
compose = mail_compose.browse(cr, uid, compose_id)
|
compose = mail_compose.browse(cr, uid, compose_id)
|
||||||
# Test: model, res_id, parent_id, content_subtype
|
# Test: model, res_id, parent_id
|
||||||
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
|
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
|
||||||
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
|
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
|
||||||
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
|
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
|
||||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
|
|
||||||
# Test: mail.message: subject as Re:.., body in html, parent_id
|
# Test: mail.message: subject as Re:.., body in html, parent_id
|
||||||
self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
|
self.assertEqual(compose.subject, _reply_subject, 'mail.message incorrect subject')
|
||||||
# self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
|
# self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
|
||||||
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
|
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
|
||||||
# Test: mail.message: attachments
|
# Test: mail.message: attachments
|
||||||
|
@ -518,8 +516,6 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
||||||
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False,
|
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False,
|
||||||
'active_ids': [self.group_pigs_id, group_bird_id]})
|
'active_ids': [self.group_pigs_id, group_bird_id]})
|
||||||
compose = mail_compose.browse(cr, uid, compose_id)
|
compose = mail_compose.browse(cr, uid, compose_id)
|
||||||
# Test: content_subtype is html
|
|
||||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
|
|
||||||
|
|
||||||
# 2. Post the comment, get created message for each group
|
# 2. Post the comment, get created message for each group
|
||||||
mail_compose.send_mail(cr, uid, [compose_id],
|
mail_compose.send_mail(cr, uid, [compose_id],
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
from openerp.addons.mail.tests import test_mail_mockup
|
from openerp.addons.mail.tests import test_mail_mockup
|
||||||
from openerp.osv.orm import except_orm
|
from openerp.osv.orm import except_orm
|
||||||
from openerp.tools.misc import mute_logger
|
from openerp.tools import mute_logger
|
||||||
|
|
||||||
|
|
||||||
class test_mail_access_rights(test_mail_mockup.TestMailMockups):
|
class test_mail_access_rights(test_mail_mockup.TestMailMockups):
|
||||||
|
@ -52,6 +52,7 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
|
||||||
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||||
self.partner_raoul_id = self.user_raoul.partner_id.id
|
self.partner_raoul_id = self.user_raoul.partner_id.id
|
||||||
|
|
||||||
|
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
|
||||||
def test_00_mail_message_search_access_rights(self):
|
def test_00_mail_message_search_access_rights(self):
|
||||||
""" Test mail_message search override about access rights. """
|
""" Test mail_message search override about access rights. """
|
||||||
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
|
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
|
||||||
|
@ -85,7 +86,7 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
|
||||||
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
|
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
|
||||||
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
|
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
|
||||||
|
|
||||||
@mute_logger('openerp.osv.orm')
|
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
|
||||||
def test_05_mail_message_read_access_rights(self):
|
def test_05_mail_message_read_access_rights(self):
|
||||||
""" Test basic mail_message read access rights. """
|
""" Test basic mail_message read access rights. """
|
||||||
cr, uid = self.cr, self.uid
|
cr, uid = self.cr, self.uid
|
||||||
|
@ -133,8 +134,7 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
|
||||||
self.assertRaises(except_orm, self.mail_message.read,
|
self.assertRaises(except_orm, self.mail_message.read,
|
||||||
cr, user_bert_id, message_id)
|
cr, user_bert_id, message_id)
|
||||||
|
|
||||||
@mute_logger('openerp.addons.base.ir.ir_model')
|
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
|
||||||
@mute_logger('openerp.osv.orm')
|
|
||||||
def test_10_mail_flow_access_rights(self):
|
def test_10_mail_flow_access_rights(self):
|
||||||
""" Test a Chatter-looks alike flow. """
|
""" Test a Chatter-looks alike flow. """
|
||||||
cr, uid = self.cr, self.uid
|
cr, uid = self.cr, self.uid
|
||||||
|
@ -182,14 +182,14 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
|
||||||
|
|
||||||
# Do: Bert create a mail.compose.message record, because he uses the wizard
|
# Do: Bert create a mail.compose.message record, because he uses the wizard
|
||||||
compose_id = mail_compose.create(cr, user_bert_id,
|
compose_id = mail_compose.create(cr, user_bert_id,
|
||||||
{'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': []},
|
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
|
||||||
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
||||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
|
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
|
||||||
mail_compose.send_mail(cr, user_bert_id, [compose_id])
|
mail_compose.send_mail(cr, user_bert_id, [compose_id])
|
||||||
|
|
||||||
self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
|
self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
|
||||||
compose_id = mail_compose.create(cr, self.user_demo_id,
|
compose_id = mail_compose.create(cr, self.user_demo_id,
|
||||||
{'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': []},
|
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
|
||||||
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
||||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
|
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
|
||||||
mail_compose.send_mail(cr, self.user_demo_id, [compose_id])
|
mail_compose.send_mail(cr, self.user_demo_id, [compose_id])
|
||||||
|
|
|
@ -81,7 +81,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
elif composition_mode == 'comment' and model and res_id:
|
elif composition_mode == 'comment' and model and res_id:
|
||||||
vals = self.get_record_data(cr, uid, model, res_id, context=context)
|
vals = self.get_record_data(cr, uid, model, res_id, context=context)
|
||||||
elif composition_mode == 'mass_mail' and model and active_ids:
|
elif composition_mode == 'mass_mail' and model and active_ids:
|
||||||
vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'}
|
vals = {'model': model, 'res_id': res_id}
|
||||||
else:
|
else:
|
||||||
vals = {'model': model, 'res_id': res_id}
|
vals = {'model': model, 'res_id': res_id}
|
||||||
if composition_mode:
|
if composition_mode:
|
||||||
|
@ -106,16 +106,10 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'mail_compose_message_ir_attachments_rel',
|
'mail_compose_message_ir_attachments_rel',
|
||||||
'wizard_id', 'attachment_id', 'Attachments'),
|
'wizard_id', 'attachment_id', 'Attachments'),
|
||||||
'filter_id': fields.many2one('ir.filters', 'Filters'),
|
'filter_id': fields.many2one('ir.filters', 'Filters'),
|
||||||
'body_text': fields.text('Plain-text Contents'),
|
|
||||||
'content_subtype': fields.char('Message content subtype', size=32, readonly=1,
|
|
||||||
help="Type of message, usually 'html' or 'plain', used to select "\
|
|
||||||
"plain-text or rich-text contents accordingly"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'composition_mode': 'comment',
|
'composition_mode': 'comment',
|
||||||
'content_subtype': lambda self, cr, uid, ctx={}: 'html',
|
|
||||||
'body_text': lambda self, cr, uid, ctx={}: False,
|
|
||||||
'body': lambda self, cr, uid, ctx={}: '',
|
'body': lambda self, cr, uid, ctx={}: '',
|
||||||
'subject': lambda self, cr, uid, ctx={}: False,
|
'subject': lambda self, cr, uid, ctx={}: False,
|
||||||
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
||||||
|
@ -172,32 +166,9 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'parent_id': message_data.id,
|
'parent_id': message_data.id,
|
||||||
'subject': reply_subject,
|
'subject': reply_subject,
|
||||||
'partner_ids': partner_ids,
|
'partner_ids': partner_ids,
|
||||||
'content_subtype': 'html',
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def toggle_content_subtype(self, cr, uid, ids, context=None):
|
|
||||||
""" toggle content_subtype: calls onchange_formatting to emulate an
|
|
||||||
on_change, then writes the value to update the form. """
|
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
|
||||||
content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html'
|
|
||||||
onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context)
|
|
||||||
self.write(cr, uid, [record.id], onchange_res['value'], context=context)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None):
|
|
||||||
""" This onchange allows to have some specific behavior when switching
|
|
||||||
between text or html mode. This method can be overridden.
|
|
||||||
:param values: 'plain' or 'html'
|
|
||||||
"""
|
|
||||||
return {'value': {'content_subtype': value}}
|
|
||||||
|
|
||||||
def dummy(self, cr, uid, ids, context=None):
|
|
||||||
""" TDE: defined to have buttons that do basically nothing. It is
|
|
||||||
currently impossible to have buttons that do nothing special
|
|
||||||
in views (if type not specified, considered as 'object'). """
|
|
||||||
return True
|
|
||||||
|
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
# Wizard validation and send
|
# Wizard validation and send
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
@ -218,8 +189,8 @@ class mail_compose_message(osv.TransientModel):
|
||||||
for res_id in res_ids:
|
for res_id in res_ids:
|
||||||
# default values, according to the wizard options
|
# default values, according to the wizard options
|
||||||
post_values = {
|
post_values = {
|
||||||
'subject': wizard.subject if wizard.content_subtype == 'html' else False,
|
'subject': wizard.subject,
|
||||||
'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % tools.ustr(wizard.body_text),
|
'body': wizard.body,
|
||||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||||
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
|
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
|
||||||
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
|
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
<field name="model" invisible="1"/>
|
<field name="model" invisible="1"/>
|
||||||
<field name="res_id" invisible="1"/>
|
<field name="res_id" invisible="1"/>
|
||||||
<field name="parent_id" invisible="1"/>
|
<field name="parent_id" invisible="1"/>
|
||||||
<field name="content_subtype" invisible="1"/>
|
|
||||||
<!-- visible wizard -->
|
<!-- visible wizard -->
|
||||||
<label for="partner_ids" string="Recipients"/>
|
<label for="partner_ids" string="Recipients"/>
|
||||||
<div>
|
<div>
|
||||||
|
@ -25,8 +24,7 @@
|
||||||
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
||||||
context="{'force_email':True}" required="1"/>
|
context="{'force_email':True}" required="1"/>
|
||||||
</div>
|
</div>
|
||||||
<field name="subject" placeholder="Subject..."
|
<field name="subject" placeholder="Subject..."/>
|
||||||
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
|
|
||||||
</group>
|
</group>
|
||||||
<field name="body"/>
|
<field name="body"/>
|
||||||
<field name="attachment_ids" widget="many2many_binary"/>
|
<field name="attachment_ids" widget="many2many_binary"/>
|
||||||
|
|
|
@ -22,6 +22,6 @@
|
||||||
import portal
|
import portal
|
||||||
import mail_mail
|
import mail_mail
|
||||||
import wizard
|
import wizard
|
||||||
|
import acquirer
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -49,8 +49,10 @@ very handy when used in combination with the module 'share'.
|
||||||
'portal_view.xml',
|
'portal_view.xml',
|
||||||
'wizard/portal_wizard_view.xml',
|
'wizard/portal_wizard_view.xml',
|
||||||
'wizard/share_wizard_view.xml',
|
'wizard/share_wizard_view.xml',
|
||||||
|
'acquirer_view.xml',
|
||||||
],
|
],
|
||||||
'demo': ['portal_demo.xml'],
|
'demo': ['portal_demo.xml'],
|
||||||
|
'css': ['static/src/css/portal.css'],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Business Applications
|
||||||
|
# Copyright (c) 2012-TODAY 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
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from urllib import quote as quote
|
||||||
|
|
||||||
|
from openerp.osv import osv, fields
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
from openerp.tools import float_repr
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
try:
|
||||||
|
from mako.template import Template as MakoTemplate
|
||||||
|
except ImportError:
|
||||||
|
_logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
|
||||||
|
|
||||||
|
|
||||||
|
class acquirer(osv.Model):
|
||||||
|
_name = 'portal.payment.acquirer'
|
||||||
|
_description = 'Online Payment Acquirer'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Name', required=True),
|
||||||
|
'form_template': fields.text('Payment form template (HTML)', translate=True, required=True),
|
||||||
|
'visible': fields.boolean('Visible', help="Make this payment acquirer available in portal forms (Customer invoices, etc.)"),
|
||||||
|
}
|
||||||
|
|
||||||
|
_default = {
|
||||||
|
'visible': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def render(self, cr, uid, id, object, reference, currency, amount, context=None, **kwargs):
|
||||||
|
""" Renders the form template of the given acquirer as a mako template """
|
||||||
|
if not isinstance(id, (int,long)):
|
||||||
|
id = id[0]
|
||||||
|
this = self.browse(cr, uid, id)
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
try:
|
||||||
|
i18n_kind = _(object._description) # may fail to translate, but at least we try
|
||||||
|
result = MakoTemplate(this.form_template).render_unicode(object=object,
|
||||||
|
reference=reference,
|
||||||
|
currency=currency,
|
||||||
|
amount=amount,
|
||||||
|
kind=i18n_kind,
|
||||||
|
quote=quote,
|
||||||
|
# context kw would clash with mako internals
|
||||||
|
ctx=context,
|
||||||
|
format_exceptions=True)
|
||||||
|
return result.strip()
|
||||||
|
except Exception:
|
||||||
|
_logger.exception("failed to render mako template value for payment.acquirer %s: %r", this.name, this.form_template)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _wrap_payment_block(self, cr, uid, html_block, amount, currency, context=None):
|
||||||
|
payment_header = _('Pay safely online')
|
||||||
|
amount_str = float_repr(amount, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
|
||||||
|
currency_str = currency.symbol or currency.name
|
||||||
|
amount = u"%s %s" % ((currency_str, amount_str) if currency.position == 'before' else (amount_str, currency_str))
|
||||||
|
result = """<div class="payment_acquirers">
|
||||||
|
<div class="payment_header">
|
||||||
|
<div class="payment_amount">%s</div>
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
%%s
|
||||||
|
</div>""" % (amount, payment_header)
|
||||||
|
return result % html_block
|
||||||
|
|
||||||
|
def render_payment_block(self, cr, uid, object, reference, currency, amount, context=None, **kwargs):
|
||||||
|
""" Renders all visible payment acquirer forms for the given rendering context, and
|
||||||
|
return them wrapped in an appropriate HTML block, ready for direct inclusion
|
||||||
|
in an OpenERP v7 form view """
|
||||||
|
acquirer_ids = self.search(cr, uid, [('visible', '=', True)])
|
||||||
|
if not acquirer_ids:
|
||||||
|
return
|
||||||
|
html_forms = []
|
||||||
|
for this in self.browse(cr, uid, acquirer_ids):
|
||||||
|
html_forms.append(this.render(object, reference, currency, amount, context=context, **kwargs))
|
||||||
|
html_block = '\n'.join(filter(None,html_forms))
|
||||||
|
return self._wrap_payment_block(cr, uid, html_block, amount, currency, context=context)
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<record id="acquirer_form" model="ir.ui.view">
|
||||||
|
<field name="model">portal.payment.acquirer</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Payment Acquirer" version="7.0">
|
||||||
|
<group col="1">
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name" class="oe_edit_only"/><h1><field name="name"/></h1>
|
||||||
|
<div class="oe_edit_only"><field name="visible"/><label for="visible"/></div>
|
||||||
|
</div>
|
||||||
|
<group string="Form Template">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
This is an HTML form template to submit a payment through this acquirer.
|
||||||
|
The template will be rendered with <a href="http://www.makotemplates.org/" target="_blank">Mako</a>, so it may use Mako expressions.
|
||||||
|
The Mako evaluation context provides:
|
||||||
|
<ul>
|
||||||
|
<li>reference: the reference number of the document to pay</li>
|
||||||
|
<li>kind: the kind of document on which the payment form is rendered (translated to user language, e.g. "Invoice")</li>
|
||||||
|
<li>currency: the currency record in which the document is issued (e.g. currency.name could be EUR)</li>
|
||||||
|
<li>amount: the total amount to pay, as a float</li>
|
||||||
|
<li>object: the document on which the payment form is rendered (usually an invoice or sale order record)</li>
|
||||||
|
<li>quote(): a method to quote special string character to make them suitable for inclusion in a URL</li>
|
||||||
|
<li>cr: the current database cursor</li>
|
||||||
|
<li>uid: the current user id</li>
|
||||||
|
<li>ctx: the current context dictionary</li>
|
||||||
|
</ul>
|
||||||
|
If the template renders to an empty result in a certain context it will be ignored, as if it was inactive.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<field name="form_template" nolabel="1" colspan="2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="acquirer_list" model="ir.ui.view">
|
||||||
|
<field name="model">portal.payment.acquirer</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Payment Acquirers">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="visible"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="acquirer_search" model="ir.ui.view">
|
||||||
|
<field name="model">portal.payment.acquirer</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search>
|
||||||
|
<field name="name"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Acquirers list action is visible in Invoicing Settings -->
|
||||||
|
<record model="ir.actions.act_window" id="action_acquirer_list">
|
||||||
|
<field name="name">Payment Acquirers</field>
|
||||||
|
<field name="res_model">portal.payment.acquirer</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -27,6 +27,23 @@
|
||||||
<field name="res_id" ref="company_jobs"/>
|
<field name="res_id" ref="company_jobs"/>
|
||||||
<field name="view_mode">form</field>
|
<field name="view_mode">form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="paypal_acquirer" model="portal.payment.acquirer">
|
||||||
|
<field name="name">Paypal</field>
|
||||||
|
<field name="form_template"><![CDATA[
|
||||||
|
% if object.company_id.paypal_account:
|
||||||
|
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
|
||||||
|
<input type="hidden" name="cmd" value="_xclick"/>
|
||||||
|
<input type="hidden" name="business" value="${object.company_id.paypal_account}"/>
|
||||||
|
<input type="hidden" name="item_name" value="${object.company_id.name} ${kind.title()} ${reference}"/>
|
||||||
|
<input type="hidden" name="amount" value="${amount}"/>
|
||||||
|
<input type="hidden" name="currency_code" value="${currency.name}"/>
|
||||||
|
<input type="image" name="submit" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
|
||||||
|
</form>
|
||||||
|
% endif
|
||||||
|
]]></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -4,3 +4,5 @@ access_res_partner,res.partner,base.model_res_partner,portal.group_portal,1,0,0,
|
||||||
access_res_partner_address,res.partner_address,base.model_res_partner_address,portal.group_portal,1,0,0,0
|
access_res_partner_address,res.partner_address,base.model_res_partner_address,portal.group_portal,1,0,0,0
|
||||||
access_res_partner_category,res.partner_category,base.model_res_partner_category,portal.group_portal,1,0,0,0
|
access_res_partner_category,res.partner_category,base.model_res_partner_category,portal.group_portal,1,0,0,0
|
||||||
access_res_partner_title,res.partner_title,base.model_res_partner_title,portal.group_portal,1,0,0,0
|
access_res_partner_title,res.partner_title,base.model_res_partner_title,portal.group_portal,1,0,0,0
|
||||||
|
access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0
|
||||||
|
access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1
|
||||||
|
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
|
||||||
|
.openerp .oe_application .oe_form_sheetbg {
|
||||||
|
/* Establish a stacking context on top of which the
|
||||||
|
payment_acquirers::before element can be positioned */
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openerp .payment_acquirers {
|
||||||
|
margin: -40px 0 -32px -24px;
|
||||||
|
position: relative;
|
||||||
|
padding: 10px 15px;
|
||||||
|
right: -153px;
|
||||||
|
|
||||||
|
background: #729FCF;
|
||||||
|
background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4));
|
||||||
|
background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
|
||||||
|
background-image: -moz-linear-gradient(top, #729FCF, #3465A4);
|
||||||
|
background-image: -ms-linear-gradient(top, #729FCF, #3465A4);
|
||||||
|
background-image: -o-linear-gradient(top, #729FCF, #3465A4);
|
||||||
|
background-image: linear-gradient(to bottom, #729FCF, #3465A4);
|
||||||
|
border-bottom: 1px solid #043574;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.openerp .payment_acquirers form {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openerp .payment_acquirers form input,
|
||||||
|
.openerp .payment_acquirers form textarea,
|
||||||
|
.openerp .payment_acquirers form select
|
||||||
|
{
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openerp .payment_acquirers::after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
width: 10px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 1px;
|
||||||
|
|
||||||
|
margin-bottom: -6px;
|
||||||
|
background: #043574;
|
||||||
|
|
||||||
|
-webkit-transform: skewY(-45deg);
|
||||||
|
-moz-transform: skewY(-45deg);
|
||||||
|
-ms-transform: skewY(-45deg);
|
||||||
|
-o-transform: skewY(-45deg);
|
||||||
|
transform: skewY(-45deg);
|
||||||
|
|
||||||
|
-webkit-box-shadow: inset 1px -1px 2px black, -1px 1px 3px black;
|
||||||
|
box-shadow: inset 1px -1px 2px black, -1px 1px 3px black;
|
||||||
|
|
||||||
|
/* push it under all its siblings, just on top of its root
|
||||||
|
in the z-index stack: div.oe_form_sheetbg */
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openerp .payment_acquirers .payment_header {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 110%;
|
||||||
|
padding-right: 15px;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 1px #729FCF, 0 -1px 1px #3465A4;
|
||||||
|
}
|
||||||
|
.openerp .payment_acquirers .payment_header .payment_amount {
|
||||||
|
font-size: 130%;
|
||||||
|
padding: 6px 0px;
|
||||||
|
}
|
|
@ -19,3 +19,5 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
import portal_sale
|
||||||
|
import res_config
|
|
@ -26,18 +26,34 @@
|
||||||
'category': 'Tools',
|
'category': 'Tools',
|
||||||
'complexity': 'easy',
|
'complexity': 'easy',
|
||||||
'description': """
|
'description': """
|
||||||
This module adds sale menu and features to your portal if sale and portal are installed.
|
This module adds a Sales menu to your portal as soon as sale and portal are installed.
|
||||||
========================================================================================
|
======================================================================================
|
||||||
|
|
||||||
|
After installing this module, portal users will be able to access their own documents
|
||||||
|
via the following menus:
|
||||||
|
|
||||||
|
- Quotations
|
||||||
|
- Sale Orders
|
||||||
|
- Delivery Orders
|
||||||
|
- Products (public ones)
|
||||||
|
- Invoices
|
||||||
|
- Payments/Refunds
|
||||||
|
|
||||||
|
If online payment acquirers are configured, portal users will also be given the opportunity to
|
||||||
|
pay online on their Sale Orders and Invoices that are not paid yet. Paypal is included
|
||||||
|
by default, you simply need to configure a Paypal account in the Accounting/Invoicing settings.
|
||||||
""",
|
""",
|
||||||
'author': 'OpenERP SA',
|
'author': 'OpenERP SA',
|
||||||
'depends': ['sale_stock','portal'],
|
'depends': ['sale_stock','portal'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/portal_security.xml',
|
'security/portal_security.xml',
|
||||||
'portal_sale_view.xml',
|
'portal_sale_view.xml',
|
||||||
|
'portal_sale_data.xml',
|
||||||
|
'res_config_view.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
],
|
],
|
||||||
'installable': True,
|
|
||||||
'auto_install': True,
|
'auto_install': True,
|
||||||
'category': 'Hidden',
|
'category': 'Hidden',
|
||||||
}
|
}
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Business Applications
|
||||||
|
# Copyright (c) 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
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from openerp.osv import osv, fields
|
||||||
|
|
||||||
|
class sale_order(osv.Model):
|
||||||
|
_inherit = 'sale.order'
|
||||||
|
|
||||||
|
# make the real method inheritable
|
||||||
|
_payment_block_proxy = lambda self,*a,**kw: self._portal_payment_block(*a, **kw)
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'portal_payment_options': fields.function(_payment_block_proxy, type="html", string="Portal Payment Options"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _portal_payment_block(self, cr, uid, ids, fieldname, arg, context=None):
|
||||||
|
result = dict.fromkeys(ids, False)
|
||||||
|
payment_acquirer = self.pool.get('portal.payment.acquirer')
|
||||||
|
for this in self.browse(cr, uid, ids, context=context):
|
||||||
|
if this.state not in ('draft','cancel') and not this.invoiced:
|
||||||
|
result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.name,
|
||||||
|
this.pricelist_id.currency_id, this.amount_total, context=context)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def action_quotation_send(self, cr, uid, ids, context=None):
|
||||||
|
''' Override to use a modified template that includes a portal signup link '''
|
||||||
|
action_dict = super(sale_order, self).action_quotation_send(cr, uid, ids, context=context)
|
||||||
|
try:
|
||||||
|
template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_sale', 'email_template_edi_sale')[1]
|
||||||
|
# assume context is still a dict, as prepared by super
|
||||||
|
ctx = action_dict['context']
|
||||||
|
ctx['default_template_id'] = template_id
|
||||||
|
ctx['default_use_template'] = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return action_dict
|
||||||
|
|
||||||
|
class account_invoice(osv.Model):
|
||||||
|
_inherit = 'account.invoice'
|
||||||
|
|
||||||
|
# make the real method inheritable
|
||||||
|
_payment_block_proxy = lambda self,*a,**kw: self._portal_payment_block(*a, **kw)
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'portal_payment_options': fields.function(_payment_block_proxy, type="html", string="Portal Payment Options"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _portal_payment_block(self, cr, uid, ids, fieldname, arg, context=None):
|
||||||
|
result = dict.fromkeys(ids, False)
|
||||||
|
payment_acquirer = self.pool.get('portal.payment.acquirer')
|
||||||
|
for this in self.browse(cr, uid, ids, context=context):
|
||||||
|
if this.state not in ('draft','done') and not this.reconciled:
|
||||||
|
result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.number,
|
||||||
|
this.currency_id, this.residual, context=context)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def action_invoice_sent(self, cr, uid, ids, context=None):
|
||||||
|
''' Override to use a modified template that includes a portal signup link '''
|
||||||
|
action_dict = super(account_invoice, self).action_invoice_sent(cr, uid, ids, context=context)
|
||||||
|
try:
|
||||||
|
template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_sale', 'email_template_edi_invoice')[1]
|
||||||
|
# assume context is still a dict, as prepared by super
|
||||||
|
ctx = action_dict['context']
|
||||||
|
ctx['default_template_id'] = template_id
|
||||||
|
ctx['default_use_template'] = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return action_dict
|
|
@ -0,0 +1,212 @@
|
||||||
|
<openerp>
|
||||||
|
<!-- 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">Sale Order - Send by Email (Portal)</field>
|
||||||
|
<field name="email_from">${object.user_id.email or ''}</field>
|
||||||
|
<field name="subject">${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
|
||||||
|
<field name="email_recipients">${object.partner_invoice_id.id}</field>
|
||||||
|
<field name="model_id" ref="sale.model_sale_order"/>
|
||||||
|
<field name="auto_delete" eval="True"/>
|
||||||
|
<field name="report_template" ref="sale.report_sale_order"/>
|
||||||
|
<field name="report_name">${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||||
|
<field name="body_html"><![CDATA[
|
||||||
|
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||||
|
|
||||||
|
<p>Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},</p>
|
||||||
|
|
||||||
|
<p>Here is your ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} from ${object.company_id.name}: </p>
|
||||||
|
|
||||||
|
<p style="border-left: 1px solid #8e0000; margin-left: 30px;">
|
||||||
|
<strong>REFERENCES</strong><br />
|
||||||
|
Order number: <strong>${object.name}</strong><br />
|
||||||
|
Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br />
|
||||||
|
Order date: ${object.date_order}<br />
|
||||||
|
% if object.origin:
|
||||||
|
Order reference: ${object.origin}<br />
|
||||||
|
% endif
|
||||||
|
% if object.client_order_ref:
|
||||||
|
Your reference: ${object.client_order_ref}<br />
|
||||||
|
% endif
|
||||||
|
% if object.user_id:
|
||||||
|
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Order%20${object.name}">${object.user_id.name}</a>
|
||||||
|
% endif
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%
|
||||||
|
action = 'portal_sale.action_quotations_portal' if object.state in ('draft', 'sent') else 'portal_sale.action_orders_portal'
|
||||||
|
object.partner_id.signup_prepare()
|
||||||
|
signup_url = object.partner_id._get_signup_url_for_action(action=action,view_type='form',res_id=object.id)[object.partner_id.id]
|
||||||
|
%>
|
||||||
|
% if signup_url:
|
||||||
|
<p>
|
||||||
|
You can access this document and pay online via our Customers Portal:
|
||||||
|
</p>
|
||||||
|
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #DDD; 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="${signup_url}">View ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'}</a>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state != 'draft':
|
||||||
|
<%
|
||||||
|
comp_name = quote(object.company_id.name)
|
||||||
|
order_name = quote(object.name)
|
||||||
|
paypal_account = quote(object.company_id.paypal_account)
|
||||||
|
order_amount = quote(str(object.residual))
|
||||||
|
cur_name = quote(object.pricelist_id.currency_id.name)
|
||||||
|
paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Order%%20%s" \
|
||||||
|
"&invoice=%s&amount=%s&currency_code=%s&button_subtype=services&no_note=1" \
|
||||||
|
"&bn=OpenERP_Order_PayNow_%s" % \
|
||||||
|
(paypal_account,comp_name,order_name,order_name,order_amount,cur_name,cur_name)
|
||||||
|
%>
|
||||||
|
<br/>
|
||||||
|
<p>It is also possible to directly pay with Paypal:</p>
|
||||||
|
<a style="margin-left: 120px;" href="${paypal_url}">
|
||||||
|
<img class="oe_edi_paypal_button" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
|
||||||
|
</a>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>If you have any question, do not hesitate to contact us.</p>
|
||||||
|
<p>Thank you for choosing ${object.company_id.name or 'us'}!</p>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<div style="width: 375px; margin: 0px; padding: 0px; background-color: #8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; background-repeat: repeat no-repeat;">
|
||||||
|
<h3 style="margin: 0px; padding: 2px 14px; font-size: 12px; color: #FFF;">
|
||||||
|
<strong style="text-transform:uppercase;">${object.company_id.name}</strong></h3>
|
||||||
|
</div>
|
||||||
|
<div style="width: 347px; margin: 0px; padding: 5px 14px; line-height: 16px; background-color: #F2F2F2;">
|
||||||
|
<span style="color: #222; margin-bottom: 5px; display: block; ">
|
||||||
|
% if object.company_id.street:
|
||||||
|
${object.company_id.street}<br/>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.street2:
|
||||||
|
${object.company_id.street2}<br/>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.city or object.company_id.zip:
|
||||||
|
${object.company_id.zip} ${object.company_id.city}<br/>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.country_id:
|
||||||
|
${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}<br/>
|
||||||
|
% endif
|
||||||
|
</span>
|
||||||
|
% if object.company_id.phone:
|
||||||
|
<div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">
|
||||||
|
Phone: ${object.company_id.phone}
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.website:
|
||||||
|
<div>
|
||||||
|
Web : <a href="${object.company_id.website}">${object.company_id.website}</a>
|
||||||
|
</div>
|
||||||
|
%endif
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
]]></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="email_template_edi_invoice" model="email.template">
|
||||||
|
<field name="name">Invoice - Send by Email (Portal)</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_recipients">${object.partner_id.id}</field>
|
||||||
|
<field name="model_id" ref="account.model_account_invoice"/>
|
||||||
|
<field name="auto_delete" eval="True"/>
|
||||||
|
<field name="report_template" ref="account.account_invoices"/>
|
||||||
|
<field name="report_name">Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||||
|
<field name="body_html"><![CDATA[
|
||||||
|
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||||
|
|
||||||
|
<p>Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},</p>
|
||||||
|
|
||||||
|
<p>A new invoice is available for ${object.partner_id.name}: </p>
|
||||||
|
|
||||||
|
<p style="border-left: 1px solid #8e0000; margin-left: 30px;">
|
||||||
|
<strong>REFERENCES</strong><br />
|
||||||
|
Invoice number: <strong>${object.number}</strong><br />
|
||||||
|
Invoice total: <strong>${object.amount_total} ${object.currency_id.name}</strong><br />
|
||||||
|
Invoice date: ${object.date_invoice}<br />
|
||||||
|
% if object.origin:
|
||||||
|
Order reference: ${object.origin}<br />
|
||||||
|
% endif
|
||||||
|
% if object.user_id:
|
||||||
|
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Invoice%20${object.number}">${object.user_id.name}</a>
|
||||||
|
% endif
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%
|
||||||
|
action = 'portal_sale.portal_action_invoices'
|
||||||
|
object.partner_id.signup_prepare()
|
||||||
|
signup_url = object.partner_id._get_signup_url_for_action(action=action,view_type='form',res_id=object.id)[object.partner_id.id]
|
||||||
|
%>
|
||||||
|
% if signup_url:
|
||||||
|
<p>
|
||||||
|
You can access the invoice document and pay online via our Customers Portal:
|
||||||
|
</p>
|
||||||
|
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #DDD; 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="${signup_url}">View Invoice</a>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
|
||||||
|
<%
|
||||||
|
comp_name = quote(object.company_id.name)
|
||||||
|
inv_number = quote(object.number)
|
||||||
|
paypal_account = quote(object.company_id.paypal_account)
|
||||||
|
inv_amount = quote(str(object.amount_residual))
|
||||||
|
cur_name = quote(object.currency_id.name)
|
||||||
|
paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Invoice%%20%s&" \
|
||||||
|
"invoice=%s&amount=%s&currency_code=%s&button_subtype=services&no_note=1&bn=OpenERP_Invoice_PayNow_%s" % \
|
||||||
|
(paypal_account,comp_name,inv_number,inv_number,inv_amount,cur_name,cur_name)
|
||||||
|
%>
|
||||||
|
<br/>
|
||||||
|
<p>It is also possible to directly pay with Paypal:</p>
|
||||||
|
<a style="margin-left: 120px;" href="${paypal_url}">
|
||||||
|
<img class="oe_edi_paypal_button" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
|
||||||
|
</a>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>If you have any question, do not hesitate to contact us.</p>
|
||||||
|
<p>Thank you for choosing ${object.company_id.name or 'us'}!</p>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<div style="width: 375px; margin: 0px; padding: 0px; background-color: #8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; background-repeat: repeat no-repeat;">
|
||||||
|
<h3 style="margin: 0px; padding: 2px 14px; font-size: 12px; color: #FFF;">
|
||||||
|
<strong style="text-transform:uppercase;">${object.company_id.name}</strong></h3>
|
||||||
|
</div>
|
||||||
|
<div style="width: 347px; margin: 0px; padding: 5px 14px; line-height: 16px; background-color: #F2F2F2;">
|
||||||
|
<span style="color: #222; margin-bottom: 5px; display: block; ">
|
||||||
|
% if object.company_id.street:
|
||||||
|
${object.company_id.street}<br/>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.street2:
|
||||||
|
${object.company_id.street2}<br/>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.city or object.company_id.zip:
|
||||||
|
${object.company_id.zip} ${object.company_id.city}<br/>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.country_id:
|
||||||
|
${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}<br/>
|
||||||
|
% endif
|
||||||
|
</span>
|
||||||
|
% if object.company_id.phone:
|
||||||
|
<div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">
|
||||||
|
Phone: ${object.company_id.phone}
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if object.company_id.website:
|
||||||
|
<div>
|
||||||
|
Web : <a href="${object.company_id.website}">${object.company_id.website}</a>
|
||||||
|
</div>
|
||||||
|
%endif
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
]]></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -1,12 +1,35 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
|
<!-- Add payment options to sale.order and invoice forms -->
|
||||||
|
<record model="ir.ui.view" id="sale_order_form_payment">
|
||||||
|
<field name="name">sale.order.form.payment</field>
|
||||||
|
<field name="model">sale.order</field>
|
||||||
|
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<notebook version="7.0" position="before">
|
||||||
|
<field name="portal_payment_options" groups="portal_sale.group_payment_options"/>
|
||||||
|
</notebook>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.ui.view" id="invoice_form_payment">
|
||||||
|
<field name="name">account.invoice.form.payment</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<notebook version="7.0" position="before">
|
||||||
|
<field name="portal_payment_options" groups="portal_sale.group_payment_options"/>
|
||||||
|
</notebook>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Override the original action to set another help field and/or
|
Override the original action to set another help field and/or
|
||||||
another context field, more suited for portal members
|
another context field, more suited for portal members
|
||||||
-->
|
-->
|
||||||
<record id="action_order_tree5" model="ir.actions.act_window">
|
<record id="action_quotations_portal" model="ir.actions.act_window">
|
||||||
<field name="name">Quotations</field>
|
<field name="name">Quotations</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
|
@ -16,8 +39,8 @@
|
||||||
<field name="help">You don't have any quotation.</field>
|
<field name="help">You don't have any quotation.</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_order_form" model="ir.actions.act_window">
|
<record id="action_orders_portal" model="ir.actions.act_window">
|
||||||
<field name="name">Sales Orders</field>
|
<field name="name">Sale Orders</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
<field name="view_mode">tree,form,calendar,graph</field>
|
<field name="view_mode">tree,form,calendar,graph</field>
|
||||||
|
@ -48,8 +71,8 @@
|
||||||
<field name="help">There are no public products.</field>
|
<field name="help">There are no public products.</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_invoice_tree1" model="ir.actions.act_window">
|
<record id="portal_action_invoices" model="ir.actions.act_window">
|
||||||
<field name="name">Customer Invoices</field>
|
<field name="name">Invoices</field>
|
||||||
<field name="res_model">account.invoice</field>
|
<field name="res_model">account.invoice</field>
|
||||||
<field name="view_mode">tree,form,calendar,graph</field>
|
<field name="view_mode">tree,form,calendar,graph</field>
|
||||||
<field name="domain">[('type','=','out_invoice')]</field>
|
<field name="domain">[('type','=','out_invoice')]</field>
|
||||||
|
@ -57,9 +80,21 @@
|
||||||
<field name="search_view_id" ref="account.view_account_invoice_filter"/>
|
<field name="search_view_id" ref="account.view_account_invoice_filter"/>
|
||||||
<field name="help">You don't have any invoice.</field>
|
<field name="help">You don't have any invoice.</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="portal_action_invoices_tree_spec" model="ir.actions.act_window.view">
|
||||||
|
<field name="act_window_id" ref="portal_action_invoices"/>
|
||||||
|
<field name="view_id" ref="account.invoice_tree"/>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
<field name="sequence" eval="0"/>
|
||||||
|
</record>
|
||||||
|
<record id="portal_action_invoices_form_spec" model="ir.actions.act_window.view">
|
||||||
|
<field name="act_window_id" ref="portal_action_invoices"/>
|
||||||
|
<field name="view_id" ref="account.invoice_form"/>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="sequence" eval="1"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="action_vendor_receipt" model="ir.actions.act_window">
|
<record id="portal_action_vouchers" model="ir.actions.act_window">
|
||||||
<field name="name">Customer Payment</field>
|
<field name="name">Refunds/Payments</field>
|
||||||
<field name="res_model">account.voucher</field>
|
<field name="res_model">account.voucher</field>
|
||||||
<field name="domain">[('journal_id.type', 'in', ['bank', 'cash']), ('type','=','receipt')]</field>
|
<field name="domain">[('journal_id.type', 'in', ['bank', 'cash']), ('type','=','receipt')]</field>
|
||||||
<field name="context">{'type':'receipt'}</field>
|
<field name="context">{'type':'receipt'}</field>
|
||||||
|
@ -68,19 +103,17 @@
|
||||||
<field name="help">You don't have any refunds or payments.</field>
|
<field name="help">You don't have any refunds or payments.</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem name="Quotations" id="portal_quotations" parent="portal.portal_orders"
|
<menuitem id="portal_quotations" parent="portal.portal_orders"
|
||||||
action="action_order_tree5" sequence="10"/>
|
action="action_quotations_portal" sequence="10"/>
|
||||||
<menuitem name="Sales Orders" id="portal_sales_orders" parent="portal.portal_orders"
|
<menuitem id="portal_sales_orders" parent="portal.portal_orders"
|
||||||
action="action_order_form" sequence="20"/>
|
action="action_orders_portal" sequence="20"/>
|
||||||
<menuitem name="Delivery Orders" id="portal_delivery" parent="portal.portal_orders"
|
<menuitem id="portal_delivery" parent="portal.portal_orders"
|
||||||
action="action_picking_tree" sequence="30"/>
|
action="action_picking_tree" sequence="30"/>
|
||||||
<menuitem name="Products" id="portal_products" parent="portal.portal_orders"
|
<menuitem id="portal_products" parent="portal.portal_orders"
|
||||||
action="product_normal_action" sequence="40"/>
|
action="product_normal_action" sequence="40"/>
|
||||||
|
<menuitem id="portal_invoices" parent="portal.portal_invoices_payements"
|
||||||
<menuitem name="Invoice" id="portal_invoices" parent="portal.portal_invoices_payements"
|
action="portal_action_invoices" sequence="10"/>
|
||||||
action="action_invoice_tree1" sequence="10"/>
|
<menuitem id="portal_payments" parent="portal.portal_invoices_payements"
|
||||||
<menuitem name="Refund/Payments" id="portal_payments" parent="portal.portal_invoices_payements"
|
action="portal_action_vouchers" sequence="20"/>
|
||||||
action="action_vendor_receipt" sequence="20"/>
|
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Business Applications
|
||||||
|
# Copyright (c) 2012-TODAY 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
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from openerp.osv import fields, osv
|
||||||
|
|
||||||
|
class sale_portal_config_settings(osv.TransientModel):
|
||||||
|
_inherit = 'account.config.settings'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'group_payment_options': fields.boolean('Show payment buttons to employees too',
|
||||||
|
implied_group='portal_sale.group_payment_options',
|
||||||
|
help="Show online payment options on Sale Orders and Customer Invoices to employees. "
|
||||||
|
"If not checked, these options are only visible to portal users."),
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- Add payment options to sale.order and invoice forms -->
|
||||||
|
<record model="ir.ui.view" id="portal_sale_payment_option_config">
|
||||||
|
<field name="model">account.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_config_settings"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//group[@name='bank_cash']/div" version="7.0" position="inside">
|
||||||
|
<div>
|
||||||
|
<field name="group_payment_options" class="oe_inline"/>
|
||||||
|
<label for="group_payment_options"/>
|
||||||
|
<button name='%(portal.action_acquirer_list)d' type="action"
|
||||||
|
string="Configure payment acquiring methods" class="oe_link"/>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -2,6 +2,18 @@
|
||||||
<openerp>
|
<openerp>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<record id="group_payment_options" model="res.groups">
|
||||||
|
<field name="name">View Online Payment Options</field>
|
||||||
|
<field name="category_id" ref="base.module_category_hidden"/>
|
||||||
|
<field name="comment">Members of this group see the online payment options
|
||||||
|
on Sale Orders and Customer Invoices. These options are meant for customers who are accessing
|
||||||
|
their documents through the portal.</field>
|
||||||
|
</record>
|
||||||
|
<!-- Make the payment_options group implied for all portal users -->
|
||||||
|
<record id="portal.group_portal" model="res.groups">
|
||||||
|
<field name="implied_ids" eval="[(4,ref('group_payment_options'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- Sale Portal Access Rules -->
|
<!-- Sale Portal Access Rules -->
|
||||||
<record id="portal_sale_order_user_rule" model="ir.rule">
|
<record id="portal_sale_order_user_rule" model="ir.rule">
|
||||||
<field name="name">Portal Personal Quotations/Sales Orders</field>
|
<field name="name">Portal Personal Quotations/Sales Orders</field>
|
||||||
|
|
|
@ -100,6 +100,23 @@ class product_uom(osv.osv):
|
||||||
def _factor_inv_write(self, cursor, user, id, name, value, arg, context=None):
|
def _factor_inv_write(self, cursor, user, id, name, value, arg, context=None):
|
||||||
return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context)
|
return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context)
|
||||||
|
|
||||||
|
def name_create(self, cr, uid, name, context=None):
|
||||||
|
""" The UoM category and factor are required, so we'll have to add temporary values
|
||||||
|
for imported UoMs """
|
||||||
|
uom_categ = self.pool.get('product.uom.categ')
|
||||||
|
# look for the category based on the english name, i.e. no context on purpose!
|
||||||
|
# TODO: should find a way to have it translated but not created until actually used
|
||||||
|
categ_misc = 'Unsorted/Imported Units'
|
||||||
|
categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
|
||||||
|
if categ_id:
|
||||||
|
categ_id = categ_id[0]
|
||||||
|
else:
|
||||||
|
categ_id, _ = uom_categ.name_create(cr, uid, categ_misc)
|
||||||
|
uom_id = self.create(cr, uid, {self._rec_name: name,
|
||||||
|
'category_id': categ_id,
|
||||||
|
'factor': 1})
|
||||||
|
return self.name_get(cr, uid, [uom_id], context=context)[0]
|
||||||
|
|
||||||
def create(self, cr, uid, data, context=None):
|
def create(self, cr, uid, data, context=None):
|
||||||
if 'factor_inv' in data:
|
if 'factor_inv' in data:
|
||||||
if data['factor_inv'] <> 1:
|
if data['factor_inv'] <> 1:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -19,13 +19,8 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from openerp.osv import osv
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
|
|
||||||
from osv import fields, osv, orm
|
|
||||||
from edi import EDIMixin
|
from edi import EDIMixin
|
||||||
from edi.models import edi
|
|
||||||
from tools import DEFAULT_SERVER_DATE_FORMAT
|
|
||||||
from tools.translate import _
|
from tools.translate import _
|
||||||
|
|
||||||
PURCHASE_ORDER_LINE_EDI_STRUCT = {
|
PURCHASE_ORDER_LINE_EDI_STRUCT = {
|
||||||
|
@ -62,16 +57,6 @@ PURCHASE_ORDER_EDI_STRUCT = {
|
||||||
class purchase_order(osv.osv, EDIMixin):
|
class purchase_order(osv.osv, EDIMixin):
|
||||||
_inherit = 'purchase.order'
|
_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):
|
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||||
"""Exports a purchase order"""
|
"""Exports a purchase order"""
|
||||||
edi_struct = dict(edi_struct or PURCHASE_ORDER_EDI_STRUCT)
|
edi_struct = dict(edi_struct or PURCHASE_ORDER_EDI_STRUCT)
|
||||||
|
@ -106,22 +91,26 @@ class purchase_order(osv.osv, EDIMixin):
|
||||||
# the desired company among the user's allowed companies
|
# the desired company among the user's allowed companies
|
||||||
|
|
||||||
self._edi_requires_attributes(('company_id','company_address'), edi_document)
|
self._edi_requires_attributes(('company_id','company_address'), edi_document)
|
||||||
res_partner_obj = self.pool.get('res.partner')
|
res_partner = self.pool.get('res.partner')
|
||||||
|
|
||||||
# imported company_address = new partner address
|
xid, company_name = edi_document.pop('company_id')
|
||||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
# Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
|
||||||
address_info = edi_document.pop('company_address')
|
company_address_edi = edi_document.pop('company_address')
|
||||||
address_info['customer'] = True
|
company_address_edi['name'] = company_name
|
||||||
if 'name' not in address_info:
|
company_address_edi['is_company'] = True
|
||||||
address_info['name'] = src_company_name
|
company_address_edi['__import_model'] = 'res.partner'
|
||||||
address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context)
|
company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
|
||||||
|
if company_address_edi.get('logo'):
|
||||||
|
company_address_edi['image'] = company_address_edi.pop('logo')
|
||||||
|
company_address_edi['supplier'] = True
|
||||||
|
partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
|
||||||
|
|
||||||
# modify edi_document to refer to new partner/address
|
# modify edi_document to refer to new partner
|
||||||
partner_address = res_partner_obj.browse(cr, uid, address_id, context=context)
|
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||||
edi_document.pop('partner_address', False) # ignored
|
partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
|
||||||
edi_document['partner_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
|
edi_document['partner_id'] = partner_edi_m2o
|
||||||
|
edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
|
||||||
return address_id
|
return partner_id
|
||||||
|
|
||||||
def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
|
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
|
# TODO: refactor into common place for purchase/sale, e.g. into product module
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<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 -->
|
<!-- EDI related Email Templates menu -->
|
||||||
<record model="ir.actions.act_window" id="action_email_templates">
|
<record model="ir.actions.act_window" id="action_email_templates">
|
||||||
<field name="name">Email Templates</field>
|
<field name="name">Email Templates</field>
|
||||||
|
@ -25,24 +13,19 @@
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</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 -->
|
so users can freely customize/delete them -->
|
||||||
<data noupdate="1">
|
<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 -->
|
<!--Email template -->
|
||||||
<record id="email_template_edi_purchase" model="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="email_from">${object.validator.email or ''}</field>
|
||||||
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
||||||
<field name="email_recipients">${object.partner_id.id}</field>
|
<field name="email_recipients">${object.partner_id.id}</field>
|
||||||
<field name="model_id" ref="purchase.model_purchase_order"/>
|
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||||
<field name="auto_delete" eval="True"/>
|
<field name="auto_delete" eval="True"/>
|
||||||
|
<field name="report_template" ref="report_purchase_quotation"/>
|
||||||
|
<field name="report_name">RFQ_${(object.name or '').replace('/','_')}</field>
|
||||||
<field name="body_html"><![CDATA[
|
<field name="body_html"><![CDATA[
|
||||||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||||
|
|
||||||
|
@ -61,15 +44,11 @@
|
||||||
% if object.partner_ref:
|
% if object.partner_ref:
|
||||||
Your reference: ${object.partner_ref}<br />
|
Your reference: ${object.partner_ref}<br />
|
||||||
% endif
|
% endif
|
||||||
|
% if object.user_id:
|
||||||
Your contact: <a href="mailto:${object.validator.email or ''}?subject=Order%20${object.name}">${object.validator.name}</a>
|
Your contact: <a href="mailto:${object.validator.email or ''}?subject=Order%20${object.name}">${object.validator.name}</a>
|
||||||
|
% endif
|
||||||
</p>
|
</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/>
|
<br/>
|
||||||
<p>If you have any question, do not hesitate to contact us.</p>
|
<p>If you have any question, do not hesitate to contact us.</p>
|
||||||
<p>Thank you!</p>
|
<p>Thank you!</p>
|
||||||
|
|
|
@ -384,29 +384,32 @@ class purchase_order(osv.osv):
|
||||||
'''
|
'''
|
||||||
This function opens a window to compose an email, with the edi purchase template message loaded by default
|
This function opens a window to compose an email, with the edi purchase template message loaded by default
|
||||||
'''
|
'''
|
||||||
mod_obj = self.pool.get('ir.model.data')
|
ir_model_data = self.pool.get('ir.model.data')
|
||||||
template = mod_obj.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')
|
try:
|
||||||
template_id = template and template[1] or False
|
template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
|
||||||
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
|
except ValueError:
|
||||||
res_id = res and res[1] or False
|
template_id = False
|
||||||
|
try:
|
||||||
|
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
|
||||||
|
except ValueError:
|
||||||
|
compose_form_id = False
|
||||||
ctx = dict(context)
|
ctx = dict(context)
|
||||||
ctx.update({
|
ctx.update({
|
||||||
'default_model': 'purchase.order',
|
'default_model': 'purchase.order',
|
||||||
'default_res_id': ids[0],
|
'default_res_id': ids[0],
|
||||||
'default_use_template': True,
|
'default_use_template': bool(template_id),
|
||||||
'default_template_id': template_id,
|
'default_template_id': template_id,
|
||||||
'default_composition_mode': 'comment',
|
'default_composition_mode': 'comment',
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
'view_type': 'form',
|
'view_type': 'form',
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'res_model': 'mail.compose.message',
|
'res_model': 'mail.compose.message',
|
||||||
'views': [(res_id, 'form')],
|
'views': [(compose_form_id, 'form')],
|
||||||
'view_id': res_id,
|
'view_id': compose_form_id,
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'context': ctx,
|
'context': ctx,
|
||||||
'nodestroy': True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#TODO: implement messages system
|
#TODO: implement messages system
|
||||||
|
|
|
@ -27,36 +27,36 @@
|
||||||
-
|
-
|
||||||
Then I export the purchase order via EDI
|
Then I export the purchase order via EDI
|
||||||
-
|
-
|
||||||
!python {model: edi.document}: |
|
!python {model: edi.edi}: |
|
||||||
order_pool = self.pool.get('purchase.order')
|
import json
|
||||||
order = order_pool.browse(cr, uid, ref("purchase_order_edi_1"))
|
order_pool = self.pool.get('purchase.order')
|
||||||
token = self.export_edi(cr, uid, [order])
|
order = order_pool.browse(cr, uid, ref("purchase_order_edi_1"))
|
||||||
assert token, 'Invalid EDI Token'
|
edi_doc = self.generate_edi(cr, uid, [order])
|
||||||
|
assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
|
||||||
-
|
-
|
||||||
Then I import a sample EDI document of a sale order
|
Then I import a sample EDI document of a sale order (v7.0)
|
||||||
-
|
-
|
||||||
!python {model: edi.document}: |
|
!python {model: edi.edi}: |
|
||||||
purchase_order_pool = self.pool.get('purchase.order')
|
purchase_order_pool = self.pool.get('purchase.order')
|
||||||
edi_document = {
|
edi_document = {
|
||||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
|
"__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_test",
|
||||||
"__module": "sale",
|
"__module": "sale",
|
||||||
"__model": "sale.order",
|
"__model": "sale.order",
|
||||||
"__import_module": "purchase",
|
"__import_module": "purchase",
|
||||||
"__import_model": "purchase.order",
|
"__import_model": "purchase.order",
|
||||||
"__version": [6,1,0],
|
"__version": [7,0,0],
|
||||||
"name": "SO008",
|
"name": "SO008",
|
||||||
"currency": {
|
"currency": {
|
||||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR",
|
"__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.EUR",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.currency",
|
"__model": "res.currency",
|
||||||
"code": "EUR",
|
"code": "EUR",
|
||||||
"symbol": "€",
|
"symbol": "€",
|
||||||
},
|
},
|
||||||
"date_order": "2011-09-13",
|
"date_order": "2011-09-13",
|
||||||
"partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"],
|
"partner_id": ["sale:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_test22", "Junjun wala"],
|
||||||
"partner_address": {
|
"partner_address": {
|
||||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh",
|
"__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_address_7wdsjasdjh",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.partner",
|
"__model": "res.partner",
|
||||||
"phone": "(+32).81.81.37.00",
|
"phone": "(+32).81.81.37.00",
|
||||||
|
@ -65,48 +65,48 @@
|
||||||
"zip": "1367",
|
"zip": "1367",
|
||||||
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||||
},
|
},
|
||||||
"company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."],
|
"company_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_company", "Supplier S.A."],
|
||||||
"company_address": {
|
"company_address": {
|
||||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address",
|
"__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_address",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.partner",
|
"__model": "res.partner",
|
||||||
"city": "Gerompont",
|
"city": "Gerompont",
|
||||||
"zip": "1367",
|
"zip": "1367",
|
||||||
"country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"],
|
"country_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.be", "Belgium"],
|
||||||
"phone": "(+32).81.81.37.00",
|
"phone": "(+32).81.81.37.00",
|
||||||
"street": "Chaussee de Namur 40",
|
"street": "Chaussee de Namur 40",
|
||||||
"street2": "mailbox 34",
|
"street2": "mailbox 34",
|
||||||
"bank_ids": [
|
"bank_ids": [
|
||||||
["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"]
|
["base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"order_line": [{
|
"order_line": [{
|
||||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0",
|
"__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeuI-SSP0",
|
||||||
"__module": "sale",
|
"__module": "sale",
|
||||||
"__model": "sale.order.line",
|
"__model": "sale.order.line",
|
||||||
"__import_module": "purchase",
|
"__import_module": "purchase",
|
||||||
"__import_model": "purchase.order.line",
|
"__import_model": "purchase.order.line",
|
||||||
"name": "PC Assemble SC234",
|
"name": "PC Assemble SC234",
|
||||||
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"],
|
"product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
|
||||||
"product_qty": 1.0,
|
"product_qty": 1.0,
|
||||||
"date_planned": "2011-09-30",
|
"date_planned": "2011-09-30",
|
||||||
"sequence": 10,
|
"sequence": 10,
|
||||||
"price_unit": 150.0,
|
"price_unit": 150.0,
|
||||||
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
|
"product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad",
|
"__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeadasdad",
|
||||||
"__module": "sale",
|
"__module": "sale",
|
||||||
"__model": "sale.order.line",
|
"__model": "sale.order.line",
|
||||||
"__import_module": "purchase",
|
"__import_module": "purchase",
|
||||||
"__import_model": "purchase.order.line",
|
"__import_model": "purchase.order.line",
|
||||||
"name": "PC on Demand",
|
"name": "PC on Demand",
|
||||||
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"],
|
"product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
|
||||||
"product_qty": 10.0,
|
"product_qty": 10.0,
|
||||||
"sequence": 11,
|
"sequence": 11,
|
||||||
"date_planned": "2011-09-15",
|
"date_planned": "2011-09-15",
|
||||||
"price_unit": 20.0,
|
"price_unit": 20.0,
|
||||||
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
|
"product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
|
new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||||
|
@ -114,6 +114,7 @@
|
||||||
order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
|
order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
|
||||||
|
|
||||||
# check bank info on partner
|
# check bank info on partner
|
||||||
|
assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order"
|
||||||
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||||
bank_info = order_new.partner_id.bank_ids[0]
|
bank_info = order_new.partner_id.bank_ids[0]
|
||||||
assert bank_info.acc_number == "Guys bank: 123477777-156113", 'Expected "Guys bank: 123477777-156113", got %s' % bank_info.acc_number
|
assert bank_info.acc_number == "Guys bank: 123477777-156113", 'Expected "Guys bank: 123477777-156113", got %s' % bank_info.acc_number
|
||||||
|
@ -133,3 +134,104 @@
|
||||||
assert purchase_line.product_qty == 10 , "product qty is not same"
|
assert purchase_line.product_qty == 10 , "product qty is not same"
|
||||||
else:
|
else:
|
||||||
raise AssertionError('unknown order line: %s' % purchase_line)
|
raise AssertionError('unknown order line: %s' % purchase_line)
|
||||||
|
-
|
||||||
|
"Then I import a sample EDI document of a sale order (v6.1 - to test backwards compatibility)"
|
||||||
|
-
|
||||||
|
!python {model: edi.edi}: |
|
||||||
|
purchase_order_pool = self.pool.get('purchase.order')
|
||||||
|
edi_document = {
|
||||||
|
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
|
||||||
|
"__module": "sale",
|
||||||
|
"__model": "sale.order",
|
||||||
|
"__import_module": "purchase",
|
||||||
|
"__import_model": "purchase.order",
|
||||||
|
"__version": [6,1,0],
|
||||||
|
"name": "SO08v61",
|
||||||
|
"currency": {
|
||||||
|
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.currency",
|
||||||
|
"code": "EUR",
|
||||||
|
"symbol": "€",
|
||||||
|
},
|
||||||
|
"date_order": "2011-09-13",
|
||||||
|
"partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"],
|
||||||
|
"partner_address": {
|
||||||
|
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.partner.address",
|
||||||
|
"phone": "(+32).81.81.37.00",
|
||||||
|
"street": "Chaussee de Namur 40",
|
||||||
|
"city": "Gerompont",
|
||||||
|
"zip": "1367",
|
||||||
|
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||||
|
},
|
||||||
|
"company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."],
|
||||||
|
"company_address": {
|
||||||
|
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.partner.address",
|
||||||
|
"city": "Gerompont",
|
||||||
|
"zip": "1367",
|
||||||
|
"country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"],
|
||||||
|
"phone": "(+32).81.81.37.00",
|
||||||
|
"street": "Chaussee de Namur 40",
|
||||||
|
"street2": "mailbox 34",
|
||||||
|
"bank_ids": [
|
||||||
|
["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Another bank: 123477700-156113"]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"order_line": [{
|
||||||
|
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0",
|
||||||
|
"__module": "sale",
|
||||||
|
"__model": "sale.order.line",
|
||||||
|
"__import_module": "purchase",
|
||||||
|
"__import_model": "purchase.order.line",
|
||||||
|
"name": "Basic PC",
|
||||||
|
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
|
||||||
|
"product_qty": 1.0,
|
||||||
|
"date_planned": "2011-09-30",
|
||||||
|
"sequence": 10,
|
||||||
|
"price_unit": 150.0,
|
||||||
|
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad",
|
||||||
|
"__module": "sale",
|
||||||
|
"__model": "sale.order.line",
|
||||||
|
"__import_module": "purchase",
|
||||||
|
"__import_model": "purchase.order.line",
|
||||||
|
"name": "Medium PC",
|
||||||
|
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
|
||||||
|
"product_qty": 10.0,
|
||||||
|
"sequence": 11,
|
||||||
|
"date_planned": "2011-09-15",
|
||||||
|
"price_unit": 20.0,
|
||||||
|
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||||
|
assert new_purchase_order_id, 'Purchase order import failed'
|
||||||
|
order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
|
||||||
|
|
||||||
|
# check bank info on partner
|
||||||
|
assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order"
|
||||||
|
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||||
|
bank_info = order_new.partner_id.bank_ids[0]
|
||||||
|
assert bank_info.acc_number == "Another bank: 123477700-156113", 'Expected "Another bank: 123477700-156113", got %s' % bank_info.acc_number
|
||||||
|
|
||||||
|
assert order_new.pricelist_id.name == 'Default Purchase Pricelist' , "Default Purchase Pricelist was not automatically assigned"
|
||||||
|
assert order_new.amount_total == 350, "Amount total is not same"
|
||||||
|
assert order_new.amount_untaxed == 350, "untaxed amount is not same"
|
||||||
|
assert len(order_new.order_line) == 2, "Purchase order lines number mismatch"
|
||||||
|
for purchase_line in order_new.order_line:
|
||||||
|
if purchase_line.name == 'Basic PC':
|
||||||
|
assert purchase_line.product_uom.name == "PCE" , "uom is not same"
|
||||||
|
assert purchase_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
|
||||||
|
assert purchase_line.product_qty == 1 , "product qty is not same"
|
||||||
|
elif purchase_line.name == 'Medium PC':
|
||||||
|
assert purchase_line.product_uom.name == "PCE" , "uom is not same"
|
||||||
|
assert purchase_line.price_unit == 20 , "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
|
||||||
|
assert purchase_line.product_qty == 10 , "product qty is not same"
|
||||||
|
else:
|
||||||
|
raise AssertionError('unknown order line: %s' % purchase_line)
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -19,13 +19,8 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from openerp.osv import osv
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
|
|
||||||
from osv import fields, osv, orm
|
|
||||||
from edi import EDIMixin
|
from edi import EDIMixin
|
||||||
from edi.models import edi
|
|
||||||
from tools import DEFAULT_SERVER_DATE_FORMAT
|
|
||||||
|
|
||||||
SALE_ORDER_LINE_EDI_STRUCT = {
|
SALE_ORDER_LINE_EDI_STRUCT = {
|
||||||
'sequence': True,
|
'sequence': True,
|
||||||
|
@ -65,15 +60,6 @@ SALE_ORDER_EDI_STRUCT = {
|
||||||
class sale_order(osv.osv, EDIMixin):
|
class sale_order(osv.osv, EDIMixin):
|
||||||
_inherit = 'sale.order'
|
_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):
|
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||||
"""Exports a Sale order"""
|
"""Exports a Sale order"""
|
||||||
edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT)
|
edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT)
|
||||||
|
@ -102,34 +88,36 @@ class sale_order(osv.osv, EDIMixin):
|
||||||
edi_doc_list.append(edi_doc)
|
edi_doc_list.append(edi_doc)
|
||||||
return edi_doc_list
|
return edi_doc_list
|
||||||
|
|
||||||
|
|
||||||
def _edi_import_company(self, cr, uid, edi_document, context=None):
|
def _edi_import_company(self, cr, uid, edi_document, context=None):
|
||||||
# TODO: for multi-company setups, we currently import the document in the
|
# 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
|
# user's current company, but we should perhaps foresee a way to select
|
||||||
# the desired company among the user's allowed companies
|
# the desired company among the user's allowed companies
|
||||||
|
|
||||||
self._edi_requires_attributes(('company_id','company_address'), edi_document)
|
self._edi_requires_attributes(('company_id','company_address'), edi_document)
|
||||||
res_partner_obj = self.pool.get('res.partner')
|
res_partner = self.pool.get('res.partner')
|
||||||
|
|
||||||
# imported company_address = new partner address
|
xid, company_name = edi_document.pop('company_id')
|
||||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
# Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
|
||||||
|
company_address_edi = edi_document.pop('company_address')
|
||||||
|
company_address_edi['name'] = company_name
|
||||||
|
company_address_edi['is_company'] = True
|
||||||
|
company_address_edi['__import_model'] = 'res.partner'
|
||||||
|
company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
|
||||||
|
if company_address_edi.get('logo'):
|
||||||
|
company_address_edi['image'] = company_address_edi.pop('logo')
|
||||||
|
company_address_edi['customer'] = True
|
||||||
|
partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
|
||||||
|
|
||||||
address_info = edi_document.pop('company_address')
|
# modify edi_document to refer to new partner
|
||||||
address_info['supplier'] = True
|
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||||
if 'name' not in address_info:
|
partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
|
||||||
address_info['name'] = src_company_name
|
edi_document['partner_id'] = partner_edi_m2o
|
||||||
|
edi_document['partner_invoice_id'] = partner_edi_m2o
|
||||||
|
edi_document['partner_shipping_id'] = partner_edi_m2o
|
||||||
|
|
||||||
address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context)
|
edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
|
||||||
|
return partner_id
|
||||||
|
|
||||||
# modify edi_document to refer to new partner/address
|
|
||||||
partner_address = res_partner_obj.browse(cr, uid, address_id, context=context)
|
|
||||||
edi_document.pop('partner_address', False) # ignored
|
|
||||||
address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context)
|
|
||||||
edi_document['partner_id'] = address_edi_m2o
|
|
||||||
edi_document['partner_invoice_id'] = address_edi_m2o
|
|
||||||
edi_document['partner_shipping_id'] = address_edi_m2o
|
|
||||||
|
|
||||||
return address_id
|
|
||||||
|
|
||||||
def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
|
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
|
# TODO: refactor into common place for purchase/sale, e.g. into product module
|
||||||
|
@ -171,7 +159,6 @@ class sale_order(osv.osv, EDIMixin):
|
||||||
currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
|
currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
|
||||||
order_currency = res_currency.browse(cr, uid, currency_id)
|
order_currency = res_currency.browse(cr, uid, currency_id)
|
||||||
|
|
||||||
date_order = edi_document['date_order']
|
|
||||||
partner_ref = edi_document.pop('partner_ref', False)
|
partner_ref = edi_document.pop('partner_ref', False)
|
||||||
edi_document['client_order_ref'] = edi_document['name']
|
edi_document['client_order_ref'] = edi_document['name']
|
||||||
edi_document['name'] = partner_ref or edi_document['name']
|
edi_document['name'] = partner_ref or edi_document['name']
|
||||||
|
@ -185,7 +172,7 @@ class sale_order(osv.osv, EDIMixin):
|
||||||
|
|
||||||
order_lines = edi_document['order_line']
|
order_lines = edi_document['order_line']
|
||||||
for order_line in order_lines:
|
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']
|
order_line['product_uom_qty'] = order_line['product_qty']
|
||||||
del order_line['product_qty']
|
del order_line['product_qty']
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
<!-- EDI related Email Templates menu -->
|
<!-- EDI related Email Templates menu -->
|
||||||
<record model="ir.actions.act_window" id="action_email_templates">
|
<record model="ir.actions.act_window" id="action_email_templates">
|
||||||
<field name="name">Email Templates</field>
|
<field name="name">Email Templates</field>
|
||||||
|
@ -15,19 +14,20 @@
|
||||||
<menuitem id="base.menu_sales_configuration_misc" name="Miscellaneous" parent="base.menu_base_config" sequence="75"/>
|
<menuitem id="base.menu_sales_configuration_misc" name="Miscellaneous" parent="base.menu_base_config" sequence="75"/>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
|
|
||||||
<!-- Mail template is done in a NOUPDATE block
|
<!-- Mail template is done in a NOUPDATE block
|
||||||
so users can freely customize/delete them -->
|
so users can freely customize/delete them -->
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
|
|
||||||
<!--Email template -->
|
<!--Email template -->
|
||||||
<record id="email_template_edi_sale" model="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 Email</field>
|
||||||
<field name="email_from">${object.user_id.email or ''}</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="subject">${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
|
||||||
<field name="email_recipients">${object.partner_invoice_id.id}</field>
|
<field name="email_recipients">${object.partner_invoice_id.id}</field>
|
||||||
<field name="model_id" ref="sale.model_sale_order"/>
|
<field name="model_id" ref="sale.model_sale_order"/>
|
||||||
<field name="auto_delete" eval="True"/>
|
<field name="auto_delete" eval="True"/>
|
||||||
|
<field name="report_template" ref="report_sale_order"/>
|
||||||
|
<field name="report_name">${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||||
<field name="body_html"><![CDATA[
|
<field name="body_html"><![CDATA[
|
||||||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||||
|
|
||||||
|
@ -46,16 +46,12 @@
|
||||||
% if object.client_order_ref:
|
% if object.client_order_ref:
|
||||||
Your reference: ${object.client_order_ref}<br />
|
Your reference: ${object.client_order_ref}<br />
|
||||||
% endif
|
% endif
|
||||||
|
% if object.user_id:
|
||||||
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Order%20${object.name}">${object.user_id.name}</a>
|
Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Order%20${object.name}">${object.user_id.name}</a>
|
||||||
|
% endif
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
% if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state != 'draft':
|
||||||
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)
|
comp_name = quote(object.company_id.name)
|
||||||
order_name = quote(object.name)
|
order_name = quote(object.name)
|
||||||
|
|
|
@ -636,30 +636,33 @@ class sale_order(osv.osv):
|
||||||
This function opens a window to compose an email, with the edi sale template message loaded by default
|
This function opens a window to compose an email, with the edi sale template message loaded by default
|
||||||
'''
|
'''
|
||||||
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
|
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
|
||||||
mod_obj = self.pool.get('ir.model.data')
|
ir_model_data = self.pool.get('ir.model.data')
|
||||||
template = mod_obj.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')
|
try:
|
||||||
template_id = template and template[1] or False
|
template_id = ir_model_data.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')[1]
|
||||||
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
|
except ValueError:
|
||||||
res_id = res and res[1] or False
|
template_id = False
|
||||||
|
try:
|
||||||
|
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
|
||||||
|
except ValueError:
|
||||||
|
compose_form_id = False
|
||||||
ctx = dict(context)
|
ctx = dict(context)
|
||||||
ctx.update({
|
ctx.update({
|
||||||
'default_model': 'sale.order',
|
'default_model': 'sale.order',
|
||||||
'default_res_id': ids[0],
|
'default_res_id': ids[0],
|
||||||
'default_use_template': True,
|
'default_use_template': bool(template_id),
|
||||||
'default_template_id': template_id,
|
'default_template_id': template_id,
|
||||||
'default_composition_mode': 'comment',
|
'default_composition_mode': 'comment',
|
||||||
'mark_so_as_sent': True
|
'mark_so_as_sent': True
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
'view_type': 'form',
|
'view_type': 'form',
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'res_model': 'mail.compose.message',
|
'res_model': 'mail.compose.message',
|
||||||
'views': [(res_id, 'form')],
|
'views': [(compose_form_id, 'form')],
|
||||||
'view_id': res_id,
|
'view_id': compose_form_id,
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'context': ctx,
|
'context': ctx,
|
||||||
'nodestroy': True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def action_done(self, cr, uid, ids, context=None):
|
def action_done(self, cr, uid, ids, context=None):
|
||||||
|
|
|
@ -166,8 +166,8 @@
|
||||||
<header>
|
<header>
|
||||||
<button name="invoice_recreate" states="invoice_except" string="Recreate Invoice" groups="base.group_user"/>
|
<button name="invoice_recreate" states="invoice_except" string="Recreate Invoice" groups="base.group_user"/>
|
||||||
<button name="invoice_corrected" states="invoice_except" string="Ignore Exception" groups="base.group_user"/>
|
<button name="invoice_corrected" states="invoice_except" string="Ignore Exception" groups="base.group_user"/>
|
||||||
<button name="action_quotation_send" string="Send by Mail" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
|
<button name="action_quotation_send" string="Send by Email" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
|
||||||
<button name="action_quotation_send" string="Send by Mail" type="object" states="sent" groups="base.group_user"/>
|
<button name="action_quotation_send" string="Send by Email" type="object" states="sent" groups="base.group_user"/>
|
||||||
<button name="print_quotation" string="Print" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
|
<button name="print_quotation" string="Print" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
|
||||||
<button name="print_quotation" string="Print" type="object" states="sent" groups="base.group_user"/>
|
<button name="print_quotation" string="Print" type="object" states="sent" groups="base.group_user"/>
|
||||||
<button name="action_button_confirm" states="draft" string="Confirm Sale" type="object" groups="base.group_user"/>
|
<button name="action_button_confirm" states="draft" string="Confirm Sale" type="object" groups="base.group_user"/>
|
||||||
|
@ -358,7 +358,7 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_order_form" model="ir.actions.act_window">
|
<record id="action_orders" model="ir.actions.act_window">
|
||||||
<field name="name">Sale Orders</field>
|
<field name="name">Sale Orders</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
|
@ -377,9 +377,9 @@
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<menuitem action="action_order_form" id="menu_sale_order" parent="base.menu_sales" sequence="5" groups="base.group_sale_salesman,base.group_sale_manager"/>
|
<menuitem action="action_orders" id="menu_sale_order" parent="base.menu_sales" sequence="5" groups="base.group_sale_salesman,base.group_sale_manager"/>
|
||||||
|
|
||||||
<record id="action_order_tree2" model="ir.actions.act_window">
|
<record id="action_orders_exception" model="ir.actions.act_window">
|
||||||
<field name="name">Sales in Exception</field>
|
<field name="name">Sales in Exception</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
|
@ -390,7 +390,7 @@
|
||||||
<field name="search_view_id" ref="view_sales_order_filter"/>
|
<field name="search_view_id" ref="view_sales_order_filter"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_order_tree4" model="ir.actions.act_window">
|
<record id="action_orders_in_progress" model="ir.actions.act_window">
|
||||||
<field name="name">Sales Order in Progress</field>
|
<field name="name">Sales Order in Progress</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
|
@ -401,7 +401,7 @@
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<record id="action_order_tree5" model="ir.actions.act_window">
|
<record id="action_quotations" model="ir.actions.act_window">
|
||||||
<field name="name">Quotations</field>
|
<field name="name">Quotations</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
|
@ -427,7 +427,7 @@
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem id="menu_sale_quotations"
|
<menuitem id="menu_sale_quotations"
|
||||||
action="action_order_tree5" parent="base.menu_sales"
|
action="action_quotations" parent="base.menu_sales"
|
||||||
sequence="4"/>
|
sequence="4"/>
|
||||||
|
|
||||||
<record id="action_order_tree" model="ir.actions.act_window">
|
<record id="action_order_tree" model="ir.actions.act_window">
|
||||||
|
|
|
@ -25,70 +25,71 @@
|
||||||
-
|
-
|
||||||
Then I export the sale order via EDI
|
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')
|
sale_order = self.pool.get('sale.order')
|
||||||
so = sale_order.browse(cr, uid, ref("sale_order_edi_1"))
|
so = sale_order.browse(cr, uid, ref("sale_order_edi_1"))
|
||||||
token = self.export_edi(cr, uid, [so])
|
edi_doc = self.generate_edi(cr, uid, [so])
|
||||||
assert token, 'Invalid EDI Token'
|
assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
|
||||||
-
|
-
|
||||||
Then I import a sample EDI document of a purchase order
|
"Then I import a sample EDI document of a purchase order (v7.0)"
|
||||||
-
|
-
|
||||||
!python {model: edi.document}: |
|
!python {model: edi.edi}: |
|
||||||
sale_order_pool = self.pool.get('sale.order')
|
sale_order_pool = self.pool.get('sale.order')
|
||||||
edi_document = {
|
edi_document = {
|
||||||
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test",
|
"__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_test",
|
||||||
"__module": "purchase",
|
"__module": "purchase",
|
||||||
"__model": "purchase.order",
|
"__model": "purchase.order",
|
||||||
"__import_module": "sale",
|
"__import_module": "sale",
|
||||||
"__import_model": "sale.order",
|
"__import_model": "sale.order",
|
||||||
"__version": [6,1,0],
|
"__version": [7,0,0],
|
||||||
"name": "PO00011",
|
"name": "PO00011",
|
||||||
"date_order": "2011-09-12",
|
"date_order": "2011-09-12",
|
||||||
"currency": {
|
"currency": {
|
||||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR",
|
"__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.EUR",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.currency",
|
"__model": "res.currency",
|
||||||
"code": "EUR",
|
"code": "EUR",
|
||||||
"symbol": "€",
|
"symbol": "€",
|
||||||
},
|
},
|
||||||
"company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."],
|
"company_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.main_company", "Client S.A."],
|
||||||
"company_address": {
|
"company_address": {
|
||||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address",
|
"__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.some_address",
|
||||||
"__module": "base",
|
"__module": "base",
|
||||||
"__model": "res.partner",
|
"__model": "res.partner",
|
||||||
"phone": "(+32).81.81.37.00",
|
"phone": "(+32).81.81.37.00",
|
||||||
"street": "Chaussee de Namur 40",
|
"street": "Chaussee de Namur 40",
|
||||||
"city": "Gerompont",
|
"city": "Gerompont",
|
||||||
"zip": "1367",
|
"zip": "1367",
|
||||||
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
"country_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.be", "Belgium"],
|
||||||
"bank_ids": [
|
"bank_ids": [
|
||||||
["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"]
|
["base:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_bank-adaWadsadasdDJzGbp","Another bank: 032465700-156700"]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"partner_id": ["purchase:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_test20", "jones white"],
|
"partner_id": ["purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_test20", "jones white"],
|
||||||
"order_line": [{
|
"order_line": [{
|
||||||
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ",
|
"__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-AlhsVDZGoKvJ",
|
||||||
"__module": "purchase",
|
"__module": "purchase",
|
||||||
"__model": "purchase.order.line",
|
"__model": "purchase.order.line",
|
||||||
"__import_module": "sale",
|
"__import_module": "sale",
|
||||||
"__import_model": "sale.order.line",
|
"__import_model": "sale.order.line",
|
||||||
"name": "PC Assemble SC234",
|
"name": "PC Assemble SC234",
|
||||||
"price_unit": 150.0,
|
"price_unit": 150.0,
|
||||||
"product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
|
"product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||||
"product_qty": 1.0,
|
"product_qty": 1.0,
|
||||||
"product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"],
|
"product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e",
|
"__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-Alsads33e",
|
||||||
"__module": "purchase",
|
"__module": "purchase",
|
||||||
"__model": "purchase.order.line",
|
"__model": "purchase.order.line",
|
||||||
"__import_module": "sale",
|
"__import_module": "sale",
|
||||||
"__import_model": "sale.order.line",
|
"__import_model": "sale.order.line",
|
||||||
"name": "PC on Demand",
|
"name": "PC on Demand",
|
||||||
"price_unit": 100.0,
|
"price_unit": 100.0,
|
||||||
"product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
|
"product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
|
||||||
"product_qty": 2.0,
|
"product_qty": 2.0,
|
||||||
"product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"],
|
"product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"],
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context)
|
new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||||
|
@ -96,9 +97,10 @@
|
||||||
order_new = sale_order_pool.browse(cr, uid, new_sale_order_id)
|
order_new = sale_order_pool.browse(cr, uid, new_sale_order_id)
|
||||||
|
|
||||||
# check bank info on partner
|
# check bank info on partner
|
||||||
|
assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order"
|
||||||
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||||
bank_info = order_new.partner_id.bank_ids[0]
|
bank_info = order_new.partner_id.bank_ids[0]
|
||||||
assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number
|
assert bank_info.acc_number == "Another bank: 032465700-156700", 'Expected "Another bank: 032465700-156700", got %s' % bank_info.acc_number
|
||||||
|
|
||||||
assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
|
assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
|
||||||
assert order_new.amount_total == 350, "Amount total is wrong"
|
assert order_new.amount_total == 350, "Amount total is wrong"
|
||||||
|
@ -115,3 +117,101 @@
|
||||||
assert sale_line.product_uom_qty == 2 , "product qty is not same"
|
assert sale_line.product_uom_qty == 2 , "product qty is not same"
|
||||||
else:
|
else:
|
||||||
raise AssertionError('unknown order line: %s' % sale_line)
|
raise AssertionError('unknown order line: %s' % sale_line)
|
||||||
|
-
|
||||||
|
"Then I import a sample EDI document of a purchase order (v6.1 - to test backwards compatibility)"
|
||||||
|
-
|
||||||
|
!python {model: edi.edi}: |
|
||||||
|
sale_order_pool = self.pool.get('sale.order')
|
||||||
|
edi_document = {
|
||||||
|
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test",
|
||||||
|
"__module": "purchase",
|
||||||
|
"__model": "purchase.order",
|
||||||
|
"__import_module": "sale",
|
||||||
|
"__import_model": "sale.order",
|
||||||
|
"__version": [6,1,0],
|
||||||
|
"name": "PO00011-v61",
|
||||||
|
"date_order": "2011-09-12",
|
||||||
|
"currency": {
|
||||||
|
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.currency",
|
||||||
|
"code": "EUR",
|
||||||
|
"symbol": "€",
|
||||||
|
},
|
||||||
|
"company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."],
|
||||||
|
"company_address": {
|
||||||
|
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.partner.address",
|
||||||
|
"phone": "(+32).81.81.37.00",
|
||||||
|
"street": "Chaussee de Namur 40",
|
||||||
|
"city": "Gerompont",
|
||||||
|
"zip": "1367",
|
||||||
|
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||||
|
"bank_ids": [
|
||||||
|
["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"partner_id": ["purchase:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_test20", "jones white"],
|
||||||
|
"partner_address": {
|
||||||
|
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||||
|
"__module": "base",
|
||||||
|
"__model": "res.partner.address",
|
||||||
|
"phone": "(+32).81.81.37.00",
|
||||||
|
"street": "Chaussee de Namur 40",
|
||||||
|
"city": "Gerompont",
|
||||||
|
"zip": "1367",
|
||||||
|
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||||
|
},
|
||||||
|
"order_line": [{
|
||||||
|
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ",
|
||||||
|
"__module": "purchase",
|
||||||
|
"__model": "purchase.order.line",
|
||||||
|
"__import_module": "sale",
|
||||||
|
"__import_model": "sale.order.line",
|
||||||
|
"name": "Basic PC",
|
||||||
|
"date_planned": "2011-09-30",
|
||||||
|
"price_unit": 150.0,
|
||||||
|
"product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
|
||||||
|
"product_qty": 1.0,
|
||||||
|
"product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "PCE"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e",
|
||||||
|
"__module": "purchase",
|
||||||
|
"__model": "purchase.order.line",
|
||||||
|
"__import_module": "sale",
|
||||||
|
"__import_model": "sale.order.line",
|
||||||
|
"name": "Medium PC",
|
||||||
|
"date_planned": "2011-09-15",
|
||||||
|
"price_unit": 100.0,
|
||||||
|
"product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
|
||||||
|
"product_qty": 2.0,
|
||||||
|
"product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "PCE"],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||||
|
assert new_sale_order_id, 'Sale order import failed'
|
||||||
|
order_new = sale_order_pool.browse(cr, uid, new_sale_order_id)
|
||||||
|
|
||||||
|
# check bank info on partner
|
||||||
|
assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order"
|
||||||
|
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||||
|
bank_info = order_new.partner_id.bank_ids[0]
|
||||||
|
assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number
|
||||||
|
|
||||||
|
assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
|
||||||
|
assert order_new.amount_total == 350, "Amount total is wrong"
|
||||||
|
assert order_new.amount_untaxed == 350, "Untaxed amount is wrong"
|
||||||
|
assert len(order_new.order_line) == 2, "Sale order lines mismatch"
|
||||||
|
for sale_line in order_new.order_line:
|
||||||
|
if sale_line.name == 'Basic PC':
|
||||||
|
assert sale_line.product_uom.name == "PCE" , "uom is not same"
|
||||||
|
assert sale_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(sale_line.price_unit,)
|
||||||
|
assert sale_line.product_uom_qty == 1 , "product qty is not same"
|
||||||
|
elif sale_line.name == 'Medium PC':
|
||||||
|
assert sale_line.product_uom.name == "PCE" , "uom is not same"
|
||||||
|
assert sale_line.price_unit == 100 , "unit price is not same, got %s, expected 100"%(sale_line.price_unit,)
|
||||||
|
assert sale_line.product_uom_qty == 2 , "product qty is not same"
|
||||||
|
else:
|
||||||
|
raise AssertionError('unknown order line: %s' % sale_line)
|
Loading…
Reference in New Issue