diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index a100c0e60ba..86d3b0d24de 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -343,7 +343,7 @@ class account_journal_cashbox_line(osv.osv): _rec_name = 'pieces' _columns = { 'pieces': fields.float('Values', digits_compute=dp.get_precision('Account')), - 'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1), + 'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1, ondelete="cascade"), } _order = 'pieces asc' diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 60e3dc44328..52bcd29622c 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -449,11 +449,15 @@ class account_invoice(osv.osv): context = {} invoices = self.read(cr, uid, ids, ['state','internal_number'], context=context) unlink_ids = [] + for t in invoices: - if t['state'] in ('draft', 'cancel') and t['internal_number']== False: - unlink_ids.append(t['id']) + if t['state'] not in ('draft', 'cancel'): + raise openerp.exceptions.Warning(_('You cannot delete an invoice which is not draft or cancelled. You should refund it instead.')) + elif t['internal_number']: + raise openerp.exceptions.Warning(_('You cannot delete an invoice after it has been validated (and received a number). You can set it back to "Draft" state and modify its content, then re-confirm it.')) else: - raise osv.except_osv(_('Invalid Action!'), _('You can not delete an invoice which is not cancelled. You should refund it instead.')) + unlink_ids.append(t['id']) + osv.osv.unlink(self, cr, uid, unlink_ids, context=context) return True @@ -1147,6 +1151,11 @@ class account_invoice(osv.osv): return self.name_get(cr, user, ids, context) def _refund_cleanup_lines(self, cr, uid, lines, context=None): + """Convert records to dict of values suitable for one2many line creation + + :param list(browse_record) lines: records to convert + :return: list of command tuple for one2many line creation [(0, 0, dict of valueis), ...] + """ clean_lines = [] for line in lines: clean_line = {} diff --git a/addons/account/project/project_view.xml b/addons/account/project/project_view.xml index 8590ee286d4..175a227a9ea 100644 --- a/addons/account/project/project_view.xml +++ b/addons/account/project/project_view.xml @@ -18,6 +18,7 @@ + diff --git a/addons/account/report/account_financial_report.py b/addons/account/report/account_financial_report.py index 3898158c457..d94955a764a 100644 --- a/addons/account/report/account_financial_report.py +++ b/addons/account/report/account_financial_report.py @@ -56,7 +56,7 @@ class report_account_common(report_sxw.rml_parse, common_report_header): for report in self.pool.get('account.financial.report').browse(self.cr, self.uid, ids2, context=data['form']['used_context']): vals = { 'name': report.name, - 'balance': report.balance * report.sign, + 'balance': report.balance * report.sign or 0.0, 'type': 'report', 'level': bool(report.style_overwrite) and report.style_overwrite or report.level, 'account_type': report.type =='sum' and 'view' or False, #used to underline the financial report balances diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index 68937004dbc..df4ad3494d2 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -180,9 +180,9 @@ class account_invoice_refund(osv.osv_memory): 'journal_id', 'period_id'], context=context) invoice = invoice[0] del invoice['id'] - invoice_lines = inv_line_obj.read(cr, uid, invoice['invoice_line'], context=context) + invoice_lines = inv_line_obj.browse(cr, uid, invoice['invoice_line'], context=context) invoice_lines = inv_obj._refund_cleanup_lines(cr, uid, invoice_lines) - tax_lines = inv_tax_obj.read(cr, uid, invoice['tax_line'], context=context) + tax_lines = inv_tax_obj.browse(cr, uid, invoice['tax_line'], context=context) tax_lines = inv_obj._refund_cleanup_lines(cr, uid, tax_lines) invoice.update({ 'type': inv.type, diff --git a/addons/account_analytic_analysis/__init__.py b/addons/account_analytic_analysis/__init__.py index 9aae723c5cf..be5a6e43f73 100644 --- a/addons/account_analytic_analysis/__init__.py +++ b/addons/account_analytic_analysis/__init__.py @@ -20,7 +20,6 @@ ############################################################################## import account_analytic_analysis -import cron_account_analytic_account import res_config # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_analytic_analysis/__openerp__.py b/addons/account_analytic_analysis/__openerp__.py index 5ff15c09076..99e4353afa1 100644 --- a/addons/account_analytic_analysis/__openerp__.py +++ b/addons/account_analytic_analysis/__openerp__.py @@ -38,7 +38,6 @@ Adds menu to show relevant information to each manager.You can also view the rep 'security/ir.model.access.csv', 'security/account_analytic_analysis_security.xml', 'account_analytic_analysis_view.xml', - 'account_analytic_analysis_menu.xml', 'account_analytic_analysis_cron.xml', 'res_config_view.xml', ], diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 23d8faaffce..708c4b56629 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -18,6 +18,9 @@ # along with this program. If not, see . # ############################################################################## +import datetime +import logging +import time from openerp.osv import osv, fields from openerp.osv.orm import intersect, except_orm @@ -26,6 +29,7 @@ from openerp.tools.translate import _ from openerp.addons.decimal_precision import decimal_precision as dp +_logger = logging.getLogger(__name__) class account_analytic_account(osv.osv): _name = "account.analytic.account" @@ -484,7 +488,48 @@ class account_analytic_account(osv.osv): res['value']['to_invoice'] = template.to_invoice.id res['value']['pricelist_id'] = template.pricelist_id.id return res -account_analytic_account() + + def cron_account_analytic_account(self, cr, uid, context=None): + if context is None: + context = {} + remind = {} + + def fill_remind(key, domain, write_pending=False): + base_domain = [ + ('type', '=', 'contract'), + ('partner_id', '!=', False), + ('manager_id', '!=', False), + ('manager_id.email', '!=', False), + ] + base_domain.extend(domain) + + accounts_ids = self.search(cr, uid, base_domain, context=context, order='name asc') + accounts = self.browse(cr, uid, accounts_ids, context=context) + for account in accounts: + if write_pending: + account.write({'state' : 'pending'}, context=context) + remind_user = remind.setdefault(account.manager_id.id, {}) + remind_type = remind_user.setdefault(key, {}) + remind_partner = remind_type.setdefault(account.partner_id, []).append(account) + + # Already expired + fill_remind("old", [('state', 'in', ['pending'])]) + + # Expires now + fill_remind("new", [('state', 'in', ['draft', 'open']), '|', '&', ('date', '!=', False), ('date', '<=', time.strftime('%Y-%m-%d')), ('is_overdue_quantity', '=', True)], True) + + # Expires in less than 30 days + fill_remind("future", [('state', 'in', ['draft', 'open']), ('date', '!=', False), ('date', '<', (datetime.datetime.now() + datetime.timedelta(30)).strftime("%Y-%m-%d"))]) + + context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') + context['action_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_analytic_analysis', 'action_account_analytic_overdue_all')[1] + template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_analytic_analysis', 'account_analytic_cron_email_template')[1] + for user_id, data in remind.items(): + context["data"] = data + _logger.debug("Sending reminder to uid %s", user_id) + self.pool.get('email.template').send_mail(cr, uid, template_id, user_id, context=context) + + return True class account_analytic_account_summary_user(osv.osv): _name = "account_analytic_analysis.summary.user" @@ -538,8 +583,6 @@ class account_analytic_account_summary_user(osv.osv): lu.user_id as "user", unit_amount from lu, mu)''') - -account_analytic_account_summary_user() class account_analytic_account_summary_month(osv.osv): _name = "account_analytic_analysis.summary.month" @@ -600,6 +643,4 @@ class account_analytic_account_summary_month(osv.osv): 'GROUP BY d.month, d.account_id ' \ ')') - -account_analytic_account_summary_month() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml index 8564b1606eb..311d8708872 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml @@ -1,15 +1,77 @@ + + + Contract expiration reminder + ${object.email or ''} + Contract expiration reminder ${user.company_id.name} + ${object.email} + ${object.lang} + + + + + Customer + Contract + Dates + Prepaid Units + Contact + + % for partner, accounts in values: + % for account in accounts: + + ${partner.name} + ${account.name} + ${account.date_start} to ${account.date and account.date or '???'} + + % if account.quantity_max != 0.0: + ${account.remaining_hours}/${account.quantity_max} units + % endif + + ${account.partner_id.phone or ''}, ${account.partner_id.email or ''} + + % endfor + % endfor + +% endmacro + +% if "new" in ctx["data"]: +

The following contracts just expired:

+ ${account_table(ctx["data"]["new"].iteritems())} +% endif + +% if "old" in ctx["data"]: +

The following expired contracts are still not processed:

+ ${account_table(ctx["data"]["old"].iteritems())} +% endif + +% if "future" in ctx["data"]: +

The following contracts will expire in less than one month:

+ ${account_table(ctx["data"]["future"].iteritems())} +% endif + + + + ]]>
+
+ + - Analytic Account Report for Sales + Contract expiration reminder 1 weeks -1 - - - - + + + +
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml deleted file mode 100644 index 75f06614124..00000000000 --- a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - Time & Materials to Invoice - account.analytic.line - form - tree,form - [('invoice_id','=',False)] - {'search_default_to_invoice': 1, 'search_default_sales': 1} - - -

- You will find here timesheets and purchases you did for - contracts that can be reinvoiced to the customer. If you want - to record new activities to invoice, you should use the timesheet - menu instead. -

-
-
- - - - account.analytic.account.search - account.analytic.account - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Contracts to Renew - account.analytic.account - form - tree,form - {'search_default_manager_id':uid, 'search_default_pending':1, 'search_default_renew':1} - [('type','=','contract')] - - -

- Click to define a new contract. -

- You will find here the contracts to be renewed because the - end date is passed or the working effort is higher than the - maximum authorized one. -

- OpenERP automatically sets contracts to be renewed in a pending - state. After the negociation, the salesman should close or renew - pending contracts. -

-
-
- - - - Contracts - account.analytic.account - form - tree,form - {'default_type':'contract', 'search_default_open':1, 'search_default_pending':1, 'default_manager_id':uid} - [('type','=','contract')] - - -

- Click to create a new contract. -

- Use contracts to follow tasks, issues, timesheets or invoicing based on - work done, expenses and/or sales orders. OpenERP will automatically manage - the alerts for the renewal of the contracts to the right salesperson. -

-
-
- - - - -
-
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml index f8f718882af..50cdaff44d1 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml @@ -1,17 +1,13 @@ - - - Sales Orders sale.order account.analytic.account + account.analytic.account.invoice.form.inherit account.analytic.account @@ -142,19 +138,7 @@
- - account.analytic.account.tree - account.analytic.account - - - - - - - - - - + account.analytic.account.form.template.required account.analytic.account @@ -167,6 +151,132 @@
+ + + account.analytic.account.list.contract + account.analytic.account + + + + + + + + + + + + + + account.analytic.account.search + account.analytic.account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contracts + account.analytic.account + form + tree,form + {'default_type':'contract', 'search_default_open':1, 'search_default_pending':1, 'default_manager_id':uid} + [('type','=','contract')] + + +

+ Click to create a new contract. +

+ Use contracts to follow tasks, issues, timesheets or invoicing based on + work done, expenses and/or sales orders. OpenERP will automatically manage + the alerts for the renewal of the contracts to the right salesperson. +

+
+
+ + + + + + Time & Materials to Invoice + account.analytic.line + form + tree,form + [('invoice_id','=',False)] + {'search_default_to_invoice': 1, 'search_default_sales': 1} + + +

+ You will find here timesheets and purchases you did for + contracts that can be reinvoiced to the customer. If you want + to record new activities to invoice, you should use the timesheet + menu instead. +

+
+
+ + + + + + Contracts to Renew + account.analytic.account + form + tree,form + {'search_default_manager_id':uid, 'search_default_pending':1, 'search_default_renew':1} + [('type','=','contract')] + + +

+ Click to define a new contract. +

+ You will find here the contracts to be renewed because the + end date is passed or the working effort is higher than the + maximum authorized one. +

+ OpenERP automatically sets contracts to be renewed in a pending + state. After the negociation, the salesman should close or renew + pending contracts. +

+
+
+ + + Contract Template ir.actions.act_window @@ -184,8 +294,8 @@ terms and conditions of the contract.

- + diff --git a/addons/account_analytic_analysis/cron_account_analytic_account.py b/addons/account_analytic_analysis/cron_account_analytic_account.py deleted file mode 100644 index 6e2ce4a47b3..00000000000 --- a/addons/account_analytic_analysis/cron_account_analytic_account.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -from mako.template import Template -import time -try: - import cStringIO as StringIO -except ImportError: - import StringIO - -from openerp import tools -from openerp.osv import osv - -MAKO_TEMPLATE = u"""Hello ${user.name}, - -Here is a list of contracts that have to be renewed for two -possible reasons: - - the end of contract date is passed - - the customer consumed more hours than expected - -Can you contact the customer in order to sell a new or renew its contract. -The contract has been set with a pending state, can you update the status -of the analytic account following this rule: - - Set Done: if the customer does not want to renew - - Set Open: if the customer purchased an extra contract - -Here is the list of contracts to renew: -% for partner, accounts in partners.iteritems(): - * ${partner.name} - % for account in accounts: - - Name: ${account.name} - % if account.quantity_max != 0.0: - - Quantity: ${account.quantity}/${account.quantity_max} hours - % endif - - Dates: ${account.date_start} to ${account.date and account.date or '???'} - - Contacts: - ${account.partner_id.name}, ${account.partner_id.phone or ''}, ${account.partner_id.email or ''} - - % endfor -% endfor - -You can use the report in the menu: Sales > Invoicing > Overdue Accounts - -Regards, - --- -OpenERP -""" - -class analytic_account(osv.osv): - _inherit = 'account.analytic.account' - - def cron_account_analytic_account(self, cr, uid, context=None): - domain = [ - ('name', 'not ilike', 'maintenance'), - ('partner_id', '!=', False), - ('user_id', '!=', False), - ('user_id.email', '!=', False), - ('state', 'in', ('draft', 'open')), - '|', ('date', '<', time.strftime('%Y-%m-%d')), ('date', '=', False), - ] - - account_ids = self.search(cr, uid, domain, context=context, order='name asc') - accounts = self.browse(cr, uid, account_ids, context=context) - - users = dict() - for account in accounts: - users.setdefault(account.user_id, dict()).setdefault(account.partner_id, []).append(account) - - account.write({'state' : 'pending'}, context=context) - - for user, data in users.iteritems(): - subject = '[OPENERP] Reporting: Analytic Accounts' - body = Template(MAKO_TEMPLATE).render_unicode(user=user, partners=data) - tools.email_send('noreply@openerp.com', [user.email, ], subject, body) - - return True - -analytic_account() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_followup/wizard/account_followup_print.py b/addons/account_followup/wizard/account_followup_print.py index 76aee365e00..37b4300b03f 100644 --- a/addons/account_followup/wizard/account_followup_print.py +++ b/addons/account_followup/wizard/account_followup_print.py @@ -26,8 +26,6 @@ from openerp import tools from openerp.osv import fields, osv from openerp.tools.translate import _ -from openerp import SUPERUSER_ID - class account_followup_stat_by_partner(osv.osv): _name = "account_followup.stat.by.partner" _description = "Follow-up Statistics by Partner" @@ -315,13 +313,6 @@ class account_followup_print(osv.osv_memory): if stat_line_id not in partner_list: partner_list.append(stat_line_id) to_update[str(id)]= {'level': fups[followup_line_id][1], 'partner_id': stat_line_id} - #Remove partners that are other companies in OpenERP - comp_obj = self.pool.get("res.company") - comp_ids = comp_obj.search(cr, SUPERUSER_ID, [], context=context) - for comp in comp_obj.browse(cr, SUPERUSER_ID, comp_ids, context=context): - company_partner_wiz_id = comp.partner_id.id * 10000 + company_id - if company_partner_wiz_id in partner_list: - partner_list.remove(company_partner_wiz_id) return {'partner_ids': partner_list, 'to_update': to_update} account_followup_print() diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 005afa5d544..85e21d5fbc5 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -196,7 +196,7 @@ class account_analytic_account(osv.osv): 'date_start': fields.date('Start Date'), 'date': fields.date('Date End', select=True), 'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts. - 'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','To Renew'),('close','Closed')], 'Status', required=True, track_visibility='onchange'), + 'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'),('pending','To Renew'),('close','Closed'),('cancelled', 'Cancelled')], 'Status', required=True, track_visibility='onchange'), 'currency_id': fields.function(_currency, fnct_inv=_set_company_currency, #the currency_id field is readonly except if it's a view account and if there is no company store = { 'res.company': (_get_analytic_account, ['currency_id'], 10), @@ -258,6 +258,9 @@ class account_analytic_account(osv.osv): (check_recursion, 'Error! You cannot create recursive analytic accounts.', ['parent_id']), ] + def name_create(self, cr, uid, name, context=None): + raise osv.except_osv(_('Warning'), _("Quick account creation disallowed.")) + def copy(self, cr, uid, id, default=None, context=None): if not default: default = {} @@ -349,6 +352,4 @@ class account_analytic_line(osv.osv): (_check_no_view, 'You cannot create analytic line on view account.', ['account_id']), ] -account_analytic_line() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_oauth_signup/res_users.py b/addons/auth_oauth_signup/res_users.py index 495331124b1..5eebc02fe04 100644 --- a/addons/auth_oauth_signup/res_users.py +++ b/addons/auth_oauth_signup/res_users.py @@ -41,7 +41,7 @@ class res_users(osv.Model): state = simplejson.loads(params['state']) token = state.get('t') oauth_uid = validation['user_id'] - email = validation.get('email', 'provider_%d_user_%d' % (provider, oauth_uid)) + email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid)) name = validation.get('name', email) values = { 'name': name, diff --git a/addons/auth_signup/res_users_view.xml b/addons/auth_signup/res_users_view.xml index fc163e1b3ed..be9942feac2 100644 --- a/addons/auth_signup/res_users_view.xml +++ b/addons/auth_signup/res_users_view.xml @@ -21,8 +21,8 @@
-
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 6498a69673b..f4b27fa4d9d 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -587,27 +587,25 @@ class crm_lead(base_stage, format_address, osv.osv): return True def _merge_opportunity_attachments(self, cr, uid, opportunity_id, opportunities, context=None): - attachment = self.pool.get('ir.attachment') + attach_obj = self.pool.get('ir.attachment') # return attachments of opportunity def _get_attachments(opportunity_id): - attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', opportunity_id)], context=context) - return attachment.browse(cr, uid, attachment_ids, context=context) + attachment_ids = attach_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', opportunity_id)], context=context) + return attach_obj.browse(cr, uid, attachment_ids, context=context) - count = 1 first_attachments = _get_attachments(opportunity_id) + #counter of all attachments to move. Used to make sure the name is different for all attachments + count = 1 for opportunity in opportunities: attachments = _get_attachments(opportunity.id) - for first in first_attachments: - for attachment in attachments: - if attachment.name == first.name: - values = dict( - name = "%s (%s)" % (attachment.name, count,), - res_id = opportunity_id, - ) - attachment.write(values) - count+=1 - + for attachment in attachments: + values = {'res_id': opportunity_id,} + for attachment_in_first in first_attachments: + if attachment.name == attachment_in_first.name: + name = "%s (%s)" % (attachment.name, count,), + count+=1 + attachment.write(values) return True def merge_opportunity(self, cr, uid, ids, context=None): diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index fc43339ba8e..87c130547cd 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -2,7 +2,6 @@ - @@ -110,7 +109,7 @@ string="Phone Calls"/>
-
@@ -217,9 +216,8 @@ - + - @@ -390,8 +388,7 @@