From f08bf5a08e67af086a26b51e67c69bd253155a38 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 4 Mar 2013 14:53:29 +0100 Subject: [PATCH 01/43] [FIX] Generates journal items immediately from expense bzr revid: jco@openerp.com-20130304135329-v1dfw564svd0zv7q --- addons/hr_expense/hr_expense.py | 301 ++++++++++++++++++++++ addons/hr_expense/hr_expense_workflow.xml | 2 +- 2 files changed, 302 insertions(+), 1 deletion(-) diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index f32479c2ae2..03475c90b04 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -145,6 +145,307 @@ class hr_expense_expense(osv.osv): def expense_canceled(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context) + + def account_move_get(self, cr, uid, expense_id, journal_id, context=None): + ''' + This method prepare the creation of the account move related to the given expense. + + :param expense_id: Id of voucher for which we are creating account_move. + :return: mapping between fieldname and value of account move to create + :rtype: dict + ''' + + + #Search for the period corresponding with confirmation date + expense_brw = self.browse(cr,uid,expense_id,context) + period_obj = self.pool.get('account.period') + company_id = expense_brw.company_id.id + ctx = context + ctx.update({'company_id': company_id}) + date = expense_brw.date_confirm + pids = period_obj.find(cr, uid, date, context=ctx) + try: + period_id = pids[0] + except: + raise osv.except_osv(_('Error! '), + _('Please define periods!')) + period = period_obj.browse(cr, uid, period_id, context=context) + + seq_obj = self.pool.get('ir.sequence') + + journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context) + if journal.sequence_id: + if not journal.sequence_id.active: + raise osv.except_osv(_('Configuration Error !'), + _('Please activate the sequence of selected journal !')) + c = dict(context) + c.update({'fiscalyear_id': period.fiscalyear_id.id}) + name = seq_obj.next_by_id(cr, uid, journal.sequence_id.id, context=c) + else: + raise osv.except_osv(_('Error!'), + _('Please define a sequence on the journal.')) + #Look for the next expense number + ref = seq_obj.get(cr, uid, 'hr.expense.invoice') + + move = { + 'name': name, + 'journal_id': journal_id, + 'narration': '', + 'date': expense_brw.date_confirm, + 'ref': ref, + 'period_id': period_id, + } + return move + + def line_get_convert(self, cr, uid, x, part, date, context=None): + return { + 'date_maturity': x.get('date_maturity', False), + 'partner_id': part.id, + 'name': x['name'][:64], + 'date': date, + 'debit': x['price']>0 and x['price'], + 'credit': x['price']<0 and -x['price'], + 'account_id': x['account_id'], + 'analytic_lines': x.get('analytic_lines', False), + 'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)), + 'currency_id': x.get('currency_id', False), + 'tax_code_id': x.get('tax_code_id', False), + 'tax_amount': x.get('tax_amount', False), + 'ref': x.get('ref', False), + 'quantity': x.get('quantity',1.00), + 'product_id': x.get('product_id', False), + 'product_uom_id': x.get('uos_id', False), + 'analytic_account_id': x.get('account_analytic_id', False), + } + + def compute_expense_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines, context=None): + if context is None: + context={} + total = 0 + total_currency = 0 + cur_obj = self.pool.get('res.currency') + for i in invoice_move_lines: + if inv.currency_id.id != company_currency: + context.update({'date': inv.date_confirm or time.strftime('%Y-%m-%d')}) + i['currency_id'] = inv.currency_id.id + i['amount_currency'] = i['price'] + i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id, + company_currency, i['price'], + context=context) + else: + i['amount_currency'] = False + i['currency_id'] = False + i['ref'] = ref + total -= i['price'] + total_currency -= i['amount_currency'] or i['price'] + return total, total_currency, invoice_move_lines + + + def action_move_create(self, cr, uid, ids, context=None): + property_obj = self.pool.get('ir.property') + sequence_obj = self.pool.get('ir.sequence') + analytic_journal_obj = self.pool.get('account.analytic.journal') + account_journal = self.pool.get('account.journal') + voucher_obj = self.pool.get('account.voucher') + currency_obj = self.pool.get('res.currency') + ait_obj = self.pool.get('account.invoice.tax') + move_obj = self.pool.get('account.move') + if context is None: + context = {} + for exp in self.browse(cr, uid, ids, context=context): + company_id = exp.company_id.id + lines = [] + total = 0.0 + ctx = context.copy() + ctx.update({'date': exp.date}) + journal = False + if exp.journal_id: + journal = exp.journal_id + else: + journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id}) + if journal_id: + journal = account_journal.browse(cr, uid, journal_id, context=context) + if not journal: + raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured.")) + if not journal.sequence_id: + raise osv.except_osv(_('Error!'), _('Please define sequence on the journal related to this invoice.')) + company_currency = exp.company_id.currency_id.id + current_currency = exp.currency_id +# for line in exp.line_ids: +# if line.product_id: +# acc = line.product_id.property_account_expense +# if not acc: +# acc = line.product_id.categ_id.property_account_expense_categ +# else: +# acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id}) +# if not acc: +# raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.')) +# total_amount = line.total_amount +# if journal.currency: +# if exp.currency_id != journal.currency: +# total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx) +# elif exp.currency_id != exp.company_id.currency_id: +# total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx) +# lines.append((0, False, { +# 'name': line.name, +# 'account_id': acc.id, +# 'account_analytic_id': line.analytic_account.id, +# 'amount': total_amount, +# 'type': 'dr' +# })) +# total += total_amount + if not exp.employee_id.address_home_id: + raise osv.except_osv(_('Error!'), _('The employee must have a home address.')) + acc = exp.employee_id.address_home_id.property_account_payable.id + + #From action_move_line_create of voucher + move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, journal.id, context=context), context=context) + move = move_obj.browse(cr, uid, move_id, context=context) + #iml = self._get_analytic_lines + + # within: iml = self.pool.get('account.invoice.line').move_line_get + iml = self.move_line_get(cr, uid, exp.id, context=context) + + #Write taxes on the lines which should automatically add the necessary extra lines upon creation + + + + diff_currency_p = exp.currency_id.id <> company_currency + # create one move line for the total + total = 0 + total_currency = 0 + total, total_currency, iml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, iml, context=ctx) + + + #Need to have counterline: + + iml.append({ + 'type':'dest', + 'name':'/', + 'price':total, + 'account_id': acc, + 'date_maturity': exp.date_confirm, + 'amount_currency': diff_currency_p and total_currency or False, + 'currency_id': diff_currency_p and exp.currency_id.id or False, + 'ref': exp.name + }) + + line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.user_id.partner_id, exp.date_confirm, context=ctx)),iml) + move_obj.write(cr, uid, [move_id], {'line_id':line}, context=ctx) + self.write(cr, uid, ids, {'account_move_id':move_id, 'state':'done'}, context=context) + + + #compute_taxes = ait_obj.compute(cr, uid, , context=context) + + + +# return { +# 'type':'src', +# 'name':line.name.split('\n')[0][:64], +# 'price_unit':line.price_unit, +# 'quantity':line.quantity, +# 'price':line.price_subtotal, +# 'account_id':line.account_id.id, +# 'product_id':line.product_id.id, +# 'uos_id':line.uos_id.id, +# 'account_analytic_id':line.account_analytic_id.id, +# 'taxes':line.invoice_line_tax_id} + + + def move_line_get(self, cr, uid, expense_id, context=None): + res = [] + tax_obj = self.pool.get('account.tax') + cur_obj = self.pool.get('res.currency') + if context is None: + context = {} + exp = self.browse(cr, uid, expense_id, context=context) + company_currency = exp.company_id.currency_id.id + + for line in exp.line_ids: + mres = self.move_line_get_item(cr, uid, line, context) + if not mres: + continue + res.append(mres) + tax_code_found= False + + #Calculate tax according to default tax on product + + #Taken from product_id_onchange in account.invoice + if line.product_id: + fposition_id = False + fpos_obj = self.pool.get('account.fiscal.position') + fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False + product = line.product_id + taxes = product.supplier_taxes_id + #If taxes are not related to the product, maybe they are in the account + if not taxes: + a = product.property_account_expense.id #Why is not there a check here? + if not a: + a = product.categ_id.property_account_expense_categ.id + a = fpos_obj.map_account(cr, uid, fpos, a) + taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False + tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) + #Calculating tax on the line and creating move? + for tax in tax_obj.compute_all(cr, uid, taxes, + line.unit_amount , + line.unit_quantity, line.product_id, + exp.user_id.partner_id)['taxes']: + tax_code_id = tax['base_code_id'] + tax_amount = line.total_amount * tax['base_sign'] + if tax_code_found: + if not tax_code_id: + continue + res.append(self.move_line_get_item(cr, uid, line, context)) + res[-1]['price'] = 0.0 + res[-1]['account_analytic_id'] = False + elif not tax_code_id: + continue + tax_code_found = True + res[-1]['tax_code_id'] = tax_code_id + res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm}) + + #Will create the tax here as we don't have the access + assoc_tax = { + 'type':'tax', + 'name':tax['name'], + 'price_unit': tax['price_unit'], + 'quantity': 1, + 'price': tax['amount'] * tax['base_sign'] or 0.0, + 'account_id': tax['account_collected_id'], + 'tax_code_id': tax['tax_code_id'], + 'tax_amount': tax['amount'] * tax['base_sign'], + } + res.append(assoc_tax) + return res + + def move_line_get_item(self, cr, uid, line, context=None): + company = line.expense_id.company_id + property_obj = self.pool.get('ir.property') + if line.product_id: + acc = line.product_id.property_account_expense + if not acc: + acc = line.product_id.categ_id.property_account_expense_categ + else: + acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id}) + if not acc: + raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.')) + return { + 'type':'src', + 'name': line.name.split('\n')[0][:64], + 'price_unit':line.unit_amount, + 'quantity':line.unit_quantity, + 'price':line.total_amount, + 'account_id':acc.id, + 'product_id':line.product_id.id, + 'uos_id':line.uom_id.id, + 'account_analytic_id':line.analytic_account.id, + #'taxes':line.invoice_line_tax_id, + } + + + + + def action_receipt_create(self, cr, uid, ids, context=None): property_obj = self.pool.get('ir.property') sequence_obj = self.pool.get('ir.sequence') diff --git a/addons/hr_expense/hr_expense_workflow.xml b/addons/hr_expense/hr_expense_workflow.xml index 2e085071207..c01bb4cd14b 100644 --- a/addons/hr_expense/hr_expense_workflow.xml +++ b/addons/hr_expense/hr_expense_workflow.xml @@ -43,7 +43,7 @@ done function - action_receipt_create() + action_move_create() From 1e3725b9e6cc0f167dd3000844dc3d13e64dff98 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 4 Mar 2013 15:47:22 +0100 Subject: [PATCH 02/43] [FIX] adjust tests and no taxes when product not available bzr revid: jco@openerp.com-20130304144722-y30nkmmd8hyby7x0 --- addons/hr_expense/hr_expense.py | 2 ++ addons/hr_expense/test/expense_process.yml | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index 03475c90b04..fb8af6649de 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -385,6 +385,8 @@ class hr_expense_expense(osv.osv): a = fpos_obj.map_account(cr, uid, fpos, a) taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) + else: + taxes = [] #Calculating tax on the line and creating move? for tax in tax_obj.compute_all(cr, uid, taxes, line.unit_amount , diff --git a/addons/hr_expense/test/expense_process.yml b/addons/hr_expense/test/expense_process.yml index eae63c460fc..4b65c16e8ea 100644 --- a/addons/hr_expense/test/expense_process.yml +++ b/addons/hr_expense/test/expense_process.yml @@ -26,11 +26,6 @@ !python {model: hr.expense.expense}: | sep_expenses = self.browse(cr, uid, ref("sep_expenses"), context=context) assert sep_expenses.state == 'done', "Expense should be in 'Done' state." - assert sep_expenses.voucher_id, "Expense should have link of Purchase Receipt." - assert sep_expenses.voucher_id.type == 'purchase', "Receipt type is not purchase receipt." - assert sep_expenses.voucher_id.amount == sep_expenses.amount,"Receipt total amount is not correspond with expense total." - assert len(sep_expenses.voucher_id.line_dr_ids) == len(sep_expenses.line_ids),"Lines of Receipt and expense line are not correspond." - - I duplicate the expenses and cancel duplicated. - From 804bc3a916916eb5c32ef44e8bc5b5f37c89078d Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 4 Mar 2013 16:55:50 +0100 Subject: [PATCH 03/43] [FIX] in case of no taxes bzr revid: jco@openerp.com-20130304155550-g5m12ryt6id0b3m3 --- addons/hr_expense/hr_expense.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index fb8af6649de..f61f8923411 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -306,9 +306,6 @@ class hr_expense_expense(osv.osv): # within: iml = self.pool.get('account.invoice.line').move_line_get iml = self.move_line_get(cr, uid, exp.id, context=context) - #Write taxes on the lines which should automatically add the necessary extra lines upon creation - - diff_currency_p = exp.currency_id.id <> company_currency # create one move line for the total @@ -385,7 +382,7 @@ class hr_expense_expense(osv.osv): a = fpos_obj.map_account(cr, uid, fpos, a) taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) - else: + if not taxes: taxes = [] #Calculating tax on the line and creating move? for tax in tax_obj.compute_all(cr, uid, taxes, From cdabf79cf27a08f8ecb63a0a94e7d13b9f05fc4e Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 6 Mar 2013 15:05:54 +0100 Subject: [PATCH 04/43] [REF] hr_expense, creation of accounting entries from hr.expense: a lot of code refactoring. Still a huge work to be done in order to factorize some code with other objects (like account.invoice, account.voucher, account.asset...) bzr revid: qdp-launchpad@openerp.com-20130306140554-fhs6fhkyb779t6re --- addons/account/account.py | 23 +++ addons/hr_expense/__openerp__.py | 2 +- addons/hr_expense/hr_expense.py | 264 ++++++-------------------- addons/hr_expense/hr_expense_view.xml | 1 + 4 files changed, 79 insertions(+), 211 deletions(-) diff --git a/addons/account/account.py b/addons/account/account.py index acf27c42392..2aab34ff8ba 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1150,6 +1150,29 @@ class account_move(osv.osv): _description = "Account Entry" _order = 'id desc' + def account_move_prepare(self, cr, uid, journal_id, date=False, ref='', company_id=False, context=None): + ''' + Prepares and returns a dictionary of values, ready to be passed to create() based on the parameters received. + ''' + if not date: + date = fields.date.today() + period_obj = self.pool.get('account.period') + if not company_id: + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + company_id = user.company_id.id + if context is None: + context = {} + #put the company in context to find the good period + ctx = context.copy() + ctx.update({'company_id': company_id}) + return { + 'journal_id': journal_id, + 'date': date, + 'period_id': period_obj.find(cr, uid, date, context=ctx)[0], + 'ref': ref, + 'company_id': company_id, + } + def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): """ Returns a list of tupples containing id, name, as internally it is called {def name_get} diff --git a/addons/hr_expense/__openerp__.py b/addons/hr_expense/__openerp__.py index 516af7f9d27..1f4e7b42209 100644 --- a/addons/hr_expense/__openerp__.py +++ b/addons/hr_expense/__openerp__.py @@ -46,7 +46,7 @@ This module also uses analytic accounting and is compatible with the invoice on 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', 'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'], - 'depends': ['hr', 'account_voucher'], + 'depends': ['hr', 'account_voucher, account_accountant'], 'data': [ 'security/ir.model.access.csv', 'hr_expense_data.xml', diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index f61f8923411..29570559c65 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -145,8 +145,7 @@ class hr_expense_expense(osv.osv): def expense_canceled(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context) - - def account_move_get(self, cr, uid, expense_id, journal_id, context=None): + def account_move_get(self, cr, uid, expense_id, context=None): ''' This method prepare the creation of the account move related to the given expense. @@ -154,53 +153,27 @@ class hr_expense_expense(osv.osv): :return: mapping between fieldname and value of account move to create :rtype: dict ''' - - - #Search for the period corresponding with confirmation date - expense_brw = self.browse(cr,uid,expense_id,context) - period_obj = self.pool.get('account.period') - company_id = expense_brw.company_id.id - ctx = context - ctx.update({'company_id': company_id}) - date = expense_brw.date_confirm - pids = period_obj.find(cr, uid, date, context=ctx) - try: - period_id = pids[0] - except: - raise osv.except_osv(_('Error! '), - _('Please define periods!')) - period = period_obj.browse(cr, uid, period_id, context=context) - - seq_obj = self.pool.get('ir.sequence') - - journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context) - if journal.sequence_id: - if not journal.sequence_id.active: - raise osv.except_osv(_('Configuration Error !'), - _('Please activate the sequence of selected journal !')) - c = dict(context) - c.update({'fiscalyear_id': period.fiscalyear_id.id}) - name = seq_obj.next_by_id(cr, uid, journal.sequence_id.id, context=c) + journal_obj = self.pool.get('account.journal') + expense = self.browse(cr, uid, expense_id, context=context) + company_id = expense.company_id.id + date = expense.date_confirm + ref = expense.name + journal_id = False + if expense.journal_id: + journal_id = expense.journal_id.id else: - raise osv.except_osv(_('Error!'), - _('Please define a sequence on the journal.')) - #Look for the next expense number - ref = seq_obj.get(cr, uid, 'hr.expense.invoice') - - move = { - 'name': name, - 'journal_id': journal_id, - 'narration': '', - 'date': expense_brw.date_confirm, - 'ref': ref, - 'period_id': period_id, - } - return move + journal_id = journal_obj.search(cr, uid, [('type', '=', 'purchase'), ('company_id', '=', company_id)]) + if not journal_id: + raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured.")) + journal_id = journal_id[0] + return self.pool.get('account_move').account_move_prepare(cr, uid, journal_id, date=date, ref=ref, company_id=company_id, context=context) def line_get_convert(self, cr, uid, x, part, date, context=None): + #partner_id = self.pool.get('res.partner')._find_partner(part) + partner_id = part.id return { 'date_maturity': x.get('date_maturity', False), - 'partner_id': part.id, + 'partner_id': partner_id, 'name': x['name'][:64], 'date': date, 'debit': x['price']>0 and x['price'], @@ -218,108 +191,65 @@ class hr_expense_expense(osv.osv): 'analytic_account_id': x.get('account_analytic_id', False), } - def compute_expense_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines, context=None): + def compute_expense_totals(self, cr, uid, exp, company_currency, ref, account_move_lines, context=None): + ''' + internal method used for computation of total amount of an expense in the company currency and + in the expense currency, given the account_move_lines that will be created. It also do some small + transformations at these account_move_lines (for multi-currency purposes) + + :param account_move_lines: list of dict + :rtype: tuple of 3 elements (a, b ,c) + a: total in company currency + b: total in hr.expense currency + c: account_move_lines potentially modified + ''' + cur_obj = self.pool.get('res.currency') if context is None: context={} - total = 0 - total_currency = 0 - cur_obj = self.pool.get('res.currency') - for i in invoice_move_lines: - if inv.currency_id.id != company_currency: - context.update({'date': inv.date_confirm or time.strftime('%Y-%m-%d')}) - i['currency_id'] = inv.currency_id.id + context.update({'date': exp.date_confirm or time.strftime('%Y-%m-%d')}) + total = 0.0 + total_currency = 0.0 + for i in account_move_lines: + if exp.currency_id.id != company_currency: + i['currency_id'] = exp.currency_id.id i['amount_currency'] = i['price'] - i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id, + i['price'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, i['price'], context=context) else: i['amount_currency'] = False i['currency_id'] = False - i['ref'] = ref total -= i['price'] total_currency -= i['amount_currency'] or i['price'] - return total, total_currency, invoice_move_lines + return total, total_currency, account_move_lines def action_move_create(self, cr, uid, ids, context=None): - property_obj = self.pool.get('ir.property') - sequence_obj = self.pool.get('ir.sequence') - analytic_journal_obj = self.pool.get('account.analytic.journal') - account_journal = self.pool.get('account.journal') - voucher_obj = self.pool.get('account.voucher') - currency_obj = self.pool.get('res.currency') - ait_obj = self.pool.get('account.invoice.tax') move_obj = self.pool.get('account.move') if context is None: context = {} for exp in self.browse(cr, uid, ids, context=context): - company_id = exp.company_id.id - lines = [] - total = 0.0 - ctx = context.copy() - ctx.update({'date': exp.date}) - journal = False - if exp.journal_id: - journal = exp.journal_id - else: - journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id}) - if journal_id: - journal = account_journal.browse(cr, uid, journal_id, context=context) - if not journal: - raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured.")) - if not journal.sequence_id: - raise osv.except_osv(_('Error!'), _('Please define sequence on the journal related to this invoice.')) - company_currency = exp.company_id.currency_id.id - current_currency = exp.currency_id -# for line in exp.line_ids: -# if line.product_id: -# acc = line.product_id.property_account_expense -# if not acc: -# acc = line.product_id.categ_id.property_account_expense_categ -# else: -# acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id}) -# if not acc: -# raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.')) -# total_amount = line.total_amount -# if journal.currency: -# if exp.currency_id != journal.currency: -# total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx) -# elif exp.currency_id != exp.company_id.currency_id: -# total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx) -# lines.append((0, False, { -# 'name': line.name, -# 'account_id': acc.id, -# 'account_analytic_id': line.analytic_account.id, -# 'amount': total_amount, -# 'type': 'dr' -# })) -# total += total_amount if not exp.employee_id.address_home_id: raise osv.except_osv(_('Error!'), _('The employee must have a home address.')) - acc = exp.employee_id.address_home_id.property_account_payable.id + company_currency = exp.company_id.currency_id.id + diff_currency_p = exp.currency_id.id <> company_currency - #From action_move_line_create of voucher - move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, journal.id, context=context), context=context) - move = move_obj.browse(cr, uid, move_id, context=context) + #create the move that will contain the accounting entries + move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, context=context), context=context) #iml = self._get_analytic_lines # within: iml = self.pool.get('account.invoice.line').move_line_get iml = self.move_line_get(cr, uid, exp.id, context=context) - - diff_currency_p = exp.currency_id.id <> company_currency # create one move line for the total - total = 0 - total_currency = 0 - total, total_currency, iml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, iml, context=ctx) - - - #Need to have counterline: + total, total_currency, iml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, iml, context=context) + #counterline with the total on payable account for the employee + acc = exp.employee_id.address_home_id.property_account_payable.id iml.append({ - 'type':'dest', - 'name':'/', - 'price':total, + 'type': 'dest', + 'name': '/', + 'price': total, 'account_id': acc, 'date_maturity': exp.date_confirm, 'amount_currency': diff_currency_p and total_currency or False, @@ -327,27 +257,10 @@ class hr_expense_expense(osv.osv): 'ref': exp.name }) - line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.user_id.partner_id, exp.date_confirm, context=ctx)),iml) - move_obj.write(cr, uid, [move_id], {'line_id':line}, context=ctx) - self.write(cr, uid, ids, {'account_move_id':move_id, 'state':'done'}, context=context) - - - #compute_taxes = ait_obj.compute(cr, uid, , context=context) - - - -# return { -# 'type':'src', -# 'name':line.name.split('\n')[0][:64], -# 'price_unit':line.price_unit, -# 'quantity':line.quantity, -# 'price':line.price_subtotal, -# 'account_id':line.account_id.id, -# 'product_id':line.product_id.id, -# 'uos_id':line.uos_id.id, -# 'account_analytic_id':line.account_analytic_id.id, -# 'taxes':line.invoice_line_tax_id} - + lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.user_id.partner_id, exp.date_confirm, context=context)),iml) + move_obj.write(cr, uid, [move_id], {'line_id': lines}, context=context) + self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context) + return True def move_line_get(self, cr, uid, expense_id, context=None): res = [] @@ -442,80 +355,11 @@ class hr_expense_expense(osv.osv): } - - - def action_receipt_create(self, cr, uid, ids, context=None): - property_obj = self.pool.get('ir.property') - sequence_obj = self.pool.get('ir.sequence') - analytic_journal_obj = self.pool.get('account.analytic.journal') - account_journal = self.pool.get('account.journal') - voucher_obj = self.pool.get('account.voucher') - currency_obj = self.pool.get('res.currency') - wkf_service = netsvc.LocalService("workflow") - if context is None: - context = {} - for exp in self.browse(cr, uid, ids, context=context): - company_id = exp.company_id.id - lines = [] - total = 0.0 - ctx = context.copy() - ctx.update({'date': exp.date}) - journal = False - if exp.journal_id: - journal = exp.journal_id - else: - journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id}) - if journal_id: - journal = account_journal.browse(cr, uid, journal_id, context=context) - if not journal: - raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured.")) - for line in exp.line_ids: - if line.product_id: - acc = line.product_id.property_account_expense - if not acc: - acc = line.product_id.categ_id.property_account_expense_categ - else: - acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id}) - if not acc: - raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.')) - total_amount = line.total_amount - if journal.currency: - if exp.currency_id != journal.currency: - total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx) - elif exp.currency_id != exp.company_id.currency_id: - total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx) - lines.append((0, False, { - 'name': line.name, - 'account_id': acc.id, - 'account_analytic_id': line.analytic_account.id, - 'amount': total_amount, - 'type': 'dr' - })) - total += total_amount - if not exp.employee_id.address_home_id: - raise osv.except_osv(_('Error!'), _('The employee must have a home address.')) - acc = exp.employee_id.address_home_id.property_account_payable.id - voucher = { - 'name': exp.name or '/', - 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'), - 'account_id': acc, - 'type': 'purchase', - 'partner_id': exp.employee_id.address_home_id.id, - 'company_id': company_id, - 'line_ids': lines, - 'amount': total, - 'journal_id': journal.id, - } - if journal and not journal.analytic_journal_id: - analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context) - if analytic_journal_ids: - account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context) - voucher_id = voucher_obj.create(cr, uid, voucher, context=context) - self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context) - return True + raise osv.except_osv(_('Error!'), _('Deprecated function used')) def action_view_receipt(self, cr, uid, ids, context=None): + raise osv.except_osv(_('Error!'), _('Deprecated function used')) ''' This function returns an action that display existing receipt of given expense ids. ''' diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 0324388786f..cb1a7408a29 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -133,6 +133,7 @@ + From d448390cafeeeadc470d1c84d77252e53483bf50 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 6 Mar 2013 15:35:34 +0100 Subject: [PATCH 05/43] [FIX] hr_expense: mising coma in manifest bzr revid: qdp-launchpad@openerp.com-20130306143534-30vh1xl79eo6bf5c --- addons/hr_expense/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_expense/__openerp__.py b/addons/hr_expense/__openerp__.py index 1f4e7b42209..8eb7de957f8 100644 --- a/addons/hr_expense/__openerp__.py +++ b/addons/hr_expense/__openerp__.py @@ -46,7 +46,7 @@ This module also uses analytic accounting and is compatible with the invoice on 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', 'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'], - 'depends': ['hr', 'account_voucher, account_accountant'], + 'depends': ['hr', 'account_voucher', 'account_accountant'], 'data': [ 'security/ir.model.access.csv', 'hr_expense_data.xml', From ee72f0953aec63c1f5f5444541a9075e0f3c9c43 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 6 Mar 2013 15:38:37 +0100 Subject: [PATCH 06/43] [FIX] hr_expense: typo bzr revid: qdp-launchpad@openerp.com-20130306143837-gc7edztg71h5hi56 --- addons/hr_expense/hr_expense.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index 29570559c65..9196cba3d96 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -166,7 +166,7 @@ class hr_expense_expense(osv.osv): if not journal_id: raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured.")) journal_id = journal_id[0] - return self.pool.get('account_move').account_move_prepare(cr, uid, journal_id, date=date, ref=ref, company_id=company_id, context=context) + return self.pool.get('account.move').account_move_prepare(cr, uid, journal_id, date=date, ref=ref, company_id=company_id, context=context) def line_get_convert(self, cr, uid, x, part, date, context=None): #partner_id = self.pool.get('res.partner')._find_partner(part) From fd54f58ccb7802af3aa8eff580affb29dceb9874 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 6 Mar 2013 17:39:31 +0100 Subject: [PATCH 07/43] [IMP] hr_expense: now propose to open the account.move in form view thanks to a button in the expense form view bzr revid: qdp-launchpad@openerp.com-20130306163931-609cgn16xmcr0wot --- addons/hr_expense/hr_expense.py | 25 +++++++++++++------------ addons/hr_expense/hr_expense_view.xml | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py index 9196cba3d96..cd11d19ff17 100644 --- a/addons/hr_expense/hr_expense.py +++ b/addons/hr_expense/hr_expense.py @@ -145,6 +145,9 @@ class hr_expense_expense(osv.osv): def expense_canceled(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context) + def action_receipt_create(self, cr, uid, ids, context=None): + raise osv.except_osv(_('Error!'), _('Deprecated function used')) + def account_move_get(self, cr, uid, expense_id, context=None): ''' This method prepare the creation of the account move related to the given expense. @@ -354,28 +357,26 @@ class hr_expense_expense(osv.osv): #'taxes':line.invoice_line_tax_id, } - - def action_receipt_create(self, cr, uid, ids, context=None): - raise osv.except_osv(_('Error!'), _('Deprecated function used')) - - def action_view_receipt(self, cr, uid, ids, context=None): - raise osv.except_osv(_('Error!'), _('Deprecated function used')) + def action_view_move(self, cr, uid, ids, context=None): ''' - This function returns an action that display existing receipt of given expense ids. + This function returns an action that display existing account.move of given expense ids. ''' assert len(ids) == 1, 'This option should only be used for a single id at a time' voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id - res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form') + try: + dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'view_move_form') + except: + view_id = False result = { - 'name': _('Expense Receipt'), + 'name': _('Expense Account Move'), 'view_type': 'form', 'view_mode': 'form', - 'view_id': res and res[1] or False, - 'res_model': 'account.voucher', + 'view_id': view_id, + 'res_model': 'account.move', 'type': 'ir.actions.act_window', 'nodestroy': True, 'target': 'current', - 'res_id': voucher_id, + 'res_id': account_move_id, } return result diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index cb1a7408a29..3ee97ba3a0e 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -67,7 +67,7 @@ - File + /web/binary/upload_attachment From 22bba15ccd7ab709db9ed2c38a132273c9847e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 8 Mar 2013 13:43:41 +0100 Subject: [PATCH 17/43] [FIX] email_template: on_change values for m2m can be list of ids, not necessarily commands. bzr revid: tde@openerp.com-20130308124341-ih0f4us81cffer7m --- addons/email_template/wizard/mail_compose_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index e9f539a22b2..9dad18e07f3 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -141,7 +141,7 @@ class mail_compose_message(osv.TransientModel): # legacy template behavior: void values do not erase existing values and the # related key is removed from the values dict if partner_ids: - values['partner_ids'] = [(6, 0, list(partner_ids))] + values['partner_ids'] = list(partner_ids) return values From 6dbb52267f514055f013207f7522f9d49dfd6110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 8 Mar 2013 13:47:24 +0100 Subject: [PATCH 18/43] [FIX] res_partner: notification_email_send help and selection values strings updated for cleaner explanations. bzr revid: tde@openerp.com-20130308124724-6tmb4w180sf32ktb --- addons/mail/res_partner.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/addons/mail/res_partner.py b/addons/mail/res_partner.py index 218b5ae3cb7..6807c13a08a 100644 --- a/addons/mail/res_partner.py +++ b/addons/mail/res_partner.py @@ -30,13 +30,16 @@ class res_partner_mail(osv.Model): _columns = { 'notification_email_send': fields.selection([ - ('all', 'All feeds'), - ('comment', 'Comments and Emails'), - ('email', 'Emails only'), + ('all', 'All Messages (discussions, emails, followed system notifications)'), + ('comment', 'Incoming Emails and Discussions'), + ('email', 'Incoming Emails only'), ('none', 'Never') - ], 'Receive Feeds by Email', required=True, - help="Choose in which case you want to receive an email when you "\ - "receive new feeds."), + ], 'Receive Messages by Email', required=True, + help="Policy to receive emails for new notifications pushed to your Inbox. "\ + "'Never' prevents OpenERP from sending emails for notifications. "\ + "'Incoming Emails only' send an email when you receive an incoming email, such as customer replies. "\ + "'Incoming Emails and Discussions' sends emails for incoming emails along with user input. "\ + "'All Messages' also sends emails for notifications received for followed subtype, like stage changes, in addition to Incoming Emails and Discussions."), } _defaults = { From d130a05e8cf147ea983c42de588d3ac9c66e319a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 8 Mar 2013 13:47:53 +0100 Subject: [PATCH 19/43] [FIX] crm, email_template: opt_out parameter used only for mass mailing and marketing campaigns. Updated help accordingly. bzr revid: tde@openerp.com-20130308124753-dzt5e5nf7zpaq2dk --- addons/crm/crm_lead.py | 2 +- addons/email_template/res_partner.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index e8bba67e1ce..9b3697b8dc5 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -259,7 +259,7 @@ class crm_lead(base_stage, format_address, osv.osv): 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"), 'contact_name': fields.char('Contact Name', size=64), 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1), - 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."), + 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign."), 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), 'date_closed': fields.datetime('Closed', readonly=True), diff --git a/addons/email_template/res_partner.py b/addons/email_template/res_partner.py index 987fca43219..bf730f37458 100644 --- a/addons/email_template/res_partner.py +++ b/addons/email_template/res_partner.py @@ -28,8 +28,8 @@ class res_partner(osv.osv): _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."), + 'opt_out': fields.boolean('Opt-Out', + help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign."), } _defaults = { From 6d78c2f2349ea64d5248644c559c34a0bdccd7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 8 Mar 2013 13:48:35 +0100 Subject: [PATCH 20/43] [FIX] res_partner: mail now adds forgotten notification_email_send field; otherwise partner manager are not able to change the email reception policy for partners that are not users. bzr revid: tde@openerp.com-20130308124835-sooyabgjhawjg7qr --- addons/mail/res_partner_view.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml index a4aa56ae971..7df5d27c9d0 100644 --- a/addons/mail/res_partner_view.xml +++ b/addons/mail/res_partner_view.xml @@ -7,6 +7,9 @@ res.partner + + +
From 16a89075e0cbfcb42bfe0e8c6ebbac2012b1097e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 8 Mar 2013 14:14:52 +0100 Subject: [PATCH 21/43] [FIX] FieldTextHtml: fixed quote error in parameter of cleditor, leading to font-family not taken into account. bzr revid: tde@openerp.com-20130308131452-8mmmpc6was3lfse8 --- addons/web/static/src/js/view_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 3ca546a9754..c8f7eaf032e 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2662,7 +2662,7 @@ instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instanc "| removeformat | bullets numbering | outdent " + "indent | link unlink | source", bodyStyle: // style to assign to document body contained within the editor - "margin:4px; color:#4c4c4c; font-size:13px; font-family:\"Lucida Grande\",Helvetica,Verdana,Arial,sans-serif; cursor:text" + "margin:4px; color:#4c4c4c; font-size:13px; font-family:\'Lucida Grande\',Helvetica,Verdana,Arial,sans-serif; cursor:text" }); this.$cleditor = this.$textarea.cleditor()[0]; this.$cleditor.change(function() { From 7310643b6b15e09db11de2ec38be007e37706936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 11 Mar 2013 12:46:23 +0100 Subject: [PATCH 22/43] [IMP] email: better reply_to that includes document name. bzr revid: tde@openerp.com-20130311114623-ftcj8s8rbahvjupz --- addons/mail/mail_mail.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 1b3ca152a9a..6219ace864f 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -21,6 +21,7 @@ import base64 import logging +import re from urllib import urlencode from urlparse import urljoin @@ -198,13 +199,27 @@ class mail_mail(osv.Model): :param browse_record mail: mail.mail browse_record :param browse_record partner: specific recipient partner """ + # TDE FIXME BEFORE MERGE: not sure the late change is interesting if mail.reply_to: return mail.reply_to - if not mail.model or not mail.res_id: - return False - if not hasattr(self.pool.get(mail.model), 'message_get_reply_to'): - return False - return self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0] + document_name = '' + email_reply_to = False + if mail.model and mail.res_id and hasattr(self.pool.get(mail.model), 'message_get_reply_to'): + email_reply_to = self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0] + + if not email_reply_to: + if mail.email_from: + match = re.search(r'([^\s,<@]+@[^>\s,]+)', mail.email_from) # TDE TODO: simplify multiple same regex + if match: + email_reply_to = match.group(1) + + if email_reply_to: + if mail.model and mail.res_id: + document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0] + if document_name: + return 'Followers of %s <%s>' % (document_name[1], email_reply_to) + else: + return email_reply_to def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): """ Return a dictionary for specific email values, depending on a From a2a8c238dcd04e683ef6d3f1051aaa9e432db89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 11 Mar 2013 13:42:54 +0100 Subject: [PATCH 23/43] [CLEAN] email_template: removed a print statement. bzr revid: tde@openerp.com-20130311124254-xzxs1r6cnqw2zxxc --- addons/email_template/wizard/mail_compose_message.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 9dad18e07f3..255f93998a2 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -70,7 +70,6 @@ class mail_compose_message(osv.TransientModel): 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) - print values.get('partner_ids') # transform attachments into attachment_ids values['attachment_ids'] = [] ir_attach_obj = self.pool.get('ir.attachment') From 6ae80a98acb524d0965658446a65bf5fd2a020d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 11 Mar 2013 13:49:14 +0100 Subject: [PATCH 24/43] [CLEAN] mail: cleaned code of modified reply_to. bzr revid: tde@openerp.com-20130311124914-xcdbgmw4w7nm02rv --- addons/mail/mail_mail.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 6219ace864f..1ca3255fd84 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -202,24 +202,26 @@ class mail_mail(osv.Model): # TDE FIXME BEFORE MERGE: not sure the late change is interesting if mail.reply_to: return mail.reply_to - document_name = '' email_reply_to = False + + # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias if mail.model and mail.res_id and hasattr(self.pool.get(mail.model), 'message_get_reply_to'): email_reply_to = self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0] + # no alias reply_to -> reply_to will be the email_from, only the email part + if not email_reply_to and mail.email_from: + match = re.search(r'([^\s,<@]+@[^>\s,]+)', mail.email_from) # TDE TODO: simplify multiple same regex + if match: + email_reply_to = match.group(1) - if not email_reply_to: - if mail.email_from: - match = re.search(r'([^\s,<@]+@[^>\s,]+)', mail.email_from) # TDE TODO: simplify multiple same regex - if match: - email_reply_to = match.group(1) - + # format 'Document name ' if email_reply_to: + document_name = '' if mail.model and mail.res_id: document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0] if document_name: - return 'Followers of %s <%s>' % (document_name[1], email_reply_to) - else: - return email_reply_to + email_reply_to = 'Followers of %s <%s>' % (document_name[1], email_reply_to) + + return email_reply_to def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): """ Return a dictionary for specific email values, depending on a From 459fce0150981269e668fda52001463269f58659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 11 Mar 2013 15:08:38 +0100 Subject: [PATCH 25/43] [CLEAN] Removed unnecessary quotes. As Teal'c said: Indeed. bzr revid: tde@openerp.com-20130311140838-g9pvncskvuibz1wm --- addons/web/static/src/js/view_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c8f7eaf032e..952a1c9d15f 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2662,7 +2662,7 @@ instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instanc "| removeformat | bullets numbering | outdent " + "indent | link unlink | source", bodyStyle: // style to assign to document body contained within the editor - "margin:4px; color:#4c4c4c; font-size:13px; font-family:\'Lucida Grande\',Helvetica,Verdana,Arial,sans-serif; cursor:text" + "margin:4px; color:#4c4c4c; font-size:13px; font-family:'Lucida Grande',Helvetica,Verdana,Arial,sans-serif; cursor:text" }); this.$cleditor = this.$textarea.cleditor()[0]; this.$cleditor.change(function() { From 910f7097ba8278e329de236341f7e2e78293c0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 11:56:40 +0100 Subject: [PATCH 26/43] [IMP] res_partner: use tools.email_split instead of custom regex when parsing partner_name to find an email. bzr revid: tde@openerp.com-20130313105640-r53xueaz36zw3fjd --- openerp/addons/base/res/res_partner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 4dce23b4d33..7c9fbf58c64 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -417,10 +417,10 @@ class res_partner(osv.osv, format_address): """ Supported syntax: - 'Raoul ': will find name and email address - otherwise: default, everything is set as the name """ - match = re.search(r'([^\s,<@]+@[^>\s,]+)', text) - if match: - email = match.group(1) - name = text[:text.index(email)].replace('"','').replace('<','').strip() + emails = tools.email_split(text) + if emails: + email = emails[0] + name = text[:text.index(email)].replace('"', '').replace('<', '').strip() else: name, email = text, '' return name, email From d49889c9a8575cdb31193770172e9de3bb1b3d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 12:17:46 +0100 Subject: [PATCH 27/43] [IMP] mail: res_partner: improved help for notification_email_send. bzr revid: tde@openerp.com-20130313111746-xfrd8kdhntb39gwl --- addons/mail/res_partner.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/addons/mail/res_partner.py b/addons/mail/res_partner.py index 6807c13a08a..05f9fc2ace0 100644 --- a/addons/mail/res_partner.py +++ b/addons/mail/res_partner.py @@ -30,16 +30,16 @@ class res_partner_mail(osv.Model): _columns = { 'notification_email_send': fields.selection([ - ('all', 'All Messages (discussions, emails, followed system notifications)'), - ('comment', 'Incoming Emails and Discussions'), + ('none', 'Never'), ('email', 'Incoming Emails only'), - ('none', 'Never') + ('comment', 'Incoming Emails and Discussions'), + ('all', 'All Messages (discussions, emails, followed system notifications)'), ], 'Receive Messages by Email', required=True, - help="Policy to receive emails for new notifications pushed to your Inbox. "\ - "'Never' prevents OpenERP from sending emails for notifications. "\ - "'Incoming Emails only' send an email when you receive an incoming email, such as customer replies. "\ - "'Incoming Emails and Discussions' sends emails for incoming emails along with user input. "\ - "'All Messages' also sends emails for notifications received for followed subtype, like stage changes, in addition to Incoming Emails and Discussions."), + help="Policy to receive emails for new messages pushed to your personal Inbox:\n " + "- Never: no emails are sent" + "- Incoming Emails only: for messages received by the system via email" + "- Incoming Emails and Discussions: for incoming emails along with internal discussions" + "- All Messages: for every notification you receive in your Inbox"), } _defaults = { From 6b2c64ff4099a3ab7cb5f0643f20bda5943f3f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 12:19:26 +0100 Subject: [PATCH 28/43] [IMP] mail_mail: cleaned code about reply_to format: now uses tools.email_split, cleaned a bit the code. bzr revid: tde@openerp.com-20130313111926-45d9gct5nihr6f6n --- addons/mail/mail_mail.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 1ca3255fd84..7720df84f92 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -209,17 +209,15 @@ class mail_mail(osv.Model): email_reply_to = self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0] # no alias reply_to -> reply_to will be the email_from, only the email part if not email_reply_to and mail.email_from: - match = re.search(r'([^\s,<@]+@[^>\s,]+)', mail.email_from) # TDE TODO: simplify multiple same regex - if match: - email_reply_to = match.group(1) + emails = tools.email_split(mail.email_from) + if emails: + email_reply_to = emails[0] # format 'Document name ' - if email_reply_to: - document_name = '' - if mail.model and mail.res_id: - document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0] + if email_reply_to and mail.model and mail.res_id: + document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0] if document_name: - email_reply_to = 'Followers of %s <%s>' % (document_name[1], email_reply_to) + email_reply_to = _('Followers of %s <%s>') % (document_name[1], email_reply_to) return email_reply_to From 2f4a59fd0652e1c5b76e5bd62826de084295c91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 12:20:13 +0100 Subject: [PATCH 29/43] [IMP] email_template, crm: added forgotten filters for opt-out; cleaned help of opt-out; email body is back into internal note as it has been decided that it was too much change for 7.0, especially that email content parsing is not robust enough. bzr revid: tde@openerp.com-20130313112013-veqzplextey1300p --- addons/crm/crm_lead.py | 6 +++++- addons/crm/crm_lead_view.xml | 2 ++ addons/email_template/res_partner.py | 5 +++-- addons/email_template/res_partner_view.xml | 12 ++++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 9b3697b8dc5..7ab9f110a7b 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -259,7 +259,9 @@ class crm_lead(base_stage, format_address, osv.osv): 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"), 'contact_name': fields.char('Contact Name', size=64), 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1), - 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign."), + 'opt_out': fields.boolean('Opt-Out', oldname='optout', + help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. " + "Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing."), 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), 'date_closed': fields.datetime('Closed', readonly=True), @@ -977,8 +979,10 @@ class crm_lead(base_stage, format_address, osv.osv): """ if custom_values is None: custom_values = {} + desc = html2plaintext(msg.get('body')) if msg.get('body') else '' defaults = { 'name': msg.get('subject') or _("No Subject"), + 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'partner_id': msg.get('author_id', False), diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index df68f90aa57..39c9a5b98f5 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -341,6 +341,8 @@ + diff --git a/addons/email_template/res_partner.py b/addons/email_template/res_partner.py index bf730f37458..cb3ef67862f 100644 --- a/addons/email_template/res_partner.py +++ b/addons/email_template/res_partner.py @@ -19,7 +19,7 @@ # ############################################################################## -from openerp.osv import fields,osv +from openerp.osv import fields, osv class res_partner(osv.osv): """Inherit res.partner to add a generic opt-out field that can be used @@ -29,7 +29,8 @@ class res_partner(osv.osv): _columns = { 'opt_out': fields.boolean('Opt-Out', - help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign."), + help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. " + "Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing."), } _defaults = { diff --git a/addons/email_template/res_partner_view.xml b/addons/email_template/res_partner_view.xml index 3d17120d69e..7e494bd5054 100644 --- a/addons/email_template/res_partner_view.xml +++ b/addons/email_template/res_partner_view.xml @@ -11,5 +11,17 @@ + + + res.partner.opt_out.search + res.partner + + + + + + + From fdf7410acffa269ee81ac7a0f03c3f9429b19e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 12:59:01 +0100 Subject: [PATCH 30/43] [FIX] crm, res_partner: added separator in search views after adding not opt-out filter. bzr revid: tde@openerp.com-20130313115901-s60j69qn3e3q4bpk --- addons/crm/crm_lead_view.xml | 3 ++- addons/email_template/res_partner_view.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 39c9a5b98f5..4c055ce1436 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -341,9 +341,10 @@ + - + diff --git a/addons/email_template/res_partner_view.xml b/addons/email_template/res_partner_view.xml index 7e494bd5054..c6331ebf514 100644 --- a/addons/email_template/res_partner_view.xml +++ b/addons/email_template/res_partner_view.xml @@ -18,8 +18,9 @@ + + help="Leads that did not ask not to be included in mass mailing campaigns" /> From 3dc1a58ed5906212a52c376b62cff69d9ac4ec78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 13:12:21 +0100 Subject: [PATCH 31/43] [FIX] crm, email_template: fixed help messages talking about partners and leads instead of leads and partners... well, I think this comment is not very understandable, but at least there is a commit message. Horray. bzr revid: tde@openerp.com-20130313121221-lo0kmkt2lltyr94e --- addons/crm/crm_lead.py | 2 +- addons/email_template/res_partner_view.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 7ab9f110a7b..d0be688c8a5 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -261,7 +261,7 @@ class crm_lead(base_stage, format_address, osv.osv): 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1), 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. " - "Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing."), + "Filter 'Available for Mass Mailing' allows users to filter the leads when performing mass mailing."), 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), 'date_closed': fields.datetime('Closed', readonly=True), diff --git a/addons/email_template/res_partner_view.xml b/addons/email_template/res_partner_view.xml index c6331ebf514..1da3681d3af 100644 --- a/addons/email_template/res_partner_view.xml +++ b/addons/email_template/res_partner_view.xml @@ -20,7 +20,7 @@ + help="Partners that did not ask not to be included in mass mailing campaigns" /> From ecd460e0d5a534af2a2536786b401c84267db40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 13:16:59 +0100 Subject: [PATCH 32/43] [IMP] email_template: improved help for email_from. bzr revid: tde@openerp.com-20130313121659-pvdy814zoq9zonf2 --- addons/email_template/email_template.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 280e44fa2fb..08ef3ec6e98 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -142,7 +142,9 @@ class email_template(osv.osv): help="If checked, the user's signature will be appended to the text version " "of the message"), 'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",), - 'email_from': fields.char('From', help="Sender address (placeholders may be used here)"), + 'email_from': fields.char('From', + help="Sender address (placeholders may be used here). If not set, the default " + "value will be the author's email alias if configured, or email address."), 'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"), 'email_recipients': fields.char('To (Partners)', help="Comma-separated ids of recipient partners (placeholders may be used here)"), 'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"), From 3b989b2c57f8aeb2d8bb92ec6338b862458d9641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 13 Mar 2013 13:26:32 +0100 Subject: [PATCH 33/43] [FIX] mail_message: fixed strings and names for read/unread filters. bzr revid: tde@openerp.com-20130313122632-bwsz0pss88z0ups2 --- addons/mail/mail_message_view.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 7d2e9483773..8f2fb3f1020 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -59,12 +59,12 @@ - - + Date: Wed, 13 Mar 2013 13:43:17 +0100 Subject: [PATCH 34/43] account: [FIX] Removes the unneeded second cancel button on supplier invoices bzr revid: cbi@openerp.com-20130313124317-4ug3gvlnocmc50ko --- addons/account/account_invoice_view.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 3d46ee8365a..8c7cec67ac5 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -146,7 +146,6 @@
From 1b8891dd955c3789754e9b2b450732097e0cbf1c Mon Sep 17 00:00:00 2001 From: "dle@openerp.com" <> Date: Thu, 14 Mar 2013 11:30:17 +0100 Subject: [PATCH 43/43] [Revert]auth_signup: should be done here bzr revid: dle@openerp.com-20130314103017-g0dps4lx151unu6o --- addons/auth_signup/res_users_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/auth_signup/res_users_view.xml b/addons/auth_signup/res_users_view.xml index 27559ea5198..be9942feac2 100644 --- a/addons/auth_signup/res_users_view.xml +++ b/addons/auth_signup/res_users_view.xml @@ -21,7 +21,7 @@
-