diff --git a/addons/account/__init__.py b/addons/account/__init__.py
index 36cc1c85b5a..a243b94c2da 100644
--- a/addons/account/__init__.py
+++ b/addons/account/__init__.py
@@ -35,5 +35,5 @@ import product
import ir_sequence
import company
import res_currency
-
+import edi
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py
index 9445688ec6d..81d09b30ae9 100644
--- a/addons/account/__openerp__.py
+++ b/addons/account/__openerp__.py
@@ -53,7 +53,7 @@ module named account_voucher.
'website': 'http://www.openerp.com',
'images' : ['images/accounts.jpeg','images/bank_statement.jpeg','images/cash_register.jpeg','images/chart_of_accounts.jpeg','images/customer_invoice.jpeg','images/journal_entries.jpeg'],
'init_xml': [],
- "depends" : ["base_setup", "product", "analytic", "process","board"],
+ "depends" : ["base_setup", "product", "analytic", "process", "board", "edi"],
'update_xml': [
'security/account_security.xml',
'security/ir.model.access.csv',
@@ -123,7 +123,8 @@ module named account_voucher.
'board_account_view.xml',
"wizard/account_report_profit_loss_view.xml",
"wizard/account_report_balance_sheet_view.xml",
- "account_bank_view.xml"
+ "edi/invoice_action_data.xml",
+ "account_bank_view.xml",
],
'demo_xml': [
'demo/account_demo.xml',
@@ -145,10 +146,9 @@ module named account_voucher.
'test/account_fiscalyear_close.yml',
'test/account_bank_statement.yml',
'test/account_cash_statement.yml',
+ 'test/test_edi_invoice.yml',
'test/account_report.yml',
-
-
- ],
+ ],
'installable': True,
'active': False,
'certificate': '0080331923549',
diff --git a/addons/account/account.py b/addons/account/account.py
index d4712c656a4..630735c487f 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -404,7 +404,7 @@ class account_account(osv.osv):
return True
_columns = {
- 'name': fields.char('Name', size=128, required=True, select=True),
+ 'name': fields.char('Name', size=256, required=True, select=True),
'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
'code': fields.char('Code', size=64, required=True, select=1),
'type': fields.selection([
@@ -431,9 +431,9 @@ class account_account(osv.osv):
'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
'foreign_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Foreign Balance', multi='balance',
help="Total amount (in Secondary currency) for transactions held in secondary currency for this account."),
- 'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance',
+ 'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance',
help="Total amount (in Company currency) for transactions held in secondary currency for this account."),
- 'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance',
+ 'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance',
help="Value of Loss or Gain due to changes in exchange rate when doing multi-currency transactions."),
'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."),
'exchange_rate': fields.related('currency_id', 'rate', type='float', string='Exchange Rate', digits=(12,6)),
@@ -2001,8 +2001,11 @@ class account_tax(osv.osv):
cur_price_unit+=amount2
return res
- def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
+ def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None, force_excluded=False):
"""
+ :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
+ tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
+ False
RETURN: {
'total': 0.0, # Total without taxes
'total_included: 0.0, # Total with taxes
@@ -2014,10 +2017,10 @@ class account_tax(osv.osv):
tin = []
tex = []
for tax in taxes:
- if tax.price_include:
- tin.append(tax)
- else:
+ if not tax.price_include or force_excluded:
tex.append(tax)
+ else:
+ tin.append(tax)
tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
for r in tin:
totalex -= r.get('amount', 0.0)
@@ -2183,6 +2186,7 @@ class account_model(osv.osv):
account_move_obj = self.pool.get('account.move')
account_move_line_obj = self.pool.get('account.move.line')
pt_obj = self.pool.get('account.payment.term')
+ period_obj = self.pool.get('account.period')
if context is None:
context = {}
@@ -2190,14 +2194,14 @@ class account_model(osv.osv):
if datas.get('date', False):
context.update({'date': datas['date']})
- period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
- if not period_id:
- raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
- period_id = period_id[0]
-
move_date = context.get('date', time.strftime('%Y-%m-%d'))
move_date = datetime.strptime(move_date,"%Y-%m-%d")
for model in self.browse(cr, uid, ids, context=context):
+ ctx = context.copy()
+ ctx.update({'company_id': model.company_id.id})
+ period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
+ period_id = period_ids and period_ids[0] or False
+ ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
try:
entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
except:
@@ -2246,9 +2250,7 @@ class account_model(osv.osv):
'date': context.get('date',time.strftime('%Y-%m-%d')),
'date_maturity': date_maturity
})
- c = context.copy()
- c.update({'journal_id': model.journal_id.id,'period_id': period_id})
- account_move_line_obj.create(cr, uid, val, context=c)
+ account_move_line_obj.create(cr, uid, val, context=ctx)
return move_ids
@@ -2395,7 +2397,7 @@ class account_account_template(osv.osv):
_description ='Templates for Accounts'
_columns = {
- 'name': fields.char('Name', size=128, required=True, select=True),
+ 'name': fields.char('Name', size=256, required=True, select=True),
'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
'code': fields.char('Code', size=64, select=1),
'type': fields.selection([
diff --git a/addons/account/account_bank.py b/addons/account/account_bank.py
index 2a9e432894f..acf070a3ad7 100644
--- a/addons/account/account_bank.py
+++ b/addons/account/account_bank.py
@@ -42,8 +42,12 @@ class bank(osv.osv):
return (bank.bank_name or '') + ' ' + bank.acc_number
def post_write(self, cr, uid, ids, context={}):
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
obj_acc = self.pool.get('account.account')
obj_data = self.pool.get('ir.model.data')
+
for bank in self.browse(cr, uid, ids, context):
if bank.company_id and not bank.journal_id:
# Find the code and parent of the bank account to create
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index e445e3f7bc8..522e9d97251 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -251,7 +251,7 @@ class account_invoice(osv.osv):
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
+ 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',
store={
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
@@ -795,6 +795,7 @@ class account_invoice(osv.osv):
"""Creates invoice related analytics and financial move lines"""
ait_obj = self.pool.get('account.invoice.tax')
cur_obj = self.pool.get('res.currency')
+ period_obj = self.pool.get('account.period')
context = {}
for inv in self.browse(cr, uid, ids):
if not inv.journal_id.sequence_id:
@@ -923,10 +924,10 @@ class account_invoice(osv.osv):
'narration':inv.comment
}
period_id = inv.period_id and inv.period_id.id or False
+ ctx.update({'company_id': inv.company_id.id})
if not period_id:
- period_ids = self.pool.get('account.period').search(cr, uid, [('date_start','<=',inv.date_invoice or time.strftime('%Y-%m-%d')),('date_stop','>=',inv.date_invoice or time.strftime('%Y-%m-%d')), ('company_id', '=', inv.company_id.id)])
- if period_ids:
- period_id = period_ids[0]
+ period_ids = period_obj.find(cr, uid, inv.date_invoice, context=ctx)
+ period_id = period_ids and period_ids[0] or False
if period_id:
move['period_id'] = period_id
for i in line:
@@ -1323,9 +1324,9 @@ class account_invoice_line(osv.osv):
raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
if not product:
if type in ('in_invoice', 'in_refund'):
- return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
+ return {'value': {}, 'domain':{'product_uom':[]}}
else:
- return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
+ return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
fpos_obj = self.pool.get('account.fiscal.position')
fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
@@ -1378,7 +1379,6 @@ class account_invoice_line(osv.osv):
if res2:
domain = {'uos_id':[('category_id','=',res2 )]}
- result['categ_id'] = res.categ_id.id
res_final = {'value':result, 'domain':domain}
if not company_id or not currency_id:
diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py
index dd749ba9820..55e154eed6d 100644
--- a/addons/account/account_move_line.py
+++ b/addons/account/account_move_line.py
@@ -1084,8 +1084,6 @@ class account_move_line(osv.osv):
f.set("invisible", "context.get('journal_id', False)")
elif field in ('period_id',):
f.set("invisible", "context.get('period_id', False)")
- else:
- f.set('invisible', "context.get('visible_id') not in %s" % (fields.get(field)))
orm.setup_modifiers(f, fields_get[field], context=context,
in_tree_view=True)
@@ -1245,6 +1243,12 @@ class account_move_line(osv.osv):
m = move_obj.browse(cr, uid, vals['move_id'])
context['journal_id'] = m.journal_id.id
context['period_id'] = m.period_id.id
+ #we need to treat the case where a value is given in the context for period_id as a string
+ if 'period_id' not in context or not isinstance(context.get('period_id', ''), (int, long)):
+ period_candidate_ids = self.pool.get('account.period').name_search(cr, uid, name=context.get('period_id',''))
+ if len(period_candidate_ids) != 1:
+ raise osv.except_osv(_('Encoding error'), _('No period found or period given is ambigous.'))
+ context['period_id'] = period_candidate_ids[0][0]
self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
move_id = vals.get('move_id', False)
journal = journal_obj.browse(cr, uid, context['journal_id'], context=context)
@@ -1326,7 +1330,7 @@ class account_move_line(osv.osv):
base_sign = 'base_sign'
tax_sign = 'tax_sign'
tmp_cnt = 0
- for tax in tax_obj.compute_all(cr, uid, [tax_id], total, 1.00).get('taxes'):
+ for tax in tax_obj.compute_all(cr, uid, [tax_id], total, 1.00, force_excluded=True).get('taxes'):
#create the base movement
if tmp_cnt == 0:
if tax[base_code]:
@@ -1338,8 +1342,6 @@ class account_move_line(osv.osv):
else:
data = {
'move_id': vals['move_id'],
- 'journal_id': vals['journal_id'],
- 'period_id': vals['period_id'],
'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
'date': vals['date'],
'partner_id': vals.get('partner_id',False),
@@ -1356,8 +1358,6 @@ class account_move_line(osv.osv):
#create the VAT movement
data = {
'move_id': vals['move_id'],
- 'journal_id': vals['journal_id'],
- 'period_id': vals['period_id'],
'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
'date': vals['date'],
'partner_id': vals.get('partner_id',False),
diff --git a/addons/account/account_report.xml b/addons/account/account_report.xml
index 32d10ba0153..9bb40a7a503 100644
--- a/addons/account/account_report.xml
+++ b/addons/account/account_report.xml
@@ -19,6 +19,7 @@
rml="account/report/account_print_invoice.rml"
string="Invoices"
attachment="(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/',''))"
+ usage="default"
multi="True"/>
diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml
index 5dd8406b126..1d336dda460 100644
--- a/addons/account/account_view.xml
+++ b/addons/account/account_view.xml
@@ -1235,8 +1235,8 @@
-
-
+
+
@@ -1306,7 +1306,6 @@
account.accountOpen Journal Items
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/account/company.py b/addons/account/company.py
index c903b6d4a71..4ad4d1226d7 100644
--- a/addons/account/company.py
+++ b/addons/account/company.py
@@ -24,6 +24,7 @@ from osv import fields, osv
class res_company(osv.osv):
_inherit = "res.company"
_columns = {
+ 'paypal_account': fields.char("Paypal Account", size=128, help="Paypal username (usually email) for receiving online payments."),
'overdue_msg': fields.text('Overdue Payments Message', translate=True),
'property_reserve_and_surplus_account': fields.property(
'account.account',
diff --git a/addons/account/company_view.xml b/addons/account/company_view.xml
index 0664fadd761..0cc67f23cfe 100644
--- a/addons/account/company_view.xml
+++ b/addons/account/company_view.xml
@@ -24,6 +24,7 @@
+
diff --git a/addons/account/edi/__init__.py b/addons/account/edi/__init__.py
new file mode 100644
index 00000000000..d86e8a91bf3
--- /dev/null
+++ b/addons/account/edi/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import invoice
diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py
new file mode 100644
index 00000000000..061bf8ee97d
--- /dev/null
+++ b/addons/account/edi/invoice.py
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import fields, osv, orm
+from edi import EDIMixin
+
+INVOICE_LINE_EDI_STRUCT = {
+ 'name': True,
+ 'origin': True,
+ 'uos_id': True,
+ 'product_id': True,
+ 'price_unit': True,
+ 'quantity': True,
+ 'discount': True,
+ 'note': True,
+
+ # fields used for web preview only - discarded on import
+ 'price_subtotal': True,
+}
+
+INVOICE_TAX_LINE_EDI_STRUCT = {
+ 'name': True,
+ 'base': True,
+ 'amount': True,
+ 'manual': True,
+ 'sequence': True,
+ 'base_amount': True,
+ 'tax_amount': True,
+}
+
+INVOICE_EDI_STRUCT = {
+ 'name': True,
+ 'origin': True,
+ 'company_id': True, # -> to be changed into partner
+ 'type': True, # -> reversed at import
+ 'internal_number': True, # -> reference at import
+ 'comment': True,
+ 'date_invoice': True,
+ 'date_due': True,
+ 'partner_id': True,
+ 'payment_term': True,
+ #custom: currency_id
+ 'invoice_line': INVOICE_LINE_EDI_STRUCT,
+ 'tax_line': INVOICE_TAX_LINE_EDI_STRUCT,
+
+ # fields used for web preview only - discarded on import
+ #custom: 'partner_ref'
+ 'amount_total': True,
+ 'amount_untaxed': True,
+ 'amount_tax': True,
+}
+
+class account_invoice(osv.osv, EDIMixin):
+ _inherit = 'account.invoice'
+
+ def edi_export(self, cr, uid, records, edi_struct=None, context=None):
+ """Exports a supplier or customer invoice"""
+ edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT)
+ res_company = self.pool.get('res.company')
+ res_partner_address = self.pool.get('res.partner.address')
+ edi_doc_list = []
+ for invoice in records:
+ # generate the main report
+ self._edi_generate_report_attachment(cr, uid, invoice, context=context)
+ edi_doc = super(account_invoice,self).edi_export(cr, uid, [invoice], edi_struct, context)[0]
+ edi_doc.update({
+ 'company_address': res_company.edi_export_address(cr, uid, invoice.company_id, context=context),
+ 'company_paypal_account': invoice.company_id.paypal_account,
+ 'partner_address': res_partner_address.edi_export(cr, uid, [invoice.address_invoice_id], context=context)[0],
+
+ 'currency': self.pool.get('res.currency').edi_export(cr, uid, [invoice.currency_id], context=context)[0],
+ 'partner_ref': invoice.reference or False,
+ })
+ edi_doc_list.append(edi_doc)
+ return edi_doc_list
+
+ def _edi_tax_account(self, cr, uid, invoice_type='out_invoice', context=None):
+ #TODO/FIXME: should select proper Tax Account
+ account_pool = self.pool.get('account.account')
+ account_ids = account_pool.search(cr, uid, [('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')])
+ tax_account = False
+ if account_ids:
+ tax_account = account_pool.browse(cr, uid, account_ids[0])
+ return tax_account
+
+ def _edi_invoice_account(self, cr, uid, partner_id, invoice_type, context=None):
+ partner_pool = self.pool.get('res.partner')
+ partner = partner_pool.browse(cr, uid, partner_id, context=context)
+ if invoice_type in ('out_invoice', 'out_refund'):
+ invoice_account = partner.property_account_receivable
+ else:
+ invoice_account = partner.property_account_payable
+ return invoice_account
+
+ def _edi_product_account(self, cr, uid, product_id, invoice_type, context=None):
+ product_pool = self.pool.get('product.product')
+ product = product_pool.browse(cr, uid, product_id, context=context)
+ if invoice_type in ('out_invoice','out_refund'):
+ account = product.property_account_income or product.categ_id.property_account_income_categ
+ else:
+ account = product.property_account_expense or product.categ_id.property_account_expense_categ
+ return account
+
+ def _edi_import_company(self, cr, uid, edi_document, context=None):
+ # TODO: for multi-company setups, we currently import the document in the
+ # user's current company, but we should perhaps foresee a way to select
+ # the desired company among the user's allowed companies
+
+ self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
+ res_partner_address = self.pool.get('res.partner.address')
+ res_partner = self.pool.get('res.partner')
+
+ # imported company = new partner
+ src_company_id, src_company_name = edi_document.pop('company_id')
+ partner_id = self.edi_import_relation(cr, uid, 'res.partner', src_company_name,
+ src_company_id, context=context)
+ invoice_type = edi_document['type']
+ partner_value = {}
+ if invoice_type in ('out_invoice', 'out_refund'):
+ partner_value.update({'customer': True})
+ if invoice_type in ('in_invoice', 'in_refund'):
+ partner_value.update({'supplier': True})
+ res_partner.write(cr, uid, [partner_id], partner_value, context=context)
+
+ # imported company_address = new partner address
+ address_info = edi_document.pop('company_address')
+ address_info['partner_id'] = (src_company_id, src_company_name)
+ address_info['type'] = 'invoice'
+ address_id = res_partner_address.edi_import(cr, uid, address_info, context=context)
+
+ # modify edi_document to refer to new partner
+ partner_address = res_partner_address.browse(cr, uid, address_id, context=context)
+ edi_document['partner_id'] = (src_company_id, src_company_name)
+ edi_document.pop('partner_address', False) # ignored
+ edi_document['address_invoice_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
+
+ return partner_id
+
+
+ def edi_import(self, cr, uid, edi_document, context=None):
+ """ During import, invoices will import the company that is provided in the invoice as
+ a new partner (e.g. supplier company for a customer invoice will be come a supplier
+ record for the new invoice.
+ Summary of tasks that need to be done:
+ - import company as a new partner, if type==in then supplier=1, else customer=1
+ - partner_id field is modified to point to the new partner
+ - company_address data used to add address to new partner
+ - change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
+ - reference: should contain the value of the 'internal_number'
+ - reference_type: 'none'
+ - internal number: reset to False, auto-generated
+ - journal_id: should be selected based on type: simply put the 'type'
+ in the context when calling create(), will be selected correctly
+ - payment_term: if set, create a default one based on name...
+ - for invoice lines, the account_id value should be taken from the
+ product's default, i.e. from the default category, as it will not
+ be provided.
+ - for tax lines, we disconnect from the invoice.line, so all tax lines
+ will be of type 'manual', and default accounts should be picked based
+ on the tax config of the DB where it is imported.
+ """
+ if context is None:
+ context = {}
+ self._edi_requires_attributes(('company_id','company_address','type','invoice_line','currency'), edi_document)
+
+ # extract currency info
+ res_currency = self.pool.get('res.currency')
+ currency_info = edi_document.pop('currency')
+ currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
+ currency = res_currency.browse(cr, uid, currency_id)
+ edi_document['currency_id'] = self.edi_m2o(cr, uid, currency, context=context)
+
+ # change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
+ invoice_type = edi_document['type']
+ invoice_type = invoice_type.startswith('in_') and invoice_type.replace('in_','out_') or invoice_type.replace('out_','in_')
+ edi_document['type'] = invoice_type
+
+ #import company as a new partner
+ partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
+
+ # Set Account
+ invoice_account = self._edi_invoice_account(cr, uid, partner_id, invoice_type, context=context)
+ edi_document['account_id'] = invoice_account and self.edi_m2o(cr, uid, invoice_account, context=context) or False
+
+ # reference: should contain the value of the 'internal_number'
+ edi_document['reference'] = edi_document.get('internal_number', False)
+ # reference_type: 'none'
+ edi_document['reference_type'] = 'none'
+
+ # internal number: reset to False, auto-generated
+ edi_document['internal_number'] = False
+
+ # discard web preview fields, if present
+ edi_document.pop('partner_ref', None)
+
+ # journal_id: should be selected based on type: simply put the 'type' in the context when calling create(), will be selected correctly
+ context.update(type=invoice_type)
+
+ # for invoice lines, the account_id value should be taken from the product's default, i.e. from the default category, as it will not be provided.
+ for edi_invoice_line in edi_document['invoice_line']:
+ product_info = edi_invoice_line['product_id']
+ product_id = self.edi_import_relation(cr, uid, 'product.product', product_info[1],
+ product_info[0], context=context)
+ account = self._edi_product_account(cr, uid, product_id, invoice_type, context=context)
+ # TODO: could be improved with fiscal positions perhaps
+ # account = fpos_obj.map_account(cr, uid, fiscal_position_id, account.id)
+ edi_invoice_line['account_id'] = self.edi_m2o(cr, uid, account, context=context) if account else False
+
+ # discard web preview fields, if present
+ edi_invoice_line.pop('price_subtotal', None)
+
+ # for tax lines, we disconnect from the invoice.line, so all tax lines will be of type 'manual', and default accounts should be picked based
+ # on the tax config of the DB where it is imported.
+ tax_account = self._edi_tax_account(cr, uid, context=context)
+ tax_account_info = self.edi_m2o(cr, uid, tax_account, context=context)
+ for edi_tax_line in edi_document.get('tax_line', []):
+ edi_tax_line['account_id'] = tax_account_info
+ edi_tax_line['manual'] = True
+
+ return super(account_invoice,self).edi_import(cr, uid, edi_document, context=context)
+
+
+ def _edi_record_display_action(self, cr, uid, id, context=None):
+ """Returns an appropriate action definition dict for displaying
+ the record with ID ``rec_id``.
+
+ :param int id: database ID of record to display
+ :return: action definition dict
+ """
+ action = super(account_invoice,self)._edi_record_display_action(cr, uid, id, context=context)
+ try:
+ invoice = self.browse(cr, uid, id, context=context)
+ if 'out_' in invoice.type:
+ view_ext_id = 'invoice_form'
+ journal_type = 'sale'
+ else:
+ view_ext_id = 'invoice_supplier_form'
+ journal_type = 'purchase'
+ ctx = "{'type': '%s', 'journal_type': '%s'}" % (invoice.type, journal_type)
+ action.update(context=ctx)
+ view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', view_ext_id)[1]
+ action.update(views=[(view_id,'form'), (False, 'tree')])
+ except ValueError:
+ # ignore if views are missing
+ pass
+ return action
+
+
+class account_invoice_line(osv.osv, EDIMixin):
+ _inherit='account.invoice.line'
+
+class account_invoice_tax(osv.osv, EDIMixin):
+ _inherit = "account.invoice.tax"
+
+
diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml
new file mode 100644
index 00000000000..0904ec6fd54
--- /dev/null
+++ b/addons/account/edi/invoice_action_data.xml
@@ -0,0 +1,186 @@
+
+
+
+
+
+ if (object.type in ('out_invoice', 'out_refund')) and not object.partner_id.opt_out: object.edi_export_and_email(template_ext_id='account.email_template_edi_invoice', context=context)
+
+ code
+ ir.actions.server
+
+ True
+ Auto-email confirmed invoices
+
+
+
+
+ Email Templates
+ email.template
+ form
+ form,tree
+
+
+ {'search_default_model_id':'account.invoice'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Automated Invoice Notification Mail
+ ${object.user_id.user_email or object.company_id.email or 'noreply@localhost'}
+ ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' })
+ ${object.address_invoice_id.email or ''}
+
+
+
+
+
Hello${object.address_invoice_id.name and ' ' or ''}${object.address_invoice_id.name or ''},
+
+
A new invoice is available for ${object.partner_id.name}:
+ OpenERP's Electronic Data Interchange documents are based on a generic and language
+ independent JSON serialization of the document's attribute.
+ It is usually very quick and straightforward to create a small plug-in for your preferred
+ application that will be capable of importing any OpenERP EDI document.
+ You can find out more details about how to do this and what the content of OpenERP EDI documents
+ is like in the OpenERP documentation.
+
+ To get started immediately, see is all it takes to use this EDI document in Python.
+
+
+
importurllib2, simplejson
+
edi_document =urllib2.urlopen('').read()
+
document_data = simplejson.loads(edi_document)[0]
+
print"Amount: ", document_data['amount_total']
+
+
+ You can download the raw EDI document here:
+
+
+
+ You may directly pay this invoice online via Paypal's secure payment gateway:
+
+
+
+
+
+
+
+
+
+
+
+ Please transfer to
+ (postal address on the invoice header)
+ using one of the following bank accounts. Be sure to mention the invoice
+ reference on the transfer:
+
+ You may directly pay this order online via Paypal's secure payment gateway:
+
+
+
+
+
+
+
+
+
+
+
+ Please transfer to
+ (postal address on the order header)
+ using one of the following bank accounts. Be sure to mention the document
+ reference on the transfer:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/edi/test/edi_partner_test.yml b/addons/edi/test/edi_partner_test.yml
new file mode 100644
index 00000000000..cca39417927
--- /dev/null
+++ b/addons/edi/test/edi_partner_test.yml
@@ -0,0 +1,44 @@
+-
+ In order to test the basic EDI system, I will export a partner,
+ modify the exported EDI document to add an attachment and change
+ the data, and then re-import it and re-export it.
+
+ with an attached file, check the result, the alter the data
+ and reimport it.
+-
+ !python {model: edi.document}: |
+ import json
+ partner_obj = self.pool.get('res.partner')
+ tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_agrolait'))])
+ doc = self.get_document(cr, uid, tokens[0], context=context)
+ edi_doc, = json.loads(doc)
+
+ # check content of the document
+ assert edi_doc.get('__id').endswith('.res_partner_agrolait'), 'Incorrect external ID'
+ assert edi_doc.get('__model') == 'res.partner', 'Incorrect/Missing __model'
+ assert edi_doc.get('__module') == 'base', 'Incorrect/Missing __module'
+ assert edi_doc.get('__last_update'), 'Missing __last_update'
+
+ # try to import the document after changing the name and id, and attaching a
+ # file, and check that a new partner is returned
+ edi_doc['__id'] = 'base:xxd37f8a-xx55-11e0-xxdd-xx81124c8b50.res_partner_xxx'
+ edi_doc['name'] = 'AgroMilk'
+ attachment = {
+ 'name': 'Test file',
+ 'file_name': 'test.png',
+ # base64 standard requires blocks of 57bytes=76chars, NL-separated
+ 'content': 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAKCAYAAACE2W/HAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIDQ84BkjLWAYAAACNSURBVCjP\njZKxDUJBDEOf/wbUbEHDhkxAzRjUDECBhMQYjIApuPBNpK+PpUinOPYll5Nt1iCJXifbSPpJZtHg\nXLUdu0FWpMEt87a/xtsmqjjNetPFyhuWYFuj7RcgQBNwXNGdw2CqY85yXWi5z7YBnmRyEHvg0YSX\nrLE9P30nwugOHHr+s5va4x+fofAGm1+JjnJICm0AAAAASUVORK5CYII=\n',
+ }
+ edi_doc['__attachments'] = [attachment]
+ doc = json.dumps([edi_doc])
+ result, = self.import_edi(cr, uid, edi_document=doc)
+ assert result[0] == 'res.partner' and result[1] > ref('base.res_partner_agrolait'),\
+ "Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_agrolait'), result)
+
+ # export the same partner we just created, and see if the output matches the input
+ tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])])
+ doc_output = self.get_document(cr, uid, tokens[0], context=context)
+ edi_doc_output, = json.loads(doc_output)
+ for attribute in ('__model', '__module', '__id', 'name', '__attachments'):
+ assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \
+ 'Incorrect value for %s, expected %r, got %r' % (attribute, edi_doc.get(attribute), edi_doc_output.get(attribute))
\ No newline at end of file
diff --git a/addons/email_template/__init__.py b/addons/email_template/__init__.py
index 97931ea8bd5..d1cf5b28431 100644
--- a/addons/email_template/__init__.py
+++ b/addons/email_template/__init__.py
@@ -21,5 +21,6 @@
##############################################################################
import email_template
import wizard
+import res_partner
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/__openerp__.py b/addons/email_template/__openerp__.py
index 06fa969c0c2..2ab7f194d18 100644
--- a/addons/email_template/__openerp__.py
+++ b/addons/email_template/__openerp__.py
@@ -61,9 +61,13 @@ Openlabs was kept
"data": [
'wizard/email_template_preview_view.xml',
'email_template_view.xml',
+ 'res_partner_view.xml',
'wizard/email_compose_message_view.xml',
'security/ir.model.access.csv'
],
+ "demo": [
+ 'res_partner_demo.yml',
+ ],
"installable": True,
"active": False,
"certificate" : "00817073628967384349",
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
index 3917b3f2ddb..237279c16aa 100644
--- a/addons/email_template/email_template.py
+++ b/addons/email_template/email_template.py
@@ -28,6 +28,7 @@ from osv import osv
from osv import fields
import tools
from tools.translate import _
+from urllib import quote as quote
try:
from mako.template import Template as MakoTemplate
@@ -66,6 +67,7 @@ class email_template(osv.osv):
user=user,
# context kw would clash with mako internals
ctx=context,
+ quote=quote,
format_exceptions=True)
if result == u'False':
result = u''
@@ -305,6 +307,7 @@ class email_template(osv.osv):
'attachment_ids': False,
'message_id': False,
'state': 'outgoing',
+ 'subtype': 'plain',
}
if not template_id:
return values
@@ -319,6 +322,9 @@ class email_template(osv.osv):
template.model, res_id, context=context) \
or False
+ if values['body_html']:
+ values.update(subtype='html')
+
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_text'] += '\n\n' + signature
@@ -355,20 +361,24 @@ class email_template(osv.osv):
values['attachments'] = attachments
return values
- def send_mail(self, cr, uid, template_id, res_id, context=None):
+ def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None):
"""Generates a new mail message for the given template and record,
- and schedule it for delivery through the ``mail`` module's scheduler.
+ and schedules it for delivery through the ``mail`` module's scheduler.
:param int template_id: id of the template to render
:param int res_id: id of the record to render the template with
(model is taken from the template)
+ :param bool force_send: if True, the generated mail.message is
+ immediately sent after being created, as if the scheduler
+ was executed for this message only.
+ :returns: id of the mail.message that was created
"""
mail_message = self.pool.get('mail.message')
ir_attachment = self.pool.get('ir.attachment')
template = self.browse(cr, uid, template_id, context)
values = self.generate_email(cr, uid, template_id, res_id, context=context)
attachments = values.pop('attachments') or {}
- message_id = mail_message.create(cr, uid, values, context=context)
+ msg_id = mail_message.create(cr, uid, values, context=context)
# link attachments
attachment_ids = []
for fname, fcontent in attachments.iteritems():
@@ -377,10 +387,13 @@ class email_template(osv.osv):
'datas_fname': fname,
'datas': fcontent,
'res_model': mail_message._name,
- 'res_id': message_id,
+ 'res_id': msg_id,
}
if context.has_key('default_type'):
del context['default_type']
- attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context))
+ attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
+ if force_send:
+ mail_message.send(cr, uid, [msg_id], context=context)
+ return msg_id
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml
index d6a651f0bc2..507dae3cd25 100644
--- a/addons/email_template/email_template_view.xml
+++ b/addons/email_template/email_template_view.xml
@@ -155,9 +155,8 @@
-
-
+
diff --git a/addons/email_template/res_partner.py b/addons/email_template/res_partner.py
new file mode 100644
index 00000000000..74d8b4e0693
--- /dev/null
+++ b/addons/email_template/res_partner.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2011 OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import fields,osv
+
+class res_partner(osv.osv):
+ """Inherit res.partner to add a generic opt-out field that can be used
+ to restrict usage of automatic email templates.
+ This field is unused by default. """
+ _inherit = 'res.partner'
+
+ _columns = {
+ 'opt_out': fields.boolean('Opt-out', help="If checked, this partner will not receive any automated email " \
+ "notifications, such as the availability of invoices."),
+ }
+
+ _defaults = {
+ 'opt_out': False,
+ }
\ No newline at end of file
diff --git a/addons/email_template/res_partner_demo.yml b/addons/email_template/res_partner_demo.yml
new file mode 100644
index 00000000000..8eaf48f9e87
--- /dev/null
+++ b/addons/email_template/res_partner_demo.yml
@@ -0,0 +1,9 @@
+-
+ Set opt-out to True on all demo partners
+-
+ !python {model: res.partner}: |
+ partner_ids = self.search(cr, uid, [])
+ # assume partners with an external ID come from demo data
+ ext_ids = self._get_external_ids(cr, uid, partner_ids)
+ ids_to_update = [k for (k,v) in ext_ids.iteritems() if v]
+ self.write(cr, uid, ids_to_update, {'opt_out': True})
\ No newline at end of file
diff --git a/addons/email_template/res_partner_view.xml b/addons/email_template/res_partner_view.xml
new file mode 100644
index 00000000000..0d2f919a96e
--- /dev/null
+++ b/addons/email_template/res_partner_view.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ res.partner.opt_out.form
+ res.partner
+ form
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/email_template/wizard/email_template_preview.py b/addons/email_template/wizard/email_template_preview.py
index 650b714866d..4ef0e9fa156 100644
--- a/addons/email_template/wizard/email_template_preview.py
+++ b/addons/email_template/wizard/email_template_preview.py
@@ -87,6 +87,8 @@ class email_template_preview(osv.osv_memory):
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
description += '\n' + signature
vals['body_text'] = description
+ if template.body_html:
+ vals['body_html'] = self.render_template(cr, uid, template.body_html, model, res_id, context) or ''
vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
return {'value': vals}
diff --git a/addons/event/board_association_view.xml b/addons/event/board_association_view.xml
index 78fc8395341..47c5e65f196 100644
--- a/addons/event/board_association_view.xml
+++ b/addons/event/board_association_view.xml
@@ -40,24 +40,20 @@
form
diff --git a/addons/event/event.py b/addons/event/event.py
index ba802ef1ffa..ab575408c63 100644
--- a/addons/event/event.py
+++ b/addons/event/event.py
@@ -296,6 +296,7 @@ class event_registration(osv.osv):
return res
_columns = {
+ 'id': fields.integer('ID'),
'name': fields.char('Summary', size=124, readonly=True, states={'draft': [('readonly', False)]}),
'email_cc': fields.text('CC', size=252, readonly=False, states={'done': [('readonly', True)]}, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
'nb_register': fields.integer('Quantity', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Number of Registrations or Tickets"),
diff --git a/addons/event/report/report_event_registration_view.xml b/addons/event/report/report_event_registration_view.xml
index 3fd24c0a366..ef69c674e43 100644
--- a/addons/event/report/report_event_registration_view.xml
+++ b/addons/event/report/report_event_registration_view.xml
@@ -53,10 +53,11 @@
-
-
+ formtree,graph
- {"search_default_365day":1, "search_default_invoiced":1, "search_default_event":1, 'group_by_no_leaf':1, 'group_by':[]}
+ {"search_default_year":1,"search_default_this_month":1,"search_default_365day":1, "search_default_invoiced":1, "search_default_event":1, 'group_by_no_leaf':1, 'group_by':[]}
diff --git a/addons/hr/hr_board.xml b/addons/hr/hr_board.xml
index 874b0d368d6..620f1f51c88 100644
--- a/addons/hr/hr_board.xml
+++ b/addons/hr/hr_board.xml
@@ -8,12 +8,12 @@
form
@@ -27,7 +27,7 @@
-
+
@@ -38,12 +38,12 @@
form
diff --git a/addons/hr/hr_demo.xml b/addons/hr/hr_demo.xml
index dbfead793e5..15269a88375 100644
--- a/addons/hr/hr_demo.xml
+++ b/addons/hr/hr_demo.xml
@@ -54,17 +54,40 @@
-
- Quentin Paolino
-
-
-
-
-
Antoine Philippe
+
+
+
+
+
+ Quentin Paolino
+
+
+
+
+
+
+
+ John Smith
+
+
+
+
+
+
+ Thomas McQueen
+
+
+
+
+
+
+ Vanessa Dupont
+
+
diff --git a/addons/hr/hr_department_demo.xml b/addons/hr/hr_department_demo.xml
index 433e77a6dd4..5ac57db52a3 100644
--- a/addons/hr/hr_department_demo.xml
+++ b/addons/hr/hr_department_demo.xml
@@ -26,31 +26,34 @@
IT
+ Marketing
- Sales
+ Sales Europe
+ Sales America
+
@@ -106,7 +109,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml
index 8b16022ec91..bbe2d00ff6f 100644
--- a/addons/hr/hr_view.xml
+++ b/addons/hr/hr_view.xml
@@ -344,6 +344,7 @@
hr.employeeformtree,form
+ [('category_ids','=', active_ids)]
@@ -351,7 +352,6 @@
Employees by Categories
-