[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
|
||||
'''
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
template = mod_obj.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')
|
||||
template_id = template and template[1] or False
|
||||
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
|
||||
res_id = res and res[1] or False
|
||||
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
try:
|
||||
template_id = ir_model_data.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1]
|
||||
except ValueError:
|
||||
template_id = False
|
||||
try:
|
||||
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
|
||||
except ValueError:
|
||||
compose_form_id = False
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_model': 'account.invoice',
|
||||
'default_res_id': ids[0],
|
||||
'default_use_template': True,
|
||||
'default_use_template': bool(template_id),
|
||||
'default_template_id': template_id,
|
||||
'default_composition_mode': 'comment',
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(res_id, 'form')],
|
||||
'view_id': res_id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [(compose_form_id, 'form')],
|
||||
'view_id': compose_form_id,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
def confirm_paid(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,9 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields, osv, orm
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from edi.models import edi
|
||||
|
||||
INVOICE_LINE_EDI_STRUCT = {
|
||||
'name': True,
|
||||
|
@ -71,16 +70,6 @@ INVOICE_EDI_STRUCT = {
|
|||
class account_invoice(osv.osv, EDIMixin):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
def action_invoice_sent(self, cr, uid, ids, context=None):
|
||||
""""Override this method to add a link to mail"""
|
||||
if context is None:
|
||||
context = {}
|
||||
invoice_objs = self.browse(cr, uid, ids, context=context)
|
||||
edi_token = self.pool.get('edi.document').export_edi(cr, uid, invoice_objs, context = context)[0]
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
|
||||
return super(account_invoice, self).action_invoice_sent(cr, uid, ids, context=ctx)
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Exports a supplier or customer invoice"""
|
||||
edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT)
|
||||
|
@ -111,8 +100,8 @@ class account_invoice(osv.osv, EDIMixin):
|
|||
return tax_account
|
||||
|
||||
def _edi_invoice_account(self, cr, uid, partner_id, invoice_type, context=None):
|
||||
partner_pool = self.pool.get('res.partner')
|
||||
partner = partner_pool.browse(cr, uid, partner_id, context=context)
|
||||
res_partner = self.pool.get('res.partner')
|
||||
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||
if invoice_type in ('out_invoice', 'out_refund'):
|
||||
invoice_account = partner.property_account_receivable
|
||||
else:
|
||||
|
@ -136,31 +125,30 @@ class account_invoice(osv.osv, EDIMixin):
|
|||
self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
|
||||
res_partner = self.pool.get('res.partner')
|
||||
|
||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
||||
xid, company_name = edi_document.pop('company_id')
|
||||
# Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
|
||||
company_address_edi = edi_document.pop('company_address')
|
||||
company_address_edi['name'] = company_name
|
||||
company_address_edi['is_company'] = True
|
||||
company_address_edi['__import_model'] = 'res.partner'
|
||||
company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
|
||||
if company_address_edi.get('logo'):
|
||||
company_address_edi['image'] = company_address_edi.pop('logo')
|
||||
|
||||
invoice_type = edi_document['type']
|
||||
partner_value = {}
|
||||
if invoice_type in ('out_invoice', 'out_refund'):
|
||||
partner_value.update({'customer': True})
|
||||
if invoice_type in ('in_invoice', 'in_refund'):
|
||||
partner_value.update({'supplier': True})
|
||||
|
||||
# imported company_address = new partner address
|
||||
address_info = edi_document.pop('company_address')
|
||||
if '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)
|
||||
if invoice_type.startswith('out_'):
|
||||
company_address_edi['customer'] = True
|
||||
else:
|
||||
company_address_edi['supplier'] = True
|
||||
partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
|
||||
|
||||
# modify edi_document to refer to new partner
|
||||
partner_address = res_partner.browse(cr, uid, address_id, context=context)
|
||||
address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context)
|
||||
edi_document['partner_id'] = address_edi_m2o
|
||||
edi_document.pop('partner_address', False) # ignored
|
||||
|
||||
return address_id
|
||||
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||
partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
|
||||
edi_document['partner_id'] = partner_edi_m2o
|
||||
edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
|
||||
|
||||
return partner_id
|
||||
|
||||
def edi_import(self, cr, uid, edi_document, context=None):
|
||||
""" 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_')
|
||||
edi_document['type'] = invoice_type
|
||||
|
||||
#import company as a new partner
|
||||
# import company as a new partner
|
||||
partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
|
||||
|
||||
# Set Account
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- EDI Export + Send email Action -->
|
||||
<record id="ir_actions_server_edi_invoice" model="ir.actions.server">
|
||||
<field name="code">if (object.type in ('out_invoice', 'out_refund')) and not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='account.email_template_edi_invoice', context=context)</field>
|
||||
<field eval="6" name="sequence"/>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="account.model_account_invoice"/>
|
||||
<field name="condition">True</field>
|
||||
<field name="name">Auto-email confirmed invoices</field>
|
||||
</record>
|
||||
|
||||
<!-- EDI related Email Templates menu -->
|
||||
<record model="ir.actions.act_window" id="action_email_templates">
|
||||
<field name="name">Email Templates</field>
|
||||
|
@ -27,22 +16,19 @@
|
|||
|
||||
</data>
|
||||
|
||||
<!-- Mail template and workflow bindings are done in a NOUPDATE block
|
||||
<!-- Mail template are declared in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
<!-- bind the mailing server action to invoice open activity -->
|
||||
<record id="account.act_open" model="workflow.activity">
|
||||
<field name="action_id" ref="ir_actions_server_edi_invoice"/>
|
||||
</record>
|
||||
|
||||
<!--Email template -->
|
||||
<record id="email_template_edi_invoice" model="email.template">
|
||||
<field name="name">Automated Invoice Notification Mail</field>
|
||||
<field name="name">Invoice - Send by Email</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_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); ">
|
||||
|
||||
|
@ -58,15 +44,11 @@
|
|||
% 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>
|
||||
|
||||
<p>
|
||||
You can view the invoice document, download it and pay online using the following link:
|
||||
</p>
|
||||
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #FFF; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
|
||||
href="${ctx.get('edi_web_url_view') or ''}">View Invoice</a>
|
||||
|
||||
% if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
|
||||
<%
|
||||
comp_name = quote(object.company_id.name)
|
||||
|
|
|
@ -227,7 +227,7 @@
|
|||
</div>
|
||||
</group>
|
||||
<separator string="Bank & Cash"/>
|
||||
<group>
|
||||
<group name="bank_cash">
|
||||
<label for="id" string="Configuration"/>
|
||||
<div>
|
||||
<div>
|
||||
|
|
|
@ -38,45 +38,46 @@
|
|||
-
|
||||
Then I export the customer invoice
|
||||
-
|
||||
!python {model: edi.document}: |
|
||||
!python {model: edi.edi}: |
|
||||
import json
|
||||
invoice_pool = self.pool.get('account.invoice')
|
||||
invoice = invoice_pool.browse(cr, uid, ref("invoice_edi_1"))
|
||||
token = self.export_edi(cr, uid, [invoice])
|
||||
assert token, 'Invalid EDI Token'
|
||||
edi_doc = self.generate_edi(cr, uid, [invoice])
|
||||
assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
|
||||
-
|
||||
Then I import a sample EDI document of another customer invoice
|
||||
Then I import a sample EDI document of another customer invoice from OpenERP 7.0
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
import time
|
||||
edi_document = {
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms",
|
||||
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.random_invoice_763jsms",
|
||||
"__module": "account",
|
||||
"__model": "account.invoice",
|
||||
"__version": [6,1,0],
|
||||
"internal_number": time.strftime("SAJ/%Y/002"),
|
||||
"__version": [7,0,0],
|
||||
"internal_number": time.strftime("SAJ/%Y/070"),
|
||||
"company_address": {
|
||||
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.main_address",
|
||||
"__id": "base:b33adf8a-decd-11f0-a4de-702a04e25700.main_address",
|
||||
"__module": "base",
|
||||
"__model": "res.partner",
|
||||
"city": "Gerompont",
|
||||
"name": "Company main address",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.be", "Belgium"],
|
||||
"country_id": ["base:b33adf8a-decd-11f0-a4de-702a04e25700.be", "Belgium"],
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"bank_ids": [
|
||||
["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 123465789-156113"]
|
||||
["base:b33adf8a-decd-11f0-a4de-702a04e25700.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 70-123465789-156113"]
|
||||
],
|
||||
},
|
||||
"company_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_company_test11", "Thomson pvt. ltd."],
|
||||
"company_id": ["account:b33adf8a-decd-11f0-a4de-702a04e25700.res_company_test11", "Thomson pvt. ltd."],
|
||||
"currency": {
|
||||
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.EUR",
|
||||
"__id": "base:b33adf8a-decd-11f0-a4de-702a04e25700.EUR",
|
||||
"__module": "base",
|
||||
"__model": "res.currency",
|
||||
"code": "EUR",
|
||||
"symbol": "€",
|
||||
},
|
||||
"partner_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_test20", "Junjun wala"],
|
||||
"partner_id": ["account:b33adf8a-decd-11f0-a4de-702a04e25700.res_partner_test20", "Junjun wala"],
|
||||
"partner_address": {
|
||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||
"__module": "base",
|
||||
|
@ -91,7 +92,7 @@
|
|||
"date_invoice": time.strftime('%Y-%m-%d'),
|
||||
"name": "sample invoice",
|
||||
"tax_line": [{
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_tax-4g4EutbiEMVl",
|
||||
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_tax-4g4EutbiEMVl",
|
||||
"__module": "account",
|
||||
"__model": "account.invoice.tax",
|
||||
"amount": 1000.0,
|
||||
|
@ -102,21 +103,21 @@
|
|||
"invoice_line": [{
|
||||
"__module": "account",
|
||||
"__model": "account.invoice.line",
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so",
|
||||
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
|
||||
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_line-1RP3so",
|
||||
"uos_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_uom_unit", "Unit"],
|
||||
"name": "PC Assemble SC234",
|
||||
"price_unit": 10.0,
|
||||
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||
"product_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||
"quantity": 1.0
|
||||
},
|
||||
{
|
||||
"__module": "account",
|
||||
"__model": "account.invoice.line",
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5",
|
||||
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
|
||||
"__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.account_invoice_line-u2XV5",
|
||||
"uos_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_uom_unit", "Unit"],
|
||||
"name": "PC on Demand",
|
||||
"price_unit": 100.0,
|
||||
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
|
||||
"product_id": ["product:b33adf8a-decd-11f0-a4de-702a04e25700.product_product_5", "[PC-DEM] PC on Demand"],
|
||||
"quantity": 5.0
|
||||
}]
|
||||
}
|
||||
|
@ -125,12 +126,13 @@
|
|||
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 bank_info.acc_number == "Sample bank: 70-123465789-156113", 'Expected "Sample bank: 70-123465789-156113", got %s' % bank_info.acc_number
|
||||
|
||||
assert invoice_new.partner_id.supplier, 'Imported Partner is not marked as supplier'
|
||||
assert invoice_new.reference == time.strftime("SAJ/%Y/002"), "internal number is not stored in reference"
|
||||
assert invoice_new.reference == time.strftime("SAJ/%Y/070"), "internal number is not stored in reference"
|
||||
assert invoice_new.reference_type == 'none', "reference type is not set to 'none'"
|
||||
assert invoice_new.internal_number == False, "internal number is not reset"
|
||||
assert invoice_new.journal_id.id, "journal id is not selected"
|
||||
|
@ -152,3 +154,111 @@
|
|||
for inv_tax in invoice_new.tax_line:
|
||||
assert inv_tax.manual, "tax line not set to manual"
|
||||
assert inv_tax.account_id, "missing tax line account"
|
||||
-
|
||||
Then I import a sample EDI document of another customer invoice from OpenERP 6.1 (to test backwards compatibility)
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
import time
|
||||
edi_document = {
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms",
|
||||
"__module": "account",
|
||||
"__model": "account.invoice",
|
||||
"__version": [6,1,0],
|
||||
"internal_number": time.strftime("SAJ/%Y/061"),
|
||||
"company_address": {
|
||||
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.main_address",
|
||||
"__module": "base",
|
||||
"__model": "res.partner.address",
|
||||
"city": "Gerompont",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.be", "Belgium"],
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"bank_ids": [
|
||||
["base:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_bank-ZrTWzesfsdDJzGbp","Sample bank: 123465789-156113"]
|
||||
],
|
||||
},
|
||||
"company_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_company_test11", "Thomson pvt. ltd."],
|
||||
"currency": {
|
||||
"__id": "base:b22acf7a-ddcd-11e0-a4db-701a04e25543.EUR",
|
||||
"__module": "base",
|
||||
"__model": "res.currency",
|
||||
"code": "EUR",
|
||||
"symbol": "€",
|
||||
},
|
||||
"partner_id": ["account:b22acf7a-ddcd-11e0-a4db-701a04e25543.res_partner_test20", "Junjun wala"],
|
||||
"partner_address": {
|
||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||
"__module": "base",
|
||||
"__model": "res.partner.address",
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"city": "Gerompont",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||
},
|
||||
"date_invoice": time.strftime('%Y-%m-%d'),
|
||||
"name": "sample invoice",
|
||||
"tax_line": [{
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_tax-4g4EutbiEMVl",
|
||||
"__module": "account",
|
||||
"__model": "account.invoice.tax",
|
||||
"amount": 1000.0,
|
||||
"manual": True,
|
||||
"name": "sale tax",
|
||||
}],
|
||||
"type": "out_invoice",
|
||||
"invoice_line": [{
|
||||
"__module": "account",
|
||||
"__model": "account.invoice.line",
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so",
|
||||
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "PCE"],
|
||||
"name": "Basic PC",
|
||||
"price_unit": 10.0,
|
||||
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
|
||||
"quantity": 1.0
|
||||
},
|
||||
{
|
||||
"__module": "account",
|
||||
"__model": "account.invoice.line",
|
||||
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5",
|
||||
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "PCE"],
|
||||
"name": "Medium PC",
|
||||
"price_unit": 100.0,
|
||||
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
|
||||
"quantity": 5.0
|
||||
}]
|
||||
}
|
||||
invoice_id = self.edi_import(cr, uid, edi_document, context=context)
|
||||
assert invoice_id, 'EDI import failed'
|
||||
invoice_new = self.browse(cr, uid, invoice_id)
|
||||
|
||||
# check bank info on partner
|
||||
assert 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):
|
||||
""" retrieve the user info (name, login or email) corresponding to a signup token """
|
||||
registry = RegistryManager.get(dbname)
|
||||
user_info = None
|
||||
with registry.cursor() as cr:
|
||||
res_partner = registry.get('res.partner')
|
||||
user_info = res_partner.signup_retrieve_info(cr, openerp.SUPERUSER_ID, token)
|
||||
|
|
|
@ -23,9 +23,7 @@ import time
|
|||
import urllib
|
||||
import urlparse
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
|
@ -35,7 +33,7 @@ class SignupError(Exception):
|
|||
def random_token():
|
||||
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
|
||||
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(random.choice(chars) for i in xrange(20))
|
||||
return ''.join(random.choice(chars) for _ in xrange(20))
|
||||
|
||||
def now():
|
||||
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)
|
||||
return res
|
||||
|
||||
def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
|
||||
""" determine a signup url for a given partner """
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
|
||||
# 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)
|
||||
|
||||
def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, context=None):
|
||||
""" generate a signup url for the given partner ids and action, possibly overriding
|
||||
the url state components (menu_id, id, view_type) """
|
||||
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):
|
||||
# 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:
|
||||
params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token))
|
||||
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params)
|
||||
action_template = "?db=%(db)s#action=%(action)s&token=%(token)s"
|
||||
params['token'] = urllib.quote(partner.signup_token)
|
||||
elif partner.user_ids:
|
||||
user = partner.user_ids[0]
|
||||
params = (urllib.quote(cr.dbname), urllib.quote(user.login))
|
||||
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params)
|
||||
action_template = "?db=%(db)s#action=%(action)s&db=%(db)s&login=%(login)s"
|
||||
params['login'] = urllib.quote(partner.user_ids[0].login)
|
||||
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
|
||||
|
||||
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 = {
|
||||
'signup_token': fields.char(size=24, string='Signup Token'),
|
||||
'signup_expiration': fields.datetime(string='Signup Expiration'),
|
||||
'signup_token': fields.char('Signup Token'),
|
||||
'signup_expiration': fields.datetime('Signup Expiration'),
|
||||
'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'),
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ class crm_lead_forward_to_partner(osv.osv_memory):
|
|||
|
||||
_defaults = {
|
||||
'history_mode': 'latest',
|
||||
'content_subtype': lambda self,cr, uid, context={}: 'html',
|
||||
}
|
||||
|
||||
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/>.
|
||||
#
|
||||
##############################################################################
|
||||
import logging
|
||||
|
||||
import models
|
||||
import edi_service
|
||||
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""")
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import edi_service
|
||||
from .models.edi import EDIMixin, edi
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -36,12 +36,10 @@ documentation at http://doc.openerp.com.
|
|||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base', 'email_template'],
|
||||
'icon': '/edi/static/src/img/knowledge.png',
|
||||
'data': ['security/ir.model.access.csv'],
|
||||
'test': ['test/edi_partner_test.yml'],
|
||||
'js': ['static/src/js/edi.js'],
|
||||
'css': ['static/src/css/edi.css'],
|
||||
'qweb': ['static/src/xml/*.xml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,77 +1,25 @@
|
|||
import json
|
||||
import textwrap
|
||||
|
||||
import simplejson
|
||||
import werkzeug.wrappers
|
||||
|
||||
import openerp.addons.web.http as openerpweb
|
||||
import openerp.addons.web.controllers.main as webmain
|
||||
|
||||
class EDI(openerpweb.Controller):
|
||||
# http://hostname:8069/edi/view?db=XXXX&token=XXXXXXXXXXX
|
||||
# http://hostname:8069/edi/import_url?url=URIEncodedURL
|
||||
_cp_path = "/edi"
|
||||
|
||||
def template(self, req, mods='web,edi'):
|
||||
d = {}
|
||||
d["js"] = "\n".join('<script type="text/javascript" src="%s"></script>'%i for i in webmain.manifest_list(req, mods, 'js'))
|
||||
d["css"] = "\n".join('<link rel="stylesheet" href="%s">'%i for i in webmain.manifest_list(req, mods, 'css'))
|
||||
d["modules"] = simplejson.dumps(mods.split(','))
|
||||
return d
|
||||
|
||||
@openerpweb.httprequest
|
||||
def view(self, req, db, token):
|
||||
d = self.template(req)
|
||||
d["init"] = 's.edi.edi_view("%s","%s");'%(db,token)
|
||||
r = webmain.html_template % d
|
||||
return r
|
||||
|
||||
@openerpweb.httprequest
|
||||
def import_url(self, req, url):
|
||||
d = self.template(req)
|
||||
d["init"] = 's.edi.edi_import("%s");'%(url)
|
||||
r = webmain.html_template % d
|
||||
return r
|
||||
|
||||
@openerpweb.httprequest
|
||||
def download(self, req, db, token):
|
||||
result = req.session.proxy('edi').get_edi_document(db, token)
|
||||
response = werkzeug.wrappers.Response( result, headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))])
|
||||
return response
|
||||
|
||||
@openerpweb.httprequest
|
||||
def download_attachment(self, req, db, token):
|
||||
result = req.session.proxy('edi').get_edi_document(db, token)
|
||||
doc = json.loads(result)[0]
|
||||
attachment = doc['__attachments'] and doc['__attachments'][0]
|
||||
if attachment:
|
||||
result = attachment["content"].decode('base64')
|
||||
import email.Utils as utils
|
||||
|
||||
# Encode as per RFC 2231
|
||||
filename_utf8 = attachment['file_name']
|
||||
filename_encoded = "%s=%s" % ('filename*',
|
||||
utils.encode_rfc2231(filename_utf8, 'utf-8'))
|
||||
response = werkzeug.wrappers.Response(result, headers=[('Content-Type', 'application/pdf'),
|
||||
('Content-Disposition', 'inline; ' + filename_encoded),
|
||||
('Content-Length', len(result))])
|
||||
return response
|
||||
|
||||
@openerpweb.httprequest
|
||||
def binary(self, req, db, token, field_path="company_address.logo", content_type='image/png'):
|
||||
result = req.session.proxy('edi').get_edi_document(db, token)
|
||||
doc = json.loads(result)[0]
|
||||
for name in field_path.split("."):
|
||||
doc = doc[name]
|
||||
result = doc.decode('base64')
|
||||
response = werkzeug.wrappers.Response(result, headers=[('Content-Type', content_type),
|
||||
('Content-Length', len(result))])
|
||||
return response
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get_edi_document(self, req, db, token):
|
||||
result = req.session.proxy('edi').get_edi_document(db, token)
|
||||
return json.loads(result)
|
||||
modules = webmain.module_boot(req) + ['edi']
|
||||
modules_str = ','.join(modules)
|
||||
modules_json = simplejson.dumps(modules)
|
||||
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'))
|
||||
return webmain.html_template % {
|
||||
'js': js,
|
||||
'css': css,
|
||||
'modules': modules_json,
|
||||
'init': 's.edi.edi_import("%s");' % url,
|
||||
}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def import_edi_url(self, req, url):
|
||||
|
@ -81,6 +29,4 @@ class EDI(openerpweb.Controller):
|
|||
return {"action": webmain.clean_action(req, result[0][2], context)}
|
||||
return True
|
||||
|
||||
#
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
##############################################################################
|
||||
import logging
|
||||
|
||||
import netsvc
|
||||
import openerp
|
||||
import openerp.netsvc as netsvc
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -34,10 +34,10 @@ class edi(netsvc.ExportService):
|
|||
try:
|
||||
registry = openerp.modules.registry.RegistryManager.get(db_name)
|
||||
assert registry, 'Unknown database %s' % db_name
|
||||
edi_document = registry['edi.document']
|
||||
edi = registry['edi.edi']
|
||||
cr = registry.db.cursor()
|
||||
res = None
|
||||
res = getattr(edi_document, method_name)(cr, *method_args)
|
||||
res = getattr(edi, method_name)(cr, *method_args)
|
||||
cr.commit()
|
||||
except Exception:
|
||||
_logger.exception('Failed to execute EDI method %s with args %r.', method_name, method_args)
|
||||
|
@ -46,9 +46,6 @@ class edi(netsvc.ExportService):
|
|||
cr.close()
|
||||
return res
|
||||
|
||||
def exp_get_edi_document(self, db_name, edi_token):
|
||||
return self._edi_dispatch(db_name, 'get_document', 1, edi_token)
|
||||
|
||||
def exp_import_edi_document(self, db_name, uid, passwd, edi_document, context=None):
|
||||
return self._edi_dispatch(db_name, 'import_edi', uid, edi_document, None)
|
||||
|
||||
|
@ -59,9 +56,6 @@ class edi(netsvc.ExportService):
|
|||
if method in ['import_edi_document', 'import_edi_url']:
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
openerp.service.security.check(db, uid, passwd)
|
||||
elif method in ['get_edi_document']:
|
||||
# No security check for these methods
|
||||
pass
|
||||
else:
|
||||
raise KeyError("Method not found: %s." % method)
|
||||
fn = getattr(self, 'exp_'+method)
|
||||
|
|
|
@ -24,15 +24,13 @@ import hashlib
|
|||
import json
|
||||
import logging
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
import urllib2
|
||||
|
||||
import openerp
|
||||
import openerp.release as release
|
||||
import netsvc
|
||||
import pooler
|
||||
from osv import osv,fields,orm
|
||||
import openerp.netsvc as netsvc
|
||||
from openerp.osv import osv, fields
|
||||
from tools.translate import _
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -74,16 +72,9 @@ def last_update_for(record):
|
|||
return False
|
||||
|
||||
|
||||
class edi_document(osv.osv):
|
||||
_name = 'edi.document'
|
||||
_description = 'EDI Document'
|
||||
_columns = {
|
||||
'name': fields.char("EDI token", size = 128, help="Unique identifier for retrieving an EDI document."),
|
||||
'document': fields.text("Document", help="EDI document content")
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'EDI Tokens must be unique!')
|
||||
]
|
||||
class edi(osv.AbstractModel):
|
||||
_name = 'edi.edi'
|
||||
_description = 'EDI Subsystem'
|
||||
|
||||
def new_edi_token(self, cr, uid, record):
|
||||
"""Return a new, random unique token to identify this model record,
|
||||
|
@ -109,7 +100,7 @@ class edi_document(osv.osv):
|
|||
"""Generates a final EDI document containing the EDI serialization
|
||||
of the given records, which should all be instances of a Model
|
||||
that has the :meth:`~.edi` mixin. The document is not saved in the
|
||||
database, this is done by :meth:`~.export_edi`.
|
||||
database.
|
||||
|
||||
:param list(browse_record) records: records to export as EDI
|
||||
:return: UTF-8 encoded string containing the serialized records
|
||||
|
@ -120,19 +111,6 @@ class edi_document(osv.osv):
|
|||
edi_list += record_model_obj.edi_export(cr, uid, [record], context=context)
|
||||
return self.serialize(edi_list)
|
||||
|
||||
def get_document(self, cr, uid, edi_token, context=None):
|
||||
"""Retrieve the EDI document corresponding to the given edi_token.
|
||||
|
||||
:return: EDI document string
|
||||
:raise: ValueError if requested EDI token does not match any know document
|
||||
"""
|
||||
_logger.debug("get_document(%s)", edi_token)
|
||||
edi_ids = self.search(cr, uid, [('name','=', edi_token)], context=context)
|
||||
if not edi_ids:
|
||||
raise ValueError('Invalid EDI token: %s.' % edi_token)
|
||||
edi = self.browse(cr, uid, edi_ids[0], context=context)
|
||||
return edi.document
|
||||
|
||||
def load_edi(self, cr, uid, edi_documents, context=None):
|
||||
"""Import the given EDI document structures into the system, using
|
||||
:meth:`~.import_edi`.
|
||||
|
@ -171,38 +149,18 @@ class edi_document(osv.osv):
|
|||
"""
|
||||
return json.loads(edi_documents_string)
|
||||
|
||||
def export_edi(self, cr, uid, records, context=None):
|
||||
"""Export the given database records as EDI documents, stores them
|
||||
permanently with a new unique EDI token, for later retrieval via :meth:`~.get_document`,
|
||||
and returns the list of the new corresponding ``ir.edi.document`` records.
|
||||
|
||||
:param records: list of browse_record of any model
|
||||
:return: list of IDs of the new ``ir.edi.document`` entries, in the same
|
||||
order as the provided ``records``.
|
||||
"""
|
||||
exported_ids = []
|
||||
for record in records:
|
||||
document = self.generate_edi(cr, uid, [record], context)
|
||||
token = self.new_edi_token(cr, uid, record)
|
||||
self.create(cr, uid, {
|
||||
'name': token,
|
||||
'document': document
|
||||
}, context=context)
|
||||
exported_ids.append(token)
|
||||
return exported_ids
|
||||
|
||||
def import_edi(self, cr, uid, edi_document=None, edi_url=None, context=None):
|
||||
"""Import a JSON serialized EDI Document string into the system, first retrieving it
|
||||
from the given ``edi_url`` if provided.
|
||||
|
||||
:param str|unicode edi_document: UTF-8 string or unicode containing JSON-serialized
|
||||
:param str|unicode edi: UTF-8 string or unicode containing JSON-serialized
|
||||
EDI Document to import. Must not be provided if
|
||||
``edi_url`` is given.
|
||||
:param str|unicode edi_url: URL where the EDI document (same format as ``edi_document``)
|
||||
:param str|unicode edi_url: URL where the EDI document (same format as ``edi``)
|
||||
may be retrieved, without authentication.
|
||||
"""
|
||||
if edi_url:
|
||||
assert not edi_document, 'edi_document must not be provided if edi_url is given.'
|
||||
assert not edi_document, 'edi must not be provided if edi_url is given.'
|
||||
edi_document = urllib2.urlopen(edi_url).read()
|
||||
assert edi_document, 'EDI Document is empty!'
|
||||
edi_documents = self.deserialize(edi_document)
|
||||
|
@ -215,10 +173,10 @@ class EDIMixin(object):
|
|||
``edi_import()`` and ``edi_export()`` methods to implement their
|
||||
specific behavior, based on the primitives provided by this mixin."""
|
||||
|
||||
def _edi_requires_attributes(self, attributes, edi_document):
|
||||
model_name = edi_document.get('__imported_model') or edi_document.get('__model') or self._name
|
||||
def _edi_requires_attributes(self, attributes, edi):
|
||||
model_name = edi.get('__imported_model') or edi.get('__model') or self._name
|
||||
for attribute in attributes:
|
||||
assert edi_document.get(attribute),\
|
||||
assert edi.get(attribute),\
|
||||
'Attribute `%s` is required in %s EDI documents.' % (attribute, model_name)
|
||||
|
||||
# private method, not RPC-exposed as it creates ir.model.data entries as
|
||||
|
@ -318,7 +276,6 @@ class EDIMixin(object):
|
|||
:return: list of dicts containing boilerplate EDI metadata for each record,
|
||||
at the corresponding index from ``records``.
|
||||
"""
|
||||
data_ids = []
|
||||
ir_attachment = self.pool.get('ir.attachment')
|
||||
results = []
|
||||
for record in records:
|
||||
|
@ -398,7 +355,7 @@ class EDIMixin(object):
|
|||
return [self.edi_m2o(cr, uid, r, context=context) for r in records]
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Returns a list of dicts representing an edi.document containing the
|
||||
"""Returns a list of dicts representing EDI documents containing the
|
||||
records, and matching the given ``edi_struct``, if provided.
|
||||
|
||||
:param edi_struct: if provided, edi_struct should be a dictionary
|
||||
|
@ -443,50 +400,6 @@ class EDIMixin(object):
|
|||
results.append(edi_dict)
|
||||
return results
|
||||
|
||||
def edi_export_and_email(self, cr, uid, ids, template_ext_id, context=None):
|
||||
"""Export the given records just like :meth:`~.export_edi`, the render the
|
||||
given email template, in order to trigger appropriate notifications.
|
||||
This method is intended to be called as part of business documents'
|
||||
lifecycle, so it silently ignores any error occurring during the process,
|
||||
as this is usually non-critical. To avoid any delay, it is also asynchronous
|
||||
and will spawn a short-lived thread to perform the action.
|
||||
|
||||
:param str template_ext_id: external id of the email.template to use for
|
||||
the mail notifications
|
||||
:return: True
|
||||
"""
|
||||
def email_task():
|
||||
db = pooler.get_db(cr.dbname)
|
||||
local_cr = None
|
||||
try:
|
||||
time.sleep(3) # lame workaround to wait for commit of parent transaction
|
||||
# grab a fresh browse_record on local cursor
|
||||
local_cr = db.cursor()
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(local_cr, uid, 'web.base.url')
|
||||
if not web_root_url:
|
||||
_logger.warning('Ignoring EDI mail notification, web.base.url is not defined in parameters.')
|
||||
return
|
||||
mail_tmpl = self._edi_get_object_by_external_id(local_cr, uid, template_ext_id, 'email.template', context=context)
|
||||
if not mail_tmpl:
|
||||
# skip EDI export if the template was not found
|
||||
_logger.warning('Ignoring EDI mail notification, template %s cannot be located.', template_ext_id)
|
||||
return
|
||||
for edi_record in self.browse(local_cr, uid, ids, context=context):
|
||||
edi_token = self.pool.get('edi.document').export_edi(local_cr, uid, [edi_record], context = context)[0]
|
||||
edi_context = dict(context, edi_web_url_view=EDI_VIEW_WEB_URL % (web_root_url, local_cr.dbname, edi_token))
|
||||
self.pool.get('email.template').send_mail(local_cr, uid, mail_tmpl.id, edi_record.id,
|
||||
force_send=False, context=edi_context)
|
||||
_logger.info('EDI export successful for %s #%s, email notification sent.', self._name, edi_record.id)
|
||||
except Exception:
|
||||
_logger.warning('Ignoring EDI mail notification, failed to generate it.', exc_info=True)
|
||||
finally:
|
||||
if local_cr:
|
||||
local_cr.commit()
|
||||
local_cr.close()
|
||||
|
||||
threading.Thread(target=email_task, name='EDI ExportAndEmail for %s %r' % (self._name, ids)).start()
|
||||
return True
|
||||
|
||||
def _edi_get_object_by_name(self, cr, uid, name, model_name, context=None):
|
||||
model = self.pool.get(model_name)
|
||||
search_results = model.name_search(cr, uid, name, operator='=', context=context)
|
||||
|
@ -515,18 +428,20 @@ class EDIMixin(object):
|
|||
file_name = record.name_get()[0][1]
|
||||
file_name = re.sub(r'[^a-zA-Z0-9_-]', '_', file_name)
|
||||
file_name += ".pdf"
|
||||
ir_attachment = self.pool.get('ir.attachment').create(cr, uid,
|
||||
{'name': file_name,
|
||||
'datas': result,
|
||||
'datas_fname': file_name,
|
||||
'res_model': self._name,
|
||||
'res_id': record.id,
|
||||
'type': 'binary'},
|
||||
context=context)
|
||||
self.pool.get('ir.attachment').create(cr, uid,
|
||||
{
|
||||
'name': file_name,
|
||||
'datas': result,
|
||||
'datas_fname': file_name,
|
||||
'res_model': self._name,
|
||||
'res_id': record.id,
|
||||
'type': 'binary'
|
||||
},
|
||||
context=context)
|
||||
|
||||
def _edi_import_attachments(self, cr, uid, record_id, edi_document, context=None):
|
||||
def _edi_import_attachments(self, cr, uid, record_id, edi, context=None):
|
||||
ir_attachment = self.pool.get('ir.attachment')
|
||||
for attachment in edi_document.get('__attachments', []):
|
||||
for attachment in edi.get('__attachments', []):
|
||||
# check attachment data is non-empty and valid
|
||||
file_data = None
|
||||
try:
|
||||
|
@ -573,8 +488,10 @@ class EDIMixin(object):
|
|||
if data_ids:
|
||||
model = self.pool.get(model)
|
||||
data = ir_model_data.browse(cr, uid, data_ids[0], context=context)
|
||||
result = model.browse(cr, uid, data.res_id, context=context)
|
||||
return result
|
||||
if model.exists(cr, uid, [data.res_id]):
|
||||
return model.browse(cr, uid, data.res_id, context=context)
|
||||
# stale external-id, cleanup to allow re-import, as the corresponding record is gone
|
||||
ir_model_data.unlink(cr, 1, [data_ids[0]])
|
||||
|
||||
def edi_import_relation(self, cr, uid, model, value, external_id, context=None):
|
||||
"""Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the
|
||||
|
@ -588,6 +505,10 @@ class EDIMixin(object):
|
|||
* If previous steps gave no result, create a new record with the given
|
||||
value in the target model, assign it the given external_id, and return
|
||||
the new database ID
|
||||
|
||||
:param str value: display name of the record to import
|
||||
:param str external_id: fully-qualified external ID of the record
|
||||
:return: database id of newly-imported or pre-existing record
|
||||
"""
|
||||
_logger.debug("%s: Importing EDI relationship [%r,%r]", model, external_id, value)
|
||||
target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context)
|
||||
|
@ -602,9 +523,11 @@ class EDIMixin(object):
|
|||
self._name, external_id, value)
|
||||
# also need_new_ext_id here, but already been set above
|
||||
model = self.pool.get(model)
|
||||
# should use name_create() but e.g. res.partner won't allow it at the moment
|
||||
res_id = model.create(cr, uid, {model._rec_name: value}, context=context)
|
||||
res_id, _ = model.name_create(cr, uid, value, context=context)
|
||||
target = model.browse(cr, uid, res_id, context=context)
|
||||
else:
|
||||
_logger.debug("%s: Importing EDI relationship [%r,%r] - record already exists with ID %s, using it",
|
||||
self._name, external_id, value, target.id)
|
||||
if need_new_ext_id:
|
||||
ext_id_members = split_external_id(external_id)
|
||||
# module name is never used bare when creating ir.model.data entries, in order
|
||||
|
@ -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)
|
||||
return target.id
|
||||
|
||||
def edi_import(self, cr, uid, edi_document, context=None):
|
||||
"""Imports a dict representing an edi.document into the system.
|
||||
def edi_import(self, cr, uid, edi, context=None):
|
||||
"""Imports a dict representing an EDI document into the system.
|
||||
|
||||
:param dict edi_document: EDI document to import
|
||||
:param dict edi: EDI document to import
|
||||
:return: the database ID of the imported record
|
||||
"""
|
||||
assert self._name == edi_document.get('__import_model') or \
|
||||
('__import_model' not in edi_document and self._name == edi_document.get('__model')), \
|
||||
assert self._name == edi.get('__import_model') or \
|
||||
('__import_model' not in edi and self._name == edi.get('__model')), \
|
||||
"EDI Document Model and current model do not match: '%s' (EDI) vs '%s' (current)." % \
|
||||
(edi_document['__model'], self._name)
|
||||
(edi.get('__model'), self._name)
|
||||
|
||||
# First check the record is now already known in the database, in which case it is ignored
|
||||
ext_id_members = split_external_id(edi_document['__id'])
|
||||
ext_id_members = split_external_id(edi['__id'])
|
||||
existing = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context)
|
||||
if existing:
|
||||
_logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full'])
|
||||
|
@ -634,7 +557,7 @@ class EDIMixin(object):
|
|||
|
||||
record_values = {}
|
||||
o2m_todo = {} # o2m values are processed after their parent already exists
|
||||
for field_name, field_value in edi_document.iteritems():
|
||||
for field_name, field_value in edi.iteritems():
|
||||
# skip metadata and empty fields
|
||||
if field_name.startswith('__') or field_value is None or field_value is False:
|
||||
continue
|
||||
|
@ -679,7 +602,7 @@ class EDIMixin(object):
|
|||
dest_model.edi_import(cr, uid, o2m_line, context=context)
|
||||
|
||||
# process the attachments, if any
|
||||
self._edi_import_attachments(cr, uid, record_id, edi_document, context=context)
|
||||
self._edi_import_attachments(cr, uid, record_id, edi, context=context)
|
||||
|
||||
return record_id
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
|
||||
class res_company(osv.osv):
|
||||
"""Helper subclass for res.company providing util methods for working with
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -20,10 +20,9 @@
|
|||
##############################################################################
|
||||
import logging
|
||||
|
||||
from osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from openerp import SUPERUSER_ID
|
||||
from tools.translate import _
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
RES_PARTNER_EDI_STRUCT = {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_ir_edi_all_read,access_ir_edi_all_read,model_edi_document,,1,0,0,0
|
||||
access_ir_edi_employee_create,access_ir_edi_employee_create,model_edi_document,base.group_user,1,0,1,0
|
|
|
@ -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 = {}
|
||||
openerp.edi = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
instance.edi = {}
|
||||
|
||||
openerp.edi.EdiView = openerp.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();
|
||||
}
|
||||
});
|
||||
instance.edi.EdiImport = instance.web.Widget.extend({
|
||||
|
||||
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) {
|
||||
this._super();
|
||||
this.url = url;
|
||||
|
@ -137,7 +27,7 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
|
|||
|
||||
show_login: function() {
|
||||
this.destroy_content();
|
||||
this.login = new openerp.web.Login(this);
|
||||
this.login = new instance.web.Login(this);
|
||||
this.login.appendTo(this.$el);
|
||||
},
|
||||
|
||||
|
@ -167,18 +57,18 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
|
|||
window.location = "/";
|
||||
}
|
||||
}
|
||||
}).html('The document has been successfully imported!');
|
||||
}).html(_t('The document has been successfully imported!'));
|
||||
}
|
||||
},
|
||||
on_imported_error: function(response){
|
||||
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) {
|
||||
msg += "\n Reason:" + response.data.fault_code;
|
||||
msg += "\n " + _t("Reason:") + response.data.fault_code;
|
||||
}
|
||||
var params = {error: response, message: msg};
|
||||
$(openerp.web.qweb.render("CrashManagerWarning", params)).dialog({
|
||||
title: "Document Import Notification",
|
||||
$(instance.web.qweb.render("CrashManager.warning", params)).dialog({
|
||||
title: _t("Document Import Notification"),
|
||||
modal: true,
|
||||
buttons: {
|
||||
Ok: function() { $(this).dialog("close"); }
|
||||
|
@ -187,9 +77,9 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.edi.edi_import = function (url) {
|
||||
openerp.session.session_bind().done(function () {
|
||||
new openerp.edi.EdiImport(null,url).appendTo($("body").addClass('openerp'));
|
||||
instance.edi.edi_import = function (url) {
|
||||
instance.session.session_bind().done(function () {
|
||||
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
|
||||
and reimport it.
|
||||
-
|
||||
!python {model: edi.document}: |
|
||||
!python {model: edi.edi}: |
|
||||
import json
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_2'))])
|
||||
doc = self.get_document(cr, uid, tokens[0], context=context)
|
||||
res_partner = self.pool.get('res.partner')
|
||||
doc = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, ref('base.res_partner_2'))])
|
||||
edi_doc, = json.loads(doc)
|
||||
|
||||
# check content of the document
|
||||
|
@ -36,8 +35,7 @@
|
|||
"Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_2'), result)
|
||||
|
||||
# export the same partner we just created, and see if the output matches the input
|
||||
tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])])
|
||||
doc_output = self.get_document(cr, uid, tokens[0], context=context)
|
||||
doc_output = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, result[1])])
|
||||
edi_doc_output, = json.loads(doc_output)
|
||||
for attribute in ('__model', '__module', '__id', 'name', '__attachments'):
|
||||
assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \
|
||||
|
|
|
@ -100,25 +100,27 @@ class test_message_compose(test_mail_mockup.TestMailMockups):
|
|||
# ----------------------------------------
|
||||
|
||||
# 1. Comment on pigs
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': 'Forget me subject', 'body': 'Dummy body'},
|
||||
{'default_composition_mode': 'comment',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_use_template': False,
|
||||
'default_template_id': email_template_id,
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
|
||||
# 2. Perform 'toggle_template', to set use_template and use template_id
|
||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
||||
context = {
|
||||
'default_composition_mode': 'comment',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_use_template': False,
|
||||
'default_template_id': email_template_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, context)
|
||||
onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
|
||||
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()
|
||||
|
||||
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'])])
|
||||
# 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.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')
|
||||
# Test: mail.compose.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,
|
||||
'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
|
||||
# ----------------------------------------
|
||||
|
||||
# 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,
|
||||
{'subject': 'Forget me subject', 'body': 'Dummy body'},
|
||||
{'default_composition_mode': 'mass_mail',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_use_template': False,
|
||||
'default_template_id': email_template_id,
|
||||
'default_partner_ids': [p_a_id],
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
|
||||
# 2. Perform 'toggle_template', to set use_template and use template_id
|
||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
||||
context = {
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_template_id': email_template_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, context)
|
||||
onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
|
||||
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()
|
||||
|
||||
message_pids = [partner.id for partner in compose.partner_ids]
|
||||
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.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')
|
||||
|
||||
# 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]})
|
||||
group_pigs.refresh()
|
||||
group_bird.refresh()
|
||||
|
|
|
@ -19,11 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import tools
|
||||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
def _reopen(self, res_id, model):
|
||||
return {'type': 'ir.actions.act_window',
|
||||
|
@ -39,7 +36,6 @@ def _reopen(self, res_id, model):
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
class mail_compose_message(osv.TransientModel):
|
||||
_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)
|
||||
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 = {
|
||||
'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
|
||||
'template_id': fields.selection(_get_templates, 'Template', size=-1),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'use_template': True,
|
||||
}
|
||||
|
||||
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':
|
||||
def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None):
|
||||
""" - mass_mailing: we cannot render, so return the template values
|
||||
- normal mode: return rendered values """
|
||||
if template_id and composition_mode == 'mass_mail':
|
||||
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
|
||||
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)
|
||||
# transform attachments into attachment_ids
|
||||
values['attachment_ids'] = []
|
||||
|
@ -102,39 +80,16 @@ class mail_compose_message(osv.TransientModel):
|
|||
'datas_fname': attach_fname,
|
||||
'res_model': model,
|
||||
'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))
|
||||
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'):
|
||||
values['body'] = values.pop('body_html')
|
||||
values.update(use_template=use_template, template_id=template_id)
|
||||
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):
|
||||
""" hit save as template button: current form value will be a new
|
||||
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])]
|
||||
}
|
||||
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)
|
||||
|
||||
#------------------------------------------------------
|
||||
|
@ -167,7 +122,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
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)
|
||||
# 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['body'] = values.pop('body_html', '')
|
||||
# transform email_to, email_cc into partner_ids
|
||||
|
|
|
@ -7,25 +7,17 @@
|
|||
<field name="model">mail.compose.message</field>
|
||||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='content_subtype']" position="after">
|
||||
<field name="use_template" invisible="1"
|
||||
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
|
||||
</xpath>
|
||||
<xpath expr="//footer" position="inside">
|
||||
<group class="oe_right" col="1">
|
||||
<div attrs="{'invisible':[('use_template','=',False)]}">Use template
|
||||
<field name="template_id" attrs="{'invisible':[('use_template','=',False)]}"
|
||||
nolabel="1"
|
||||
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
|
||||
</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>
|
||||
<xpath expr="//footer" position="inside">
|
||||
<group class="oe_right" col="1">
|
||||
<div>Use template
|
||||
<field name="template_id" nolabel="1"
|
||||
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)"/>
|
||||
</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"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -437,9 +437,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
|||
|
||||
# Mail data
|
||||
_subject = 'Pigs'
|
||||
_body_text = 'Pigs rules'
|
||||
_msg_reply = 'Re: Pigs'
|
||||
_msg_body = '<pre>Pigs rules</pre>'
|
||||
_body = 'Pigs <b>rule</b>'
|
||||
_reply_subject = 'Re: Pigs'
|
||||
_attachments = [
|
||||
{'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
|
||||
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
|
||||
|
@ -462,9 +461,9 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
|||
|
||||
# 1. Comment group_pigs with body_text and subject
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
||||
{'subject': _subject, 'body': _body, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id,
|
||||
'default_content_subtype': 'plaintext'})
|
||||
'default_content_subtype': 'plaintext'})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
# Test: mail.compose.message: composition_mode, model, res_id
|
||||
self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
|
||||
|
@ -476,8 +475,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
|||
group_pigs.refresh()
|
||||
message = group_pigs.message_ids[0]
|
||||
# Test: mail.message: subject, body inside pre
|
||||
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
|
||||
self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
|
||||
self.assertEqual(message.subject, _subject, 'mail.message incorrect subject')
|
||||
self.assertEqual(message.body, _body, 'mail.message incorrect body')
|
||||
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
|
||||
msg_pids = [partner.id for partner in message.notified_partner_ids]
|
||||
test_pids = [p_b_id, p_c_id, p_d_id]
|
||||
|
@ -495,13 +494,12 @@ class test_mail(test_mail_mockup.TestMailMockups):
|
|||
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
|
||||
{'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
# Test: model, res_id, parent_id, content_subtype
|
||||
# Test: model, res_id, parent_id
|
||||
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
|
||||
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
|
||||
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
|
||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
|
||||
# Test: mail.message: subject as Re:.., body in html, parent_id
|
||||
self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
|
||||
self.assertEqual(compose.subject, _reply_subject, 'mail.message incorrect subject')
|
||||
# self.assertIn('Administrator wrote:<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')
|
||||
# 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,
|
||||
'active_ids': [self.group_pigs_id, group_bird_id]})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
# Test: content_subtype is html
|
||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
|
||||
|
||||
# 2. Post the comment, get created message for each group
|
||||
mail_compose.send_mail(cr, uid, [compose_id],
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from openerp.addons.mail.tests import test_mail_mockup
|
||||
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):
|
||||
|
@ -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.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):
|
||||
""" Test mail_message search override about access rights. """
|
||||
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')])
|
||||
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):
|
||||
""" Test basic mail_message read access rights. """
|
||||
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,
|
||||
cr, user_bert_id, message_id)
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model')
|
||||
@mute_logger('openerp.osv.orm')
|
||||
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
|
||||
def test_10_mail_flow_access_rights(self):
|
||||
""" Test a Chatter-looks alike flow. """
|
||||
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
|
||||
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)]},
|
||||
{'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])
|
||||
|
||||
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,
|
||||
{'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)]},
|
||||
{'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])
|
||||
|
|
|
@ -81,7 +81,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
elif composition_mode == 'comment' and model and res_id:
|
||||
vals = self.get_record_data(cr, uid, model, res_id, context=context)
|
||||
elif composition_mode == 'mass_mail' and model and active_ids:
|
||||
vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'}
|
||||
vals = {'model': model, 'res_id': res_id}
|
||||
else:
|
||||
vals = {'model': model, 'res_id': res_id}
|
||||
if composition_mode:
|
||||
|
@ -106,16 +106,10 @@ class mail_compose_message(osv.TransientModel):
|
|||
'mail_compose_message_ir_attachments_rel',
|
||||
'wizard_id', 'attachment_id', 'Attachments'),
|
||||
'filter_id': fields.many2one('ir.filters', 'Filters'),
|
||||
'body_text': fields.text('Plain-text Contents'),
|
||||
'content_subtype': fields.char('Message content subtype', size=32, readonly=1,
|
||||
help="Type of message, usually 'html' or 'plain', used to select "\
|
||||
"plain-text or rich-text contents accordingly"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'composition_mode': 'comment',
|
||||
'content_subtype': lambda self, cr, uid, ctx={}: 'html',
|
||||
'body_text': lambda self, cr, uid, ctx={}: False,
|
||||
'body': lambda self, cr, uid, ctx={}: '',
|
||||
'subject': lambda self, cr, uid, ctx={}: False,
|
||||
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
||||
|
@ -172,32 +166,9 @@ class mail_compose_message(osv.TransientModel):
|
|||
'parent_id': message_data.id,
|
||||
'subject': reply_subject,
|
||||
'partner_ids': partner_ids,
|
||||
'content_subtype': 'html',
|
||||
}
|
||||
return result
|
||||
|
||||
def toggle_content_subtype(self, cr, uid, ids, context=None):
|
||||
""" toggle content_subtype: calls onchange_formatting to emulate an
|
||||
on_change, then writes the value to update the form. """
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html'
|
||||
onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context)
|
||||
self.write(cr, uid, [record.id], onchange_res['value'], context=context)
|
||||
return True
|
||||
|
||||
def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None):
|
||||
""" This onchange allows to have some specific behavior when switching
|
||||
between text or html mode. This method can be overridden.
|
||||
:param values: 'plain' or 'html'
|
||||
"""
|
||||
return {'value': {'content_subtype': value}}
|
||||
|
||||
def dummy(self, cr, uid, ids, context=None):
|
||||
""" TDE: defined to have buttons that do basically nothing. It is
|
||||
currently impossible to have buttons that do nothing special
|
||||
in views (if type not specified, considered as 'object'). """
|
||||
return True
|
||||
|
||||
#------------------------------------------------------
|
||||
# Wizard validation and send
|
||||
#------------------------------------------------------
|
||||
|
@ -218,8 +189,8 @@ class mail_compose_message(osv.TransientModel):
|
|||
for res_id in res_ids:
|
||||
# default values, according to the wizard options
|
||||
post_values = {
|
||||
'subject': wizard.subject if wizard.content_subtype == 'html' else False,
|
||||
'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % tools.ustr(wizard.body_text),
|
||||
'subject': wizard.subject,
|
||||
'body': wizard.body,
|
||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
|
||||
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<field name="model" invisible="1"/>
|
||||
<field name="res_id" invisible="1"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
<field name="content_subtype" invisible="1"/>
|
||||
<!-- visible wizard -->
|
||||
<label for="partner_ids" string="Recipients"/>
|
||||
<div>
|
||||
|
@ -25,8 +24,7 @@
|
|||
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
||||
context="{'force_email':True}" required="1"/>
|
||||
</div>
|
||||
<field name="subject" placeholder="Subject..."
|
||||
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
|
||||
<field name="subject" placeholder="Subject..."/>
|
||||
</group>
|
||||
<field name="body"/>
|
||||
<field name="attachment_ids" widget="many2many_binary"/>
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
import portal
|
||||
import mail_mail
|
||||
import wizard
|
||||
|
||||
import acquirer
|
||||
|
||||
# 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',
|
||||
'wizard/portal_wizard_view.xml',
|
||||
'wizard/share_wizard_view.xml',
|
||||
'acquirer_view.xml',
|
||||
],
|
||||
'demo': ['portal_demo.xml'],
|
||||
'css': ['static/src/css/portal.css'],
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
|
|
|
@ -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="view_mode">form</field>
|
||||
</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>
|
||||
</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_category,res.partner_category,base.model_res_partner_category,portal.group_portal,1,0,0,0
|
||||
access_res_partner_title,res.partner_title,base.model_res_partner_title,portal.group_portal,1,0,0,0
|
||||
access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0
|
||||
access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1
|
||||
|
|
|
|
@ -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',
|
||||
'complexity': 'easy',
|
||||
'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',
|
||||
'depends': ['sale_stock','portal'],
|
||||
'data': [
|
||||
'security/portal_security.xml',
|
||||
'portal_sale_view.xml',
|
||||
'portal_sale_data.xml',
|
||||
'res_config_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'category': 'Hidden',
|
||||
}
|
||||
|
||||
# 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"?>
|
||||
<openerp>
|
||||
<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
|
||||
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="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
|
@ -16,8 +39,8 @@
|
|||
<field name="help">You don't have any quotation.</field>
|
||||
</record>
|
||||
|
||||
<record id="action_order_form" model="ir.actions.act_window">
|
||||
<field name="name">Sales Orders</field>
|
||||
<record id="action_orders_portal" model="ir.actions.act_window">
|
||||
<field name="name">Sale Orders</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="view_mode">tree,form,calendar,graph</field>
|
||||
|
@ -48,8 +71,8 @@
|
|||
<field name="help">There are no public products.</field>
|
||||
</record>
|
||||
|
||||
<record id="action_invoice_tree1" model="ir.actions.act_window">
|
||||
<field name="name">Customer Invoices</field>
|
||||
<record id="portal_action_invoices" model="ir.actions.act_window">
|
||||
<field name="name">Invoices</field>
|
||||
<field name="res_model">account.invoice</field>
|
||||
<field name="view_mode">tree,form,calendar,graph</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="help">You don't have any invoice.</field>
|
||||
</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">
|
||||
<field name="name">Customer Payment</field>
|
||||
<record id="portal_action_vouchers" model="ir.actions.act_window">
|
||||
<field name="name">Refunds/Payments</field>
|
||||
<field name="res_model">account.voucher</field>
|
||||
<field name="domain">[('journal_id.type', 'in', ['bank', 'cash']), ('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>
|
||||
</record>
|
||||
|
||||
<menuitem name="Quotations" id="portal_quotations" parent="portal.portal_orders"
|
||||
action="action_order_tree5" sequence="10"/>
|
||||
<menuitem name="Sales Orders" id="portal_sales_orders" parent="portal.portal_orders"
|
||||
action="action_order_form" sequence="20"/>
|
||||
<menuitem name="Delivery Orders" id="portal_delivery" parent="portal.portal_orders"
|
||||
<menuitem id="portal_quotations" parent="portal.portal_orders"
|
||||
action="action_quotations_portal" sequence="10"/>
|
||||
<menuitem id="portal_sales_orders" parent="portal.portal_orders"
|
||||
action="action_orders_portal" sequence="20"/>
|
||||
<menuitem id="portal_delivery" parent="portal.portal_orders"
|
||||
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"/>
|
||||
|
||||
<menuitem name="Invoice" id="portal_invoices" parent="portal.portal_invoices_payements"
|
||||
action="action_invoice_tree1" sequence="10"/>
|
||||
<menuitem name="Refund/Payments" id="portal_payments" parent="portal.portal_invoices_payements"
|
||||
action="action_vendor_receipt" sequence="20"/>
|
||||
|
||||
<menuitem id="portal_invoices" parent="portal.portal_invoices_payements"
|
||||
action="portal_action_invoices" sequence="10"/>
|
||||
<menuitem id="portal_payments" parent="portal.portal_invoices_payements"
|
||||
action="portal_action_vouchers" sequence="20"/>
|
||||
</data>
|
||||
</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>
|
||||
<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 -->
|
||||
<record id="portal_sale_order_user_rule" model="ir.rule">
|
||||
<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):
|
||||
return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context)
|
||||
|
||||
def name_create(self, cr, uid, name, context=None):
|
||||
""" The UoM category and factor are required, so we'll have to add temporary values
|
||||
for imported UoMs """
|
||||
uom_categ = self.pool.get('product.uom.categ')
|
||||
# look for the category based on the english name, i.e. no context on purpose!
|
||||
# TODO: should find a way to have it translated but not created until actually used
|
||||
categ_misc = 'Unsorted/Imported Units'
|
||||
categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
|
||||
if categ_id:
|
||||
categ_id = categ_id[0]
|
||||
else:
|
||||
categ_id, _ = uom_categ.name_create(cr, uid, categ_misc)
|
||||
uom_id = self.create(cr, uid, {self._rec_name: name,
|
||||
'category_id': categ_id,
|
||||
'factor': 1})
|
||||
return self.name_get(cr, uid, [uom_id], context=context)[0]
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
if 'factor_inv' in data:
|
||||
if data['factor_inv'] <> 1:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,13 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from osv import fields, osv, orm
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from edi.models import edi
|
||||
from tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from tools.translate import _
|
||||
|
||||
PURCHASE_ORDER_LINE_EDI_STRUCT = {
|
||||
|
@ -62,16 +57,6 @@ PURCHASE_ORDER_EDI_STRUCT = {
|
|||
class purchase_order(osv.osv, EDIMixin):
|
||||
_inherit = 'purchase.order'
|
||||
|
||||
def wkf_send_rfq(self, cr, uid, ids, context=None):
|
||||
""""Override this method to add a link to mail"""
|
||||
if context is None:
|
||||
context = {}
|
||||
purchase_objs = self.browse(cr, uid, ids, context=context)
|
||||
edi_token = self.pool.get('edi.document').export_edi(cr, uid, purchase_objs, context = context)[0]
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
|
||||
return super(purchase_order, self).wkf_send_rfq(cr, uid, ids, context=ctx)
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Exports a purchase order"""
|
||||
edi_struct = dict(edi_struct or PURCHASE_ORDER_EDI_STRUCT)
|
||||
|
@ -106,22 +91,26 @@ class purchase_order(osv.osv, EDIMixin):
|
|||
# the desired company among the user's allowed companies
|
||||
|
||||
self._edi_requires_attributes(('company_id','company_address'), edi_document)
|
||||
res_partner_obj = self.pool.get('res.partner')
|
||||
res_partner = self.pool.get('res.partner')
|
||||
|
||||
# imported company_address = new partner address
|
||||
src_company_id, src_company_name = edi_document.pop('company_id')
|
||||
address_info = edi_document.pop('company_address')
|
||||
address_info['customer'] = True
|
||||
if 'name' not in address_info:
|
||||
address_info['name'] = src_company_name
|
||||
address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context)
|
||||
xid, company_name = edi_document.pop('company_id')
|
||||
# Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
|
||||
company_address_edi = edi_document.pop('company_address')
|
||||
company_address_edi['name'] = company_name
|
||||
company_address_edi['is_company'] = True
|
||||
company_address_edi['__import_model'] = 'res.partner'
|
||||
company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
|
||||
if company_address_edi.get('logo'):
|
||||
company_address_edi['image'] = company_address_edi.pop('logo')
|
||||
company_address_edi['supplier'] = True
|
||||
partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
|
||||
|
||||
# modify edi_document to refer to new partner/address
|
||||
partner_address = res_partner_obj.browse(cr, uid, address_id, context=context)
|
||||
edi_document.pop('partner_address', False) # ignored
|
||||
edi_document['partner_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
|
||||
|
||||
return address_id
|
||||
# modify edi_document to refer to new partner
|
||||
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||
partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
|
||||
edi_document['partner_id'] = partner_edi_m2o
|
||||
edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
|
||||
return partner_id
|
||||
|
||||
def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
|
||||
# TODO: refactor into common place for purchase/sale, e.g. into product module
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!--Export edi document -->
|
||||
<!--
|
||||
<record id="ir_actions_server_edi_purchase" model="ir.actions.server">
|
||||
<field name="code">if not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='purchase.email_template_edi_purchase', context=context)</field>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||
<field name="condition">True</field>
|
||||
<field name="name">Auto-email confirmed purchase orders</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- EDI related Email Templates menu -->
|
||||
<record model="ir.actions.act_window" id="action_email_templates">
|
||||
<field name="name">Email Templates</field>
|
||||
|
@ -25,24 +13,19 @@
|
|||
</record>
|
||||
</data>
|
||||
|
||||
<!-- Mail template and workflow bindings are done in a NOUPDATE block
|
||||
<!-- Mail template are declared in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
<!-- bind the mailing server action to purchase.order confirmed activity -->
|
||||
<!--
|
||||
<record id="purchase.act_confirmed" model="workflow.activity">
|
||||
<field name="action_id" ref="ir_actions_server_edi_purchase"/>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!--Email template -->
|
||||
<record id="email_template_edi_purchase" model="email.template">
|
||||
<field name="name">Automated Purchase Order Notification Mail</field>
|
||||
<field name="name">Purchase Order - Send by mail</field>
|
||||
<field name="email_from">${object.validator.email or ''}</field>
|
||||
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
|
||||
<field name="email_recipients">${object.partner_id.id}</field>
|
||||
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||
<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[
|
||||
<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:
|
||||
Your reference: ${object.partner_ref}<br />
|
||||
% endif
|
||||
% if object.user_id:
|
||||
Your contact: <a href="mailto:${object.validator.email or ''}?subject=Order%20${object.name}">${object.validator.name}</a>
|
||||
% endif
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can view the ${object.state in ('draft', 'sent') and 'request for quotation' or 'order confirmation'} document and download it using the following link:
|
||||
</p>
|
||||
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #FFF; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
|
||||
href="${ctx.get('edi_web_url_view') or ''}">View Order</a>
|
||||
|
||||
<br/>
|
||||
<p>If you have any question, do not hesitate to contact us.</p>
|
||||
<p>Thank you!</p>
|
||||
|
|
|
@ -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
|
||||
'''
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
template = mod_obj.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')
|
||||
template_id = template and template[1] or False
|
||||
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
|
||||
res_id = res and res[1] or False
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
try:
|
||||
template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
|
||||
except ValueError:
|
||||
template_id = False
|
||||
try:
|
||||
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
|
||||
except ValueError:
|
||||
compose_form_id = False
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_model': 'purchase.order',
|
||||
'default_res_id': ids[0],
|
||||
'default_use_template': True,
|
||||
'default_use_template': bool(template_id),
|
||||
'default_template_id': template_id,
|
||||
'default_composition_mode': 'comment',
|
||||
})
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(res_id, 'form')],
|
||||
'view_id': res_id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [(compose_form_id, 'form')],
|
||||
'view_id': compose_form_id,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
#TODO: implement messages system
|
||||
|
|
|
@ -27,36 +27,36 @@
|
|||
-
|
||||
Then I export the purchase order via EDI
|
||||
-
|
||||
!python {model: edi.document}: |
|
||||
order_pool = self.pool.get('purchase.order')
|
||||
order = order_pool.browse(cr, uid, ref("purchase_order_edi_1"))
|
||||
token = self.export_edi(cr, uid, [order])
|
||||
assert token, 'Invalid EDI Token'
|
||||
|
||||
!python {model: edi.edi}: |
|
||||
import json
|
||||
order_pool = self.pool.get('purchase.order')
|
||||
order = order_pool.browse(cr, uid, ref("purchase_order_edi_1"))
|
||||
edi_doc = self.generate_edi(cr, uid, [order])
|
||||
assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
|
||||
-
|
||||
Then I import a sample EDI document of a sale order
|
||||
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')
|
||||
edi_document = {
|
||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
|
||||
"__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_test",
|
||||
"__module": "sale",
|
||||
"__model": "sale.order",
|
||||
"__import_module": "purchase",
|
||||
"__import_model": "purchase.order",
|
||||
"__version": [6,1,0],
|
||||
"__version": [7,0,0],
|
||||
"name": "SO008",
|
||||
"currency": {
|
||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR",
|
||||
"__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.EUR",
|
||||
"__module": "base",
|
||||
"__model": "res.currency",
|
||||
"code": "EUR",
|
||||
"symbol": "€",
|
||||
},
|
||||
"date_order": "2011-09-13",
|
||||
"partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"],
|
||||
"partner_id": ["sale:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_test22", "Junjun wala"],
|
||||
"partner_address": {
|
||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||
"__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_address_7wdsjasdjh",
|
||||
"__module": "base",
|
||||
"__model": "res.partner",
|
||||
"phone": "(+32).81.81.37.00",
|
||||
|
@ -65,48 +65,48 @@
|
|||
"zip": "1367",
|
||||
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||
},
|
||||
"company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."],
|
||||
"company_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_company", "Supplier S.A."],
|
||||
"company_address": {
|
||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address",
|
||||
"__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_address",
|
||||
"__module": "base",
|
||||
"__model": "res.partner",
|
||||
"city": "Gerompont",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"],
|
||||
"country_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.be", "Belgium"],
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"street2": "mailbox 34",
|
||||
"bank_ids": [
|
||||
["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"]
|
||||
["base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"]
|
||||
],
|
||||
},
|
||||
"order_line": [{
|
||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0",
|
||||
"__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeuI-SSP0",
|
||||
"__module": "sale",
|
||||
"__model": "sale.order.line",
|
||||
"__import_module": "purchase",
|
||||
"__import_model": "purchase.order.line",
|
||||
"name": "PC Assemble SC234",
|
||||
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"],
|
||||
"product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
|
||||
"product_qty": 1.0,
|
||||
"date_planned": "2011-09-30",
|
||||
"sequence": 10,
|
||||
"price_unit": 150.0,
|
||||
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||
"product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||
},
|
||||
{
|
||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad",
|
||||
"__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeadasdad",
|
||||
"__module": "sale",
|
||||
"__model": "sale.order.line",
|
||||
"__import_module": "purchase",
|
||||
"__import_model": "purchase.order.line",
|
||||
"name": "PC on Demand",
|
||||
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"],
|
||||
"product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
|
||||
"product_qty": 10.0,
|
||||
"sequence": 11,
|
||||
"date_planned": "2011-09-15",
|
||||
"price_unit": 20.0,
|
||||
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
|
||||
"product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
|
||||
}],
|
||||
}
|
||||
new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||
|
@ -114,6 +114,7 @@
|
|||
order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
|
||||
|
||||
# check bank info on partner
|
||||
assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order"
|
||||
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||
bank_info = order_new.partner_id.bank_ids[0]
|
||||
assert bank_info.acc_number == "Guys bank: 123477777-156113", 'Expected "Guys bank: 123477777-156113", got %s' % bank_info.acc_number
|
||||
|
@ -133,3 +134,104 @@
|
|||
assert purchase_line.product_qty == 10 , "product qty is not same"
|
||||
else:
|
||||
raise AssertionError('unknown order line: %s' % purchase_line)
|
||||
-
|
||||
"Then I import a sample EDI document of a sale order (v6.1 - to test backwards compatibility)"
|
||||
-
|
||||
!python {model: edi.edi}: |
|
||||
purchase_order_pool = self.pool.get('purchase.order')
|
||||
edi_document = {
|
||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
|
||||
"__module": "sale",
|
||||
"__model": "sale.order",
|
||||
"__import_module": "purchase",
|
||||
"__import_model": "purchase.order",
|
||||
"__version": [6,1,0],
|
||||
"name": "SO08v61",
|
||||
"currency": {
|
||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR",
|
||||
"__module": "base",
|
||||
"__model": "res.currency",
|
||||
"code": "EUR",
|
||||
"symbol": "€",
|
||||
},
|
||||
"date_order": "2011-09-13",
|
||||
"partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"],
|
||||
"partner_address": {
|
||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh",
|
||||
"__module": "base",
|
||||
"__model": "res.partner.address",
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"city": "Gerompont",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||
},
|
||||
"company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."],
|
||||
"company_address": {
|
||||
"__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address",
|
||||
"__module": "base",
|
||||
"__model": "res.partner.address",
|
||||
"city": "Gerompont",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"],
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"street2": "mailbox 34",
|
||||
"bank_ids": [
|
||||
["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Another bank: 123477700-156113"]
|
||||
],
|
||||
},
|
||||
"order_line": [{
|
||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0",
|
||||
"__module": "sale",
|
||||
"__model": "sale.order.line",
|
||||
"__import_module": "purchase",
|
||||
"__import_model": "purchase.order.line",
|
||||
"name": "Basic PC",
|
||||
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
|
||||
"product_qty": 1.0,
|
||||
"date_planned": "2011-09-30",
|
||||
"sequence": 10,
|
||||
"price_unit": 150.0,
|
||||
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
|
||||
},
|
||||
{
|
||||
"__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad",
|
||||
"__module": "sale",
|
||||
"__model": "sale.order.line",
|
||||
"__import_module": "purchase",
|
||||
"__import_model": "purchase.order.line",
|
||||
"name": "Medium PC",
|
||||
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
|
||||
"product_qty": 10.0,
|
||||
"sequence": 11,
|
||||
"date_planned": "2011-09-15",
|
||||
"price_unit": 20.0,
|
||||
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
|
||||
}],
|
||||
}
|
||||
new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||
assert new_purchase_order_id, 'Purchase order import failed'
|
||||
order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
|
||||
|
||||
# check bank info on partner
|
||||
assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order"
|
||||
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
|
||||
bank_info = order_new.partner_id.bank_ids[0]
|
||||
assert bank_info.acc_number == "Another bank: 123477700-156113", 'Expected "Another bank: 123477700-156113", got %s' % bank_info.acc_number
|
||||
|
||||
assert order_new.pricelist_id.name == 'Default Purchase Pricelist' , "Default Purchase Pricelist was not automatically assigned"
|
||||
assert order_new.amount_total == 350, "Amount total is not same"
|
||||
assert order_new.amount_untaxed == 350, "untaxed amount is not same"
|
||||
assert len(order_new.order_line) == 2, "Purchase order lines number mismatch"
|
||||
for purchase_line in order_new.order_line:
|
||||
if purchase_line.name == 'Basic PC':
|
||||
assert purchase_line.product_uom.name == "PCE" , "uom is not same"
|
||||
assert purchase_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
|
||||
assert purchase_line.product_qty == 1 , "product qty is not same"
|
||||
elif purchase_line.name == 'Medium PC':
|
||||
assert purchase_line.product_uom.name == "PCE" , "uom is not same"
|
||||
assert purchase_line.price_unit == 20 , "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
|
||||
assert purchase_line.product_qty == 10 , "product qty is not same"
|
||||
else:
|
||||
raise AssertionError('unknown order line: %s' % purchase_line)
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
# Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,13 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from osv import fields, osv, orm
|
||||
from openerp.osv import osv
|
||||
from edi import EDIMixin
|
||||
from edi.models import edi
|
||||
from tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
SALE_ORDER_LINE_EDI_STRUCT = {
|
||||
'sequence': True,
|
||||
|
@ -65,15 +60,6 @@ SALE_ORDER_EDI_STRUCT = {
|
|||
class sale_order(osv.osv, EDIMixin):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
def action_quotation_send(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
sale_objs = self.browse(cr, uid, ids, context=context)
|
||||
edi_token = self.pool.get('edi.document').export_edi(cr, uid, sale_objs, context = context)[0]
|
||||
web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
|
||||
return super(sale_order, self).action_quotation_send(cr, uid, ids, context=ctx)
|
||||
|
||||
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
|
||||
"""Exports a Sale order"""
|
||||
edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT)
|
||||
|
@ -102,34 +88,36 @@ class sale_order(osv.osv, EDIMixin):
|
|||
edi_doc_list.append(edi_doc)
|
||||
return edi_doc_list
|
||||
|
||||
|
||||
def _edi_import_company(self, cr, uid, edi_document, context=None):
|
||||
# TODO: for multi-company setups, we currently import the document in the
|
||||
# user's current company, but we should perhaps foresee a way to select
|
||||
# the desired company among the user's allowed companies
|
||||
|
||||
self._edi_requires_attributes(('company_id','company_address'), edi_document)
|
||||
res_partner_obj = self.pool.get('res.partner')
|
||||
res_partner = self.pool.get('res.partner')
|
||||
|
||||
# imported company_address = new partner address
|
||||
src_company_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')
|
||||
company_address_edi['customer'] = True
|
||||
partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
|
||||
|
||||
address_info = edi_document.pop('company_address')
|
||||
address_info['supplier'] = True
|
||||
if 'name' not in address_info:
|
||||
address_info['name'] = src_company_name
|
||||
# modify edi_document to refer to new partner
|
||||
partner = res_partner.browse(cr, uid, partner_id, context=context)
|
||||
partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
|
||||
edi_document['partner_id'] = partner_edi_m2o
|
||||
edi_document['partner_invoice_id'] = partner_edi_m2o
|
||||
edi_document['partner_shipping_id'] = partner_edi_m2o
|
||||
|
||||
address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context)
|
||||
edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
|
||||
return partner_id
|
||||
|
||||
# modify edi_document to refer to new partner/address
|
||||
partner_address = res_partner_obj.browse(cr, uid, address_id, context=context)
|
||||
edi_document.pop('partner_address', False) # ignored
|
||||
address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context)
|
||||
edi_document['partner_id'] = address_edi_m2o
|
||||
edi_document['partner_invoice_id'] = address_edi_m2o
|
||||
edi_document['partner_shipping_id'] = address_edi_m2o
|
||||
|
||||
return address_id
|
||||
|
||||
def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
|
||||
# TODO: refactor into common place for purchase/sale, e.g. into product module
|
||||
|
@ -171,7 +159,6 @@ class sale_order(osv.osv, EDIMixin):
|
|||
currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
|
||||
order_currency = res_currency.browse(cr, uid, currency_id)
|
||||
|
||||
date_order = edi_document['date_order']
|
||||
partner_ref = edi_document.pop('partner_ref', False)
|
||||
edi_document['client_order_ref'] = edi_document['name']
|
||||
edi_document['name'] = partner_ref or edi_document['name']
|
||||
|
@ -185,7 +172,7 @@ class sale_order(osv.osv, EDIMixin):
|
|||
|
||||
order_lines = edi_document['order_line']
|
||||
for order_line in order_lines:
|
||||
self._edi_requires_attributes(( 'product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
|
||||
self._edi_requires_attributes(('product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
|
||||
order_line['product_uom_qty'] = order_line['product_qty']
|
||||
del order_line['product_qty']
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- EDI related Email Templates menu -->
|
||||
<record model="ir.actions.act_window" id="action_email_templates">
|
||||
<field name="name">Email Templates</field>
|
||||
|
@ -15,19 +14,20 @@
|
|||
<menuitem id="base.menu_sales_configuration_misc" name="Miscellaneous" parent="base.menu_base_config" sequence="75"/>
|
||||
</data>
|
||||
|
||||
|
||||
<!-- Mail template is done in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
|
||||
<!--Email template -->
|
||||
<record id="email_template_edi_sale" model="email.template">
|
||||
<field name="name">Automated Sale Order Notification Mail</field>
|
||||
<field name="name">Sale Order - Send by Email</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="model_id" ref="sale.model_sale_order"/>
|
||||
<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[
|
||||
<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:
|
||||
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>
|
||||
|
||||
<p>
|
||||
You can view the ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} document, download it and pay online using the following link:
|
||||
</p>
|
||||
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #FFF; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
|
||||
href="${ctx.get('edi_web_url_view') or ''}">View Order</a>
|
||||
|
||||
% if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'):
|
||||
% 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)
|
||||
|
|
|
@ -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
|
||||
'''
|
||||
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
template = mod_obj.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')
|
||||
template_id = template and template[1] or False
|
||||
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
|
||||
res_id = res and res[1] or False
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
try:
|
||||
template_id = ir_model_data.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')[1]
|
||||
except ValueError:
|
||||
template_id = False
|
||||
try:
|
||||
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
|
||||
except ValueError:
|
||||
compose_form_id = False
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_model': 'sale.order',
|
||||
'default_res_id': ids[0],
|
||||
'default_use_template': True,
|
||||
'default_use_template': bool(template_id),
|
||||
'default_template_id': template_id,
|
||||
'default_composition_mode': 'comment',
|
||||
'mark_so_as_sent': True
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(res_id, 'form')],
|
||||
'view_id': res_id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [(compose_form_id, 'form')],
|
||||
'view_id': compose_form_id,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -166,8 +166,8 @@
|
|||
<header>
|
||||
<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="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 Mail" type="object" states="sent" 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 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="sent" 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>
|
||||
</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="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
|
@ -377,9 +377,9 @@
|
|||
</p>
|
||||
</field>
|
||||
</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="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
|
@ -390,7 +390,7 @@
|
|||
<field name="search_view_id" ref="view_sales_order_filter"/>
|
||||
</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="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
|
@ -401,7 +401,7 @@
|
|||
</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="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
|
@ -427,7 +427,7 @@
|
|||
</record>
|
||||
|
||||
<menuitem id="menu_sale_quotations"
|
||||
action="action_order_tree5" parent="base.menu_sales"
|
||||
action="action_quotations" parent="base.menu_sales"
|
||||
sequence="4"/>
|
||||
|
||||
<record id="action_order_tree" model="ir.actions.act_window">
|
||||
|
|
|
@ -25,70 +25,71 @@
|
|||
-
|
||||
Then I export the sale order via EDI
|
||||
-
|
||||
!python {model: edi.document}: |
|
||||
!python {model: edi.edi}: |
|
||||
import json
|
||||
sale_order = self.pool.get('sale.order')
|
||||
so = sale_order.browse(cr, uid, ref("sale_order_edi_1"))
|
||||
token = self.export_edi(cr, uid, [so])
|
||||
assert token, 'Invalid EDI Token'
|
||||
edi_doc = self.generate_edi(cr, uid, [so])
|
||||
assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
|
||||
-
|
||||
Then I import a sample EDI document of a purchase order
|
||||
"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')
|
||||
edi_document = {
|
||||
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test",
|
||||
"__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_test",
|
||||
"__module": "purchase",
|
||||
"__model": "purchase.order",
|
||||
"__import_module": "sale",
|
||||
"__import_model": "sale.order",
|
||||
"__version": [6,1,0],
|
||||
"__version": [7,0,0],
|
||||
"name": "PO00011",
|
||||
"date_order": "2011-09-12",
|
||||
"currency": {
|
||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR",
|
||||
"__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.EUR",
|
||||
"__module": "base",
|
||||
"__model": "res.currency",
|
||||
"code": "EUR",
|
||||
"symbol": "€",
|
||||
},
|
||||
"company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."],
|
||||
"company_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.main_company", "Client S.A."],
|
||||
"company_address": {
|
||||
"__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address",
|
||||
"__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.some_address",
|
||||
"__module": "base",
|
||||
"__model": "res.partner",
|
||||
"phone": "(+32).81.81.37.00",
|
||||
"street": "Chaussee de Namur 40",
|
||||
"city": "Gerompont",
|
||||
"zip": "1367",
|
||||
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
|
||||
"country_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.be", "Belgium"],
|
||||
"bank_ids": [
|
||||
["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"]
|
||||
["base:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_bank-adaWadsadasdDJzGbp","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": [{
|
||||
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ",
|
||||
"__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-AlhsVDZGoKvJ",
|
||||
"__module": "purchase",
|
||||
"__model": "purchase.order.line",
|
||||
"__import_module": "sale",
|
||||
"__import_model": "sale.order.line",
|
||||
"name": "PC Assemble SC234",
|
||||
"price_unit": 150.0,
|
||||
"product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||
"product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
|
||||
"product_qty": 1.0,
|
||||
"product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"],
|
||||
"product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"],
|
||||
},
|
||||
{
|
||||
"__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e",
|
||||
"__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-Alsads33e",
|
||||
"__module": "purchase",
|
||||
"__model": "purchase.order.line",
|
||||
"__import_module": "sale",
|
||||
"__import_model": "sale.order.line",
|
||||
"name": "PC on Demand",
|
||||
"price_unit": 100.0,
|
||||
"product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
|
||||
"product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
|
||||
"product_qty": 2.0,
|
||||
"product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"],
|
||||
"product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"],
|
||||
}],
|
||||
}
|
||||
new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context)
|
||||
|
@ -96,9 +97,10 @@
|
|||
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 bank_info.acc_number == "Another bank: 032465700-156700", 'Expected "Another bank: 032465700-156700", got %s' % bank_info.acc_number
|
||||
|
||||
assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
|
||||
assert order_new.amount_total == 350, "Amount total is wrong"
|
||||
|
@ -115,3 +117,101 @@
|
|||
assert sale_line.product_uom_qty == 2 , "product qty is not same"
|
||||
else:
|
||||
raise AssertionError('unknown order line: %s' % sale_line)
|
||||
-
|
||||
"Then I import a sample EDI document of a purchase order (v6.1 - to test backwards compatibility)"
|
||||
-
|
||||
!python {model: edi.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