diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 3e86c4963b7..35afac5a508 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -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): diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py index 751eb8656f7..3e27bc5ea99 100644 --- a/addons/account/edi/invoice.py +++ b/addons/account/edi/invoice.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -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 diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml index d86ebca3537..723f5c06c22 100644 --- a/addons/account/edi/invoice_action_data.xml +++ b/addons/account/edi/invoice_action_data.xml @@ -1,17 +1,6 @@ - - - if (object.type in ('out_invoice', 'out_refund')) and not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='account.email_template_edi_invoice', context=context) - - code - ir.actions.server - - True - Auto-email confirmed invoices - - Email Templates @@ -27,22 +16,19 @@ - - - - - - - Automated Invoice Notification Mail + Invoice - Send by Email ${object.user_id.email or object.company_id.email or 'noreply@localhost'} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' }) ${object.partner_id.id} + + Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''} @@ -58,15 +44,11 @@ % if object.origin:   Order reference: ${object.origin}
% endif + % if object.user_id:   Your contact: ${object.user_id.name} + % endif

-

- You can view the invoice document, download it and pay online using the following link: -

- View Invoice - % if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'): <% comp_name = quote(object.company_id.name) diff --git a/addons/account/res_config_view.xml b/addons/account/res_config_view.xml index 52c8c8daaa9..4bfd3697d89 100644 --- a/addons/account/res_config_view.xml +++ b/addons/account/res_config_view.xml @@ -227,7 +227,7 @@ - +
+
+ +
+
\ No newline at end of file diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index b46175f7feb..d6c2f288205 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -1,12 +1,35 @@ + + + + sale.order.form.payment + sale.order + + + + + + + + + account.invoice.form.payment + account.invoice + + + + + + + + - + Quotations ir.actions.act_window sale.order @@ -16,8 +39,8 @@ You don't have any quotation. - - Sales Orders + + Sale Orders ir.actions.act_window sale.order tree,form,calendar,graph @@ -48,8 +71,8 @@ There are no public products. - - Customer Invoices + + Invoices account.invoice tree,form,calendar,graph [('type','=','out_invoice')] @@ -57,9 +80,21 @@ You don't have any invoice. + + + + tree + + + + + + form + + - - Customer Payment + + Refunds/Payments account.voucher [('journal_id.type', 'in', ['bank', 'cash']), ('type','=','receipt')] {'type':'receipt'} @@ -68,19 +103,17 @@ You don't have any refunds or payments. - - - + + - - - - - + + diff --git a/addons/portal_sale/res_config.py b/addons/portal_sale/res_config.py new file mode 100644 index 00000000000..d96a63f173e --- /dev/null +++ b/addons/portal_sale/res_config.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +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."), + } \ No newline at end of file diff --git a/addons/portal_sale/res_config_view.xml b/addons/portal_sale/res_config_view.xml new file mode 100644 index 00000000000..ce33a1a1a72 --- /dev/null +++ b/addons/portal_sale/res_config_view.xml @@ -0,0 +1,22 @@ + + + + + + + account.config.settings + + + +
+ +
+
+
+
+ +
+
\ No newline at end of file diff --git a/addons/portal_sale/security/portal_security.xml b/addons/portal_sale/security/portal_security.xml index 19d44947f03..e86258dcdd2 100644 --- a/addons/portal_sale/security/portal_security.xml +++ b/addons/portal_sale/security/portal_security.xml @@ -2,6 +2,18 @@ + + View Online Payment Options + + 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. + + + + + + Portal Personal Quotations/Sales Orders diff --git a/addons/product/product.py b/addons/product/product.py index 27cd4249a60..1333c9581a7 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -100,6 +100,23 @@ class product_uom(osv.osv): def _factor_inv_write(self, cursor, user, id, name, value, arg, context=None): return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context) + def name_create(self, cr, uid, name, context=None): + """ The UoM category and factor are required, so we'll have to add temporary values + for imported UoMs """ + uom_categ = self.pool.get('product.uom.categ') + # look for the category based on the english name, i.e. no context on purpose! + # TODO: should find a way to have it translated but not created until actually used + categ_misc = 'Unsorted/Imported Units' + categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)]) + if categ_id: + categ_id = categ_id[0] + else: + categ_id, _ = uom_categ.name_create(cr, uid, categ_misc) + uom_id = self.create(cr, uid, {self._rec_name: name, + 'category_id': categ_id, + 'factor': 1}) + return self.name_get(cr, uid, [uom_id], context=context)[0] + def create(self, cr, uid, data, context=None): if 'factor_inv' in data: if data['factor_inv'] <> 1: diff --git a/addons/purchase/edi/purchase_order.py b/addons/purchase/edi/purchase_order.py index d1c00a3e60d..cdb4c0ca73d 100644 --- a/addons/purchase/edi/purchase_order.py +++ b/addons/purchase/edi/purchase_order.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -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 diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml index fab7eeacf6c..4190a625fdb 100644 --- a/addons/purchase/edi/purchase_order_action_data.xml +++ b/addons/purchase/edi/purchase_order_action_data.xml @@ -1,18 +1,6 @@ - - - Email Templates @@ -25,24 +13,19 @@ - - - - - Automated Purchase Order Notification Mail + Purchase Order - Send by mail ${object.validator.email or ''} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) ${object.partner_id.id} + + RFQ_${(object.name or '').replace('/','_')} @@ -61,15 +44,11 @@ % if object.partner_ref:   Your reference: ${object.partner_ref}
% endif + % if object.user_id:   Your contact: ${object.validator.name} + % endif

-

- You can view the ${object.state in ('draft', 'sent') and 'request for quotation' or 'order confirmation'} document and download it using the following link: -

- View Order -

If you have any question, do not hesitate to contact us.

Thank you!

diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 5bb38316604..ad7563140b2 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -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 diff --git a/addons/purchase/test/process/edi_purchase_order.yml b/addons/purchase/test/process/edi_purchase_order.yml index d0e90f3788d..8e34606afb4 100644 --- a/addons/purchase/test/process/edi_purchase_order.yml +++ b/addons/purchase/test/process/edi_purchase_order.yml @@ -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) \ No newline at end of file diff --git a/addons/sale/edi/sale_order.py b/addons/sale/edi/sale_order.py index edec15a86ae..15389f1481c 100644 --- a/addons/sale/edi/sale_order.py +++ b/addons/sale/edi/sale_order.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. +# Copyright (c) 2011-2012 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -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'] diff --git a/addons/sale/edi/sale_order_action_data.xml b/addons/sale/edi/sale_order_action_data.xml index da1a1ceb393..92611cea180 100644 --- a/addons/sale/edi/sale_order_action_data.xml +++ b/addons/sale/edi/sale_order_action_data.xml @@ -1,7 +1,6 @@ - Email Templates @@ -15,19 +14,20 @@ - - Automated Sale Order Notification Mail + Sale Order - Send by Email ${object.user_id.email or ''} - ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) + ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' }) ${object.partner_invoice_id.id} + + ${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''} @@ -46,16 +46,12 @@ % if object.client_order_ref:   Your reference: ${object.client_order_ref}
% endif + % if object.user_id:   Your contact: ${object.user_id.name} + % endif

-

- You can view the ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} document, download it and pay online using the following link: -

- View Order - - % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'): + % 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) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index c1af024ad9c..3c568cc8227 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -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): diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 9f385339173..419047dbbd2 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -166,8 +166,8 @@