From 430b6c07a7f714949d833e7fd0e9b68cc4b2bc86 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Mon, 29 Apr 2013 15:48:49 +0530 Subject: [PATCH 001/130] [IMP]HR: Improve Application form some field label Improve meeting logs for Application Form Set My Requests instead of My Leaves bzr revid: dbr@tinyerp.com-20130429101849-1iz7d52jvgxwsbfu --- addons/hr_holidays/hr_holidays_view.xml | 2 +- addons/hr_recruitment/hr_recruitment.py | 26 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 5828790c152..cc3813b7240 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -14,7 +14,7 @@ - + diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index e41d80fb348..3ec36886edb 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -186,7 +186,7 @@ class hr_applicant(base_stage, osv.Model): return res _columns = { - 'name': fields.char('Subject', size=128, required=True), + 'name': fields.char('Application Name', size=128, required=True), 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."), 'description': fields.text('Description'), 'email_from': fields.char('Email', size=128, help="These people will receive email."), @@ -219,7 +219,7 @@ class hr_applicant(base_stage, osv.Model): 'salary_expected_extra': fields.char('Expected Salary Extra', size=100, help="Salary Expected by Applicant, extra advantages"), 'salary_proposed': fields.float('Proposed Salary', help="Salary Proposed by the Organisation"), 'salary_expected': fields.float('Expected Salary', help="Salary Expected by Applicant"), - 'availability': fields.integer('Availability'), + 'availability': fields.integer('Availability', help="The number of days in which the applicant will be available to start working"), 'partner_name': fields.char("Applicant's Name", size=64), 'partner_phone': fields.char('Phone', size=32), 'partner_mobile': fields.char('Mobile', size=32), @@ -306,6 +306,14 @@ class hr_applicant(base_stage, osv.Model): return stage_ids[0] return False + def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): + if not duration: + duration = _('unknown') + else: + duration = str(duration) + message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting_date, meeting_subject, duration) + return self.message_post(cr, uid, ids, body=message,context=context) + def action_makeMeeting(self, cr, uid, ids, context=None): """ This opens Meeting's calendar view to schedule meeting on current applicant @return: Dictionary value for created Meeting view @@ -314,6 +322,7 @@ class hr_applicant(base_stage, osv.Model): category = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) res['context'] = { + 'default_applicant_id':applicant.id, 'default_partner_ids': applicant.partner_id and [applicant.partner_id.id] or False, 'default_user_id': uid, 'default_name': applicant.name, @@ -562,4 +571,17 @@ class applicant_category(osv.osv): 'name': fields.char('Name', size=64, required=True, translate=True), } +class crm_meeting(osv.Model): + _inherit = 'crm.meeting' + _columns = { + 'applicant_id': fields.many2one ('hr.applicant', 'Recruitment') + } + + def create(self, cr, uid, vals, context=None): + res = super(crm_meeting, self).create(cr, uid, vals, context=context) + obj = self.browse(cr, uid, res, context=context) + if obj.applicant_id: + self.pool.get('hr.applicant').log_meeting(cr, uid, [obj.applicant_id.id] , obj.name, obj.date,obj.duration, context=context) + return res + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 76491b781a9614aec7a4ade332ae9cbff8c0e84b Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Wed, 1 May 2013 18:55:25 +0530 Subject: [PATCH 002/130] [IMP]: Improve typo bzr revid: dbr@tinyerp.com-20130501132525-04ebuxo5usmsi0av --- addons/hr_recruitment/hr_recruitment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 3ec36886edb..1d19fc6b204 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -578,10 +578,10 @@ class crm_meeting(osv.Model): } def create(self, cr, uid, vals, context=None): - res = super(crm_meeting, self).create(cr, uid, vals, context=context) - obj = self.browse(cr, uid, res, context=context) - if obj.applicant_id: - self.pool.get('hr.applicant').log_meeting(cr, uid, [obj.applicant_id.id] , obj.name, obj.date,obj.duration, context=context) - return res + meeting_id = super(crm_meeting, self).create(cr, uid, vals, context=context) + obj_meeting = self.browse(cr, uid, meeting_id, context=context) + if obj_meeting.applicant_id: + self.pool.get('hr.applicant').log_meeting(cr, uid, [obj_meeting.applicant_id.id] , obj_meeting.name, obj_meeting.date,obj_meeting.duration, context=context) + return meeting_id # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 1cdc9245e7fccff0e9bf5d83363a210c71445e3e Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Mon, 27 May 2013 17:47:04 +0530 Subject: [PATCH 003/130] [IMP]Hr: Improve tooltip and labels bzr revid: dbr@tinyerp.com-20130527121704-k7fyapvs0ogfdamx --- addons/hr_holidays/hr_holidays_view.xml | 2 +- addons/hr_recruitment/hr_recruitment.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index cc3813b7240..6ff0b2efc9a 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -14,7 +14,7 @@ - + diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index f4069ed70ab..4fd0c15202a 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -186,7 +186,7 @@ class hr_applicant(base_stage, osv.Model): return res _columns = { - 'name': fields.char('Application Name', size=128, required=True), + 'name': fields.char('Subject / Application Name', size=128, required=True), 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."), 'description': fields.text('Description'), 'email_from': fields.char('Email', size=128, help="These people will receive email."), From c7c84a8695b629aa4158721ac551375a94f833c9 Mon Sep 17 00:00:00 2001 From: "Jitendra Prajapati (OpenERP)" Date: Tue, 28 May 2013 11:13:39 +0530 Subject: [PATCH 004/130] [IMP]hr_recruitment: Improve code with active id and remove the extra field bzr revid: prajapatijitendra7969@gmail.com-20130528054339-1dqdc6ytwu5nyi75 --- addons/hr_recruitment/hr_recruitment.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 4fd0c15202a..364a4a851df 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -322,7 +322,6 @@ class hr_applicant(base_stage, osv.Model): category = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) res['context'] = { - 'default_applicant_id':applicant.id, 'default_partner_ids': applicant.partner_id and [applicant.partner_id.id] or False, 'default_user_id': uid, 'default_name': applicant.name, @@ -576,15 +575,11 @@ class applicant_category(osv.osv): class crm_meeting(osv.Model): _inherit = 'crm.meeting' - _columns = { - 'applicant_id': fields.many2one ('hr.applicant', 'Recruitment') - } def create(self, cr, uid, vals, context=None): meeting_id = super(crm_meeting, self).create(cr, uid, vals, context=context) obj_meeting = self.browse(cr, uid, meeting_id, context=context) - if obj_meeting.applicant_id: - self.pool.get('hr.applicant').log_meeting(cr, uid, [obj_meeting.applicant_id.id] , obj_meeting.name, obj_meeting.date,obj_meeting.duration, context=context) + self.pool.get('hr.applicant').log_meeting(cr, uid, context.get('active_ids',[]), obj_meeting.name, obj_meeting.date, obj_meeting.duration, context=context) return meeting_id # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From cf322c62acd67bef490d2d65cfde90085e648f00 Mon Sep 17 00:00:00 2001 From: "Jitendra Prajapati (OpenERP)" Date: Tue, 28 May 2013 12:00:31 +0530 Subject: [PATCH 005/130] [IMP]hr_holidays:Remove the extra space bzr revid: prajapatijitendra7969@gmail.com-20130528063031-ad6gp45az42q9018 --- addons/hr_holidays/hr_holidays_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 6ff0b2efc9a..43c659847fc 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -14,7 +14,7 @@ - + From 86abcbf3bbc3ac597221c6af5a3d69933da090e5 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Wed, 29 May 2013 13:08:28 +0530 Subject: [PATCH 006/130] [IMP]hr_recruitment: remove label from tree so .py code effect bzr revid: dbr@tinyerp.com-20130529073828-a77uw29ijqpkw2ib --- addons/hr_recruitment/hr_recruitment_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 716c1cba8d5..c3757508b38 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -42,7 +42,7 @@ - + From 516d7fbb4330f9d937eaf458c82411b6e855b8bc Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Mon, 3 Jun 2013 18:09:20 +0530 Subject: [PATCH 007/130] [IMP]hr_recruit : Improve create method and Improve lfilter help as per suggest bzr revid: dbr@tinyerp.com-20130603123920-6spa009pzvrhzxos --- addons/hr_holidays/hr_holidays_view.xml | 2 +- addons/hr_recruitment/hr_recruitment.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 43c659847fc..1d6d2227e84 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -14,7 +14,7 @@ - + diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 364a4a851df..8fc104bf34f 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -577,9 +577,8 @@ class crm_meeting(osv.Model): _inherit = 'crm.meeting' def create(self, cr, uid, vals, context=None): - meeting_id = super(crm_meeting, self).create(cr, uid, vals, context=context) - obj_meeting = self.browse(cr, uid, meeting_id, context=context) - self.pool.get('hr.applicant').log_meeting(cr, uid, context.get('active_ids',[]), obj_meeting.name, obj_meeting.date, obj_meeting.duration, context=context) - return meeting_id + if 'active_ids' in context and 'active_model' in context and context['active_model'] == 'hr.applicant': + self.pool.get('hr.applicant').log_meeting(cr, uid, context['active_ids'], vals['name'], vals['date'], vals['duration'], context=context) + return super(crm_meeting, self).create(cr, uid, vals, context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 33594d9d6755068c2a2bc3855967345e65412174 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Fri, 7 Jun 2013 13:21:24 +0530 Subject: [PATCH 008/130] [IMP]hr_recruite: If in any case no duration tn imp msg as MP suggest bzr revid: dbr@tinyerp.com-20130607075124-x70f2dl5vb6e3s8u --- addons/hr_recruitment/hr_recruitment.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index f1c2f3939c2..affacf10d61 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -308,10 +308,9 @@ class hr_applicant(base_stage, osv.Model): def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): if not duration: - duration = _('unknown') + message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting_date, meeting_subject) else: - duration = str(duration) - message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting_date, meeting_subject, duration) + message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting_date, meeting_subject, str(duration)) return self.message_post(cr, uid, ids, body=message,context=context) def action_makeMeeting(self, cr, uid, ids, context=None): From edff1d957585dc36ec8a67d95df389509fc6511f Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 26 Jul 2013 12:48:34 +0530 Subject: [PATCH 009/130] [IMP] Fixed issue of traceback when we enter non integer number,improved css and help. bzr revid: tpa@tinyerp.com-20130726071834-hd9qyyotf41ja57w --- addons/crm/static/src/css/crm.css | 1 + addons/sale/report/sale_report_view.xml | 4 +-- addons/sale_crm/sale_crm_view.xml | 37 ++++++++++++++++++++--- addons/sale_crm/static/src/js/sale_crm.js | 21 ++++++++----- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index ba4473edc26..53939191eb5 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -29,6 +29,7 @@ margin: 0 !important; position: relative; display: inline-block; + float: left; } .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover { text-decoration: underline !important; diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 69e9cc5e119..4d0ac87bc13 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -47,8 +47,8 @@ - - + + diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index ae21934ce61..09b613a40ec 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -211,6 +211,33 @@ + + Sales Analysis + sale.report + form + tree,graph + {'search_default_year':1,'search_default_month':1,'search_default_User':1,'search_default_Quotations':1,'group_by_no_leaf':1,'group_by':[]} + This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + + + + Sales Analysis + sale.report + form + tree,graph + {'search_default_year':1,'search_default_month':1,'search_default_User':1,'search_default_Sales':1,'group_by_no_leaf':1,'group_by':[]} + This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + + + + Invoices Analysis + account.invoice.report + form + tree,graph + {'search_default_period':1, 'search_default_current':1, 'search_default_user':1, 'search_default_year': 1, 'search_default_category_product':1, 'search_default_customer':1, 'group_by':[], 'group_by_no_leaf':1,} + From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. + + crm.case.section.form crm.case.section @@ -241,9 +268,9 @@ @@ -251,7 +278,7 @@ diff --git a/addons/sale_crm/static/src/js/sale_crm.js b/addons/sale_crm/static/src/js/sale_crm.js index 207ac959af3..77871bb3a50 100644 --- a/addons/sale_crm/static/src/js/sale_crm.js +++ b/addons/sale_crm/static/src/js/sale_crm.js @@ -67,13 +67,17 @@ openerp.sale_crm.GaugeWidget = openerp.web_kanban.AbstractField.extend({ $input.focus() .keydown(function (event) { event.stopPropagation(); - if (event.keyCode == 13 || event.keyCode == 9) { - if ($input.val() != value) { - parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () { - parent.do_reload(); - }); - } else { - $div.remove(); + if(isNaN($input.val())){ + self.do_warn(_t("Wrong value entered!"), _t("Only Integer Value should be valid.")); + $div.remove(); + } else { + if (event.keyCode == 13 || event.keyCode == 9) { + var val = self.parse_client($input.val()); + if (val != value) { + parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () { + parent.do_reload(); + }); + } else { $div.remove();} } } }) @@ -102,6 +106,9 @@ openerp.sale_crm.GaugeWidget = openerp.web_kanban.AbstractField.extend({ } } }, + parse_client: function(value) { + return openerp.web.parse_value(value, { type:"integer" }); + }, }); openerp.web_kanban.fields_registry.add("gage", "openerp.sale_crm.GaugeWidget"); From c588dc46990c67f66662a34e700b295851e086df Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 26 Jul 2013 18:52:42 +0530 Subject: [PATCH 010/130] [IMP] improved typo bzr revid: tpa@tinyerp.com-20130726132242-595e8x43s3jfiov1 --- addons/sale_crm/sale_crm_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 09b613a40ec..4a624db44a1 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -270,7 +270,7 @@ Sales Orders - Revenue of confirmed sales orders per month.
Click to acces the Sales Analysis + Revenue of confirmed sales orders per month.
Click to acces the Sales Analysis.
From 0049c8efe85a67f1a20b82d086a34af3f3b09e64 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 29 Jul 2013 15:41:17 +0530 Subject: [PATCH 011/130] [IMP] Improved code of sale report to inherit and add fields in sale_crm,sale_stock modules, added proper domins on the actions in sales teams kanban bzr revid: tpa@tinyerp.com-20130729101117-6sxwrsh2coz8ygkd --- addons/crm/report/crm_lead_report_view.xml | 12 ++-- addons/sale/report/sale_report.py | 38 ++++++++---- addons/sale/report/sale_report_view.xml | 2 - addons/sale_crm/__openerp__.py | 1 + addons/sale_crm/report/__init__.py | 1 + addons/sale_crm/report/sale_report.py | 37 ++++++++++++ addons/sale_crm/report/sale_report_view.xml | 27 +++++++++ addons/sale_crm/sale_crm_view.xml | 17 +++--- addons/sale_stock/report/sale_report.py | 59 ++----------------- addons/sale_stock/report/sale_report_view.xml | 4 ++ 10 files changed, 117 insertions(+), 81 deletions(-) create mode 100644 addons/sale_crm/report/sale_report.py create mode 100644 addons/sale_crm/report/sale_report_view.xml diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml index 09853c6379e..23594a7f9e1 100644 --- a/addons/crm/report/crm_lead_report_view.xml +++ b/addons/crm/report/crm_lead_report_view.xml @@ -66,9 +66,6 @@ crm.lead.report - - - @@ -113,7 +110,7 @@ - @@ -165,9 +162,9 @@ Leads Analysis crm.lead.report form - {'search_default_year': 1,'search_default_lead': 1, "search_default_user":1, "search_default_this_month":1, 'group_by_no_leaf':1, 'group_by':[]} + {'search_default_year': 1, "search_default_user":1, "search_default_month":1, 'group_by_no_leaf':1, 'group_by':[]} tree,graph - [] + [('type','=', 'lead')] Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.
@@ -187,8 +184,9 @@ Opportunities Analysis crm.lead.report form - {"search_default_year":1,"search_default_opportunity":1, "search_default_user":1,"search_default_this_month":1,'group_by_no_leaf':1,'group_by':[]} + {"search_default_year":1, "search_default_user":1,"search_default_month":1,'group_by_no_leaf':1,'group_by':[]} tree,graph + [('type','=', 'opportunity')] Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline. diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index d5f07f863b9..d2f7849a958 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -60,12 +60,9 @@ class sale_report(osv.osv): } _order = 'date desc' - def init(self, cr): - tools.drop_view_if_exists(cr, 'sale_report') - cr.execute(""" - create or replace view sale_report as ( - select - min(l.id) as id, + def _select(self): + select_str = """ + SELECT min(l.id) as id, l.product_id as product_id, t.uom_id as product_uom, sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty, @@ -84,15 +81,23 @@ class sale_report(osv.osv): t.categ_id as categ_id, s.pricelist_id as pricelist_id, s.project_id as analytic_account_id - from - sale_order s + """ + return select_str + + def _from(self): + from_str = """ + sale_order s join sale_order_line l on (s.id=l.order_id) left join product_product p on (l.product_id=p.id) left join product_template t on (p.product_tmpl_id=t.id) left join product_uom u on (u.id=l.product_uom) left join product_uom u2 on (u2.id=t.uom_id) - group by - l.product_id, + """ + return from_str + + def _group_by(self): + group_by_str = """ + GROUP BY l.product_id, l.product_uom_qty, l.order_id, t.uom_id, @@ -105,7 +110,16 @@ class sale_report(osv.osv): s.state, s.pricelist_id, s.project_id - ) - """) + """ + return group_by_str + + def init(self, cr): + # self._table = sale_report + tools.drop_view_if_exists(cr, self._table) + cr.execute("""CREATE or REPLACE VIEW %s as ( + %s + FROM ( %s ) + %s + )""" % (self._table, self._select(), self._from(), self._group_by())) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 4d0ac87bc13..28a6f83936a 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -50,8 +50,6 @@ - - diff --git a/addons/sale_crm/__openerp__.py b/addons/sale_crm/__openerp__.py index 7db1aa1eff0..25b1fb5f7f4 100644 --- a/addons/sale_crm/__openerp__.py +++ b/addons/sale_crm/__openerp__.py @@ -46,6 +46,7 @@ modules. 'security/sale_crm_security.xml', 'security/ir.model.access.csv', 'report/sale_crm_account_invoice_report_view.xml', + 'report/sale_report_view.xml', ], 'js': [ 'static/lib/justgage.js', diff --git a/addons/sale_crm/report/__init__.py b/addons/sale_crm/report/__init__.py index d7c6d373468..d86b3e66de9 100644 --- a/addons/sale_crm/report/__init__.py +++ b/addons/sale_crm/report/__init__.py @@ -20,6 +20,7 @@ ############################################################################## import sales_crm_account_invoice_report +import sale_report # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/report/sale_report.py b/addons/sale_crm/report/sale_report.py new file mode 100644 index 00000000000..f8fae28f600 --- /dev/null +++ b/addons/sale_crm/report/sale_report.py @@ -0,0 +1,37 @@ + # -*- coding: utf-8 -*- + ############################################################################## + # + # OpenERP, Open Source Management Solution + # Copyright (C) 2004-2010 Tiny SPRL (). + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU Affero General Public License as + # published by the Free Software Foundation, either version 3 of the + # License, or (at your option) any later version. + # + # This program is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU Affero General Public License for more details. + # + # You should have received a copy of the GNU Affero General Public License + # along with this program. If not, see . + # + ############################################################################## + +from openerp.osv import fields, osv +from openerp import tools + +class sale_report(osv.osv): + _inherit = "sale.report" + _columns = { + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), + } + + def _select(self): + return super(sale_report, self)._select() + ", s.section_id as section_id" + + def _group_by(self): + return super(sale_report, self)._group_by() + ", s.section_id" + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/report/sale_report_view.xml b/addons/sale_crm/report/sale_report_view.xml new file mode 100644 index 00000000000..f054634cf1c --- /dev/null +++ b/addons/sale_crm/report/sale_report_view.xml @@ -0,0 +1,27 @@ + + + + + sale.report.tree.sale.crm + sale.report + + + + + + + + + + sale.report.search.sale.crm + sale.report + + + + + + + + + + \ No newline at end of file diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 4a624db44a1..7e0d6b27a3a 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -212,12 +212,13 @@ - Sales Analysis + Quotations Analysis sale.report form tree,graph - {'search_default_year':1,'search_default_month':1,'search_default_User':1,'search_default_Quotations':1,'group_by_no_leaf':1,'group_by':[]} - This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + [('state','=','draft'),('section_id', '=', active_id)] + {'search_default_order_month':1,'search_default_User':1} + This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. @@ -225,8 +226,9 @@ sale.report form tree,graph - {'search_default_year':1,'search_default_month':1,'search_default_User':1,'search_default_Sales':1,'group_by_no_leaf':1,'group_by':[]} - This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + [('state','not in',('draft','done','cancel')),('section_id', '=', active_id)] + {'search_default_order_month':1,'search_default_User':1} + This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. @@ -234,7 +236,8 @@ account.invoice.report form tree,graph - {'search_default_period':1, 'search_default_current':1, 'search_default_user':1, 'search_default_year': 1, 'search_default_category_product':1, 'search_default_customer':1, 'group_by':[], 'group_by_no_leaf':1,} + [('section_id', '=', active_id)] + {'search_default_month':1, 'search_default_user':1} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. @@ -288,7 +291,7 @@ Quotations - Revenue of created quotations per month.
Click to see a detailed analysis of sales. + Revenue of created quotations per month.
Click to see a detailed analysis.
diff --git a/addons/sale_stock/report/sale_report.py b/addons/sale_stock/report/sale_report.py index 0feb3ee2392..7a8234fa7fa 100644 --- a/addons/sale_stock/report/sale_report.py +++ b/addons/sale_stock/report/sale_report.py @@ -39,58 +39,11 @@ class sale_report(osv.osv): ('cancel', 'Cancelled') ], 'Order Status', readonly=True), } - - def init(self, cr): - tools.drop_view_if_exists(cr, 'sale_report') - cr.execute(""" - create or replace view sale_report as ( - select - min(l.id) as id, - l.product_id as product_id, - t.uom_id as product_uom, - sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty, - sum(l.product_uom_qty * l.price_unit * (100.0-l.discount) / 100.0) as price_total, - 1 as nbr, - s.date_order as date, - s.date_confirm as date_confirm, - to_char(s.date_order, 'YYYY') as year, - to_char(s.date_order, 'MM') as month, - to_char(s.date_order, 'YYYY-MM-DD') as day, - s.partner_id as partner_id, - s.user_id as user_id, - s.company_id as company_id, - s.warehouse_id as warehouse_id, - extract(epoch from avg(date_trunc('day',s.date_confirm)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay, - s.state, - t.categ_id as categ_id, - s.shipped, - s.shipped::integer as shipped_qty_1, - s.pricelist_id as pricelist_id, - s.project_id as analytic_account_id - from - sale_order s - join sale_order_line l on (s.id=l.order_id) - left join product_product p on (l.product_id=p.id) - left join product_template t on (p.product_tmpl_id=t.id) - left join product_uom u on (u.id=l.product_uom) - left join product_uom u2 on (u2.id=t.uom_id) - group by - l.product_id, - l.product_uom_qty, - l.order_id, - t.uom_id, - t.categ_id, - s.date_order, - s.date_confirm, - s.partner_id, - s.user_id, - s.warehouse_id, - s.company_id, - s.state, - s.shipped, - s.pricelist_id, - s.project_id - ) - """) + + def _select(self): + return super(sale_report, self)._select() + ", s.warehouse_id as warehouse_id, s.shipped, s.shipped::integer as shipped_qty_1" + + def _group_by(self): + return super(sale_report, self)._group_by() + ", s.warehouse_id, s.shipped" # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_stock/report/sale_report_view.xml b/addons/sale_stock/report/sale_report_view.xml index 6642bffcede..486cf3a7b11 100644 --- a/addons/sale_stock/report/sale_report_view.xml +++ b/addons/sale_stock/report/sale_report_view.xml @@ -17,6 +17,10 @@ sale.report + + + + From eb700cf29beb3865e26fb4f8c53469f2cac40eb7 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 30 Jul 2013 16:38:22 +0530 Subject: [PATCH 012/130] [IMP] Added company_id and currency_symbole field and improved related code to show tooltip as per requrement and 5 sec late,improved css to show proper cursore, improved actions & domains for proper results. bzr revid: tpa@tinyerp.com-20130730110822-zzioh92in4azqay8 --- addons/crm/crm.py | 12 ++++- addons/crm/crm_case_section_view.xml | 53 ++++++++++++++++++-- addons/crm/static/src/css/crm.css | 3 +- addons/crm/static/src/js/crm_case_section.js | 5 +- addons/sale_crm/report/sale_report_view.xml | 2 +- addons/sale_crm/sale_crm_view.xml | 8 +-- 6 files changed, 69 insertions(+), 14 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index d1194508b71..876814fe5b7 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -156,6 +156,13 @@ class crm_case_section(osv.osv): res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date'], 'planned_revenue', 'create_date', context=context) return res + def _get_currency_symbol(self, cr, uid, ids, name, arg, context=None): + """Set currency symbole of company currency""" + res = {} + for sales_team in self.browse(cr, uid, ids, context=context): + res[sales_team.id] = sales_team.company_id.currency_id.symbol + return res + _columns = { 'name': fields.char('Sales Team', size=64, required=True, translate=True), 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True), @@ -184,7 +191,9 @@ class crm_case_section(osv.osv): string='Open Leads per Month'), 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', - string='Planned Revenue per Month') + string='Planned Revenue per Month'), + 'company_id': fields.many2one('res.company', 'Company', select=1), + 'currency_symbol': fields.function(_get_currency_symbol, string='Currency Symbol', method=True, type='char'), } def _get_stage_common(self, cr, uid, context): @@ -195,6 +204,7 @@ class crm_case_section(osv.osv): 'active': 1, 'stage_ids': _get_stage_common, 'use_leads': True, + 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case.section', context=c), } _sql_constraints = [ diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index c957311b47f..c8d4cb01c41 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -13,7 +13,6 @@ { 'search_default_section_id': [active_id], - 'search_default_open': 1, 'default_section_id': active_id, 'default_type': 'lead', 'stage_type': 'lead', @@ -43,7 +42,6 @@ { 'search_default_section_id': [active_id], - 'search_default_assigned_to_me': 1, 'default_section_id': active_id, 'stage_type': 'opportunity', 'default_type': 'opportunity', @@ -64,6 +62,52 @@ + + Leads Analysis + crm.lead.report + form + {"search_default_month":1} + tree,graph + [('type','=', 'lead'),('section_id', '=', active_id)] + Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis. + + + + tree + + + + + + graph + + + + + + Opportunities Analysis + crm.lead.report + form + {"search_default_month":1} + tree,graph + [('type','=', 'opportunity'), ('section_id', '=', active_id)] + Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline. + + + + + tree + + + + + + + graph + + + + @@ -80,6 +124,7 @@ + diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index 53939191eb5..680c3a9bd88 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -1,5 +1,6 @@ .openerp .oe_kanban_view .oe_kanban_crm_salesteams { width: 345px; + cursor: default; } .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars { text-align: right; @@ -61,5 +62,5 @@ .openerp .oe_kanban_view .oe_sparkline_bar { height: 20px; - width: 36px; + width: 36px !important; } \ No newline at end of file diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index 2b4ca741d75..f16dc31f634 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -8,7 +8,6 @@ openerp.crm = function(openerp) { } }, }); - openerp.crm.SparklineBarWidget = openerp.web_kanban.AbstractField.extend({ className: "oe_sparkline_bar", start: function() { @@ -20,12 +19,12 @@ openerp.crm = function(openerp) { self.$el.sparkline(value, { type: 'bar', barWidth: 5, - tooltipFormat: '{{offset:offset}} {{value}}', + tooltipFormat: _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? 'Leads' : self.getParent().record.currency_symbol.raw_value), tooltipValueLookups: { 'offset': tooltips }, }); - self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); + self.$el.tipsy({'delayIn': 3000, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); }, 0); }, }); diff --git a/addons/sale_crm/report/sale_report_view.xml b/addons/sale_crm/report/sale_report_view.xml index f054634cf1c..99ab196a115 100644 --- a/addons/sale_crm/report/sale_report_view.xml +++ b/addons/sale_crm/report/sale_report_view.xml @@ -17,7 +17,7 @@ sale.report - + diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 7e0d6b27a3a..667d5f4f412 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -217,7 +217,7 @@ form tree,graph [('state','=','draft'),('section_id', '=', active_id)] - {'search_default_order_month':1,'search_default_User':1} + {'search_default_order_month':1} This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. @@ -227,7 +227,7 @@ form tree,graph [('state','not in',('draft','done','cancel')),('section_id', '=', active_id)] - {'search_default_order_month':1,'search_default_User':1} + {'search_default_order_month':1} This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. @@ -237,7 +237,7 @@ form tree,graph [('section_id', '=', active_id)] - {'search_default_month':1, 'search_default_user':1} + {'search_default_month':1} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. @@ -299,7 +299,7 @@
Invoiced - Forecast + Forecast

Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance. From 14522ba8a8ff8787fe0b2620f3cab91c5428c1ae Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 30 Jul 2013 16:43:37 +0530 Subject: [PATCH 013/130] [IMP] improved code to show only paid invoices on click action of graph bzr revid: tpa@tinyerp.com-20130730111337-nw7s9i2dad7s4o7k --- addons/sale_crm/sale_crm_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 667d5f4f412..fa44cd7e821 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -236,7 +236,7 @@ account.invoice.report form tree,graph - [('section_id', '=', active_id)] + [('state','=','paid'),('section_id', '=', active_id)] {'search_default_month':1} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. From 4a826985a28023c3160c14d3a80dd5e9e37df9e9 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 30 Jul 2013 17:00:59 +0530 Subject: [PATCH 014/130] [IMP] improved code to show Lead(s) insted of Leads and make it traslatable also bzr revid: tpa@tinyerp.com-20130730113059-03mvlq21b3dlzknz --- addons/crm/static/src/js/crm_case_section.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index f16dc31f634..a281e4bb0f1 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -1,4 +1,5 @@ openerp.crm = function(openerp) { +var _t = openerp.web._t; openerp.web_kanban.KanbanRecord.include({ on_card_clicked: function() { if (this.view.dataset.model === 'crm.case.section') { @@ -19,7 +20,7 @@ openerp.crm = function(openerp) { self.$el.sparkline(value, { type: 'bar', barWidth: 5, - tooltipFormat: _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? 'Leads' : self.getParent().record.currency_symbol.raw_value), + tooltipFormat: _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? _t('Lead(s)') : self.getParent().record.currency_symbol.raw_value), tooltipValueLookups: { 'offset': tooltips }, From 9010169be290874fdba68543d5f185d7b2118653 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 30 Jul 2013 18:55:39 +0530 Subject: [PATCH 015/130] [IMP] sale_crm: improved filter in search view bzr revid: tpa@tinyerp.com-20130730132539-zgh8y85sz83ly5bt --- addons/sale_crm/report/sale_report_view.xml | 2 +- addons/sale_crm/sale_crm_view.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sale_crm/report/sale_report_view.xml b/addons/sale_crm/report/sale_report_view.xml index 99ab196a115..8661d13c470 100644 --- a/addons/sale_crm/report/sale_report_view.xml +++ b/addons/sale_crm/report/sale_report_view.xml @@ -19,7 +19,7 @@ - + diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index fa44cd7e821..73057e63bfc 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -226,7 +226,7 @@ sale.report form tree,graph - [('state','not in',('draft','done','cancel')),('section_id', '=', active_id)] + [('state','not in',('draft','sent','cancel')),('section_id', '=', active_id)] {'search_default_order_month':1} This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. From d2777a494fa3af402b41a88ad21a2545852174f3 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 31 Jul 2013 18:50:56 +0530 Subject: [PATCH 016/130] [IMP] improved code to show opportunity revenue in current user currency in multy currency senario for sales team sparkline bars bzr revid: tpa@tinyerp.com-20130731132056-ns7wl04r33grznrs --- addons/crm/crm.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 876814fe5b7..137f619f2e3 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -111,7 +111,7 @@ class crm_case_section(osv.osv): def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) - def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, type=None, context=None): """ Generic method to generate data for bar chart values using SparklineBarWidget. This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). @@ -135,8 +135,19 @@ class crm_case_section(osv.osv): group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) for group in group_obj: group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT) - month_delta = relativedelta.relativedelta(month_begin, group_begin_date) - section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')} + month = self._period_number - (relativedelta.relativedelta(month_begin, group_begin_date).months + 1) + section_result[month] = {'value': 0, 'tooltip': group_begin_date.strftime('%B')} + inner_groupby = (group.get('__context', {})).get('group_by',[]) + if type == "Lead" or not inner_groupby: + section_result[month]['value'] = group.get(value_field, 0) + continue + if inner_groupby: + groupby_company = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) + for groupby in groupby_company: + company = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context) + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + value = self.pool.get('res.currency').compute(cr, uid, company.currency_id.id, user.company_id.currency_id.id, groupby.get(value_field, 0)) + section_result[month]['value'] = section_result[month]['value'] + value return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): @@ -151,16 +162,16 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() lead_domain = [('type', '=', 'lead'), ('section_id', '=', id), ('create_date', '>=', groupby_begin)] - res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context) + res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], type="Lead", context=context) opp_domain = [('type', '=', 'opportunity'), ('section_id', '=', id), ('create_date', '>=', groupby_begin)] - res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date'], 'planned_revenue', 'create_date', context=context) + res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date', 'company_id'], 'planned_revenue', ['create_date', 'company_id'], context=context) return res def _get_currency_symbol(self, cr, uid, ids, name, arg, context=None): """Set currency symbole of company currency""" res = {} - for sales_team in self.browse(cr, uid, ids, context=context): - res[sales_team.id] = sales_team.company_id.currency_id.symbol + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + res = dict.fromkeys(ids, user.company_id.currency_id.symbol) return res _columns = { @@ -192,7 +203,6 @@ class crm_case_section(osv.osv): 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', string='Planned Revenue per Month'), - 'company_id': fields.many2one('res.company', 'Company', select=1), 'currency_symbol': fields.function(_get_currency_symbol, string='Currency Symbol', method=True, type='char'), } @@ -204,7 +214,6 @@ class crm_case_section(osv.osv): 'active': 1, 'stage_ids': _get_stage_common, 'use_leads': True, - 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case.section', context=c), } _sql_constraints = [ From 7f6daa12f92c07351f349c338d2ae1666b3b3c6e Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 5 Aug 2013 17:05:49 +0530 Subject: [PATCH 017/130] [IMP] Improved report of lead/apportunity analysis to show in user currency and improved code to show target and forcast amount in current user currency. bzr revid: tpa@tinyerp.com-20130805113549-9vmwd2v0zsjwlcms --- addons/crm/report/crm_lead_report.py | 32 +++++++++++++++++++--- addons/crm/report/crm_lead_report_view.xml | 8 +++--- addons/sale_crm/sale_crm.py | 32 ++++++++++++++++++++-- addons/sale_crm/sale_crm_demo.xml | 8 +++--- addons/sale_crm/sale_crm_view.xml | 4 +-- 5 files changed, 67 insertions(+), 17 deletions(-) diff --git a/addons/crm/report/crm_lead_report.py b/addons/crm/report/crm_lead_report.py index d12facd66a0..23347988573 100644 --- a/addons/crm/report/crm_lead_report.py +++ b/addons/crm/report/crm_lead_report.py @@ -53,6 +53,19 @@ class crm_lead_report(osv.osv): _description = "CRM Lead Analysis" _rec_name = 'deadline_day' + def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): + """Compute the amounts in the currency of the user + """ + res = {} + currency_obj = self.pool.get('res.currency') + user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id + for item in self.browse(cr, uid, ids, context=context): + res[item.id] = { + 'user_currency_planned_revenue': currency_obj.compute(cr, uid, item.company_id.currency_id.id, user_currency_id, item.planned_revenue, context=context), + 'user_currency_probable_revenue': currency_obj.compute(cr, uid, item.company_id.currency_id.id, user_currency_id, item.probable_revenue, context=context), + } + return res + _columns = { # grouping fields based on Deadline Date 'deadline_year': fields.char('Ex. Closing Year', size=10, readonly=True, help="Expected closing year"), @@ -83,7 +96,9 @@ class crm_lead_report(osv.osv): 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"), 'planned_revenue': fields.float('Planned Revenue',digits=(16,2),readonly=True), + 'user_currency_planned_revenue': fields.function(_compute_amounts_in_user_currency, string="Planned Revenue", type='float',digits=(16,2), multi="_compute_amounts", readonly=True), 'probable_revenue': fields.float('Probable Revenue', digits=(16,2),readonly=True), + 'user_currency_probable_revenue': fields.function(_compute_amounts_in_user_currency, string="Probable Revenue", type='float',digits=(16,2), multi="_compute_amounts",readonly=True), 'stage_id': fields.many2one ('crm.case.stage', 'Stage', readonly=True, domain="[('section_ids', '=', section_id)]"), 'partner_id': fields.many2one('res.partner', 'Partner' , readonly=True), 'nbr': fields.integer('# of Cases', readonly=True), @@ -94,12 +109,21 @@ class crm_lead_report(osv.osv): ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), } - - - + + #FIX:To show sum of values of function fields in groupby + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): + res = super(crm_lead_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) + for group in res: + group['user_currency_probable_revenue'] = 0 + group['user_currency_planned_revenue'] = 0 + group_ids = self.search(cr, uid, group.get('__domain'),context=context) + record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) + for id, rec in record.iteritems(): + group['user_currency_planned_revenue'] += rec['user_currency_planned_revenue'] + group['user_currency_probable_revenue'] += rec['user_currency_probable_revenue'] + return res def init(self, cr): - """ CRM Lead Report @param cr: the current row, from the database cursor diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml index 23594a7f9e1..a9b7bc73aab 100644 --- a/addons/crm/report/crm_lead_report_view.xml +++ b/addons/crm/report/crm_lead_report_view.xml @@ -28,7 +28,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -146,12 +146,12 @@ - + - + diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 97c5f4579e0..634df104d34 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -67,12 +67,25 @@ class crm_case_section(osv.osv): res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) return res + def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): + """Compute the amounts in the currency of the user """ + res = {} + currency_obj = self.pool.get('res.currency') + user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id + for item in self.browse(cr, uid, ids, context=context): + base_currency_id = item.user_id.company_id.currency_id.id if item.user_id else item.create_uid.company_id.currency_id.id + res[item.id] = { + 'invoiced_forecast': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.base_invoiced_forecast, context=context), + 'invoiced_target': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.base_invoiced_target, context=context), + } + return res + _columns = { - 'invoiced_forecast': fields.integer(string='Invoice Forecast', + 'invoiced_forecast': fields.function(_compute_amounts_in_user_currency, string='Invoice Forecast',type='integer', multi="_compute_amounts", help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" "team should invoice this month. It is used to compute the progression ratio \n" " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.integer(string='Invoice Target', + 'invoiced_target': fields.function(_compute_amounts_in_user_currency,string='Invoice Target',type='integer', multi="_compute_amounts", help="Target of invoice revenue for the current month. This is the amount the sales \n" "team estimates to be able to invoice this month."), 'monthly_quoted': fields.function(_get_sale_orders_data, @@ -84,11 +97,24 @@ class crm_case_section(osv.osv): 'monthly_invoiced': fields.function(_get_invoices_data, type='string', readonly=True, string='Rate of sent invoices per duration'), + 'base_invoiced_forecast': fields.integer(string="Invoice Forecast"), + 'base_invoiced_target': fields.integer(string="Invoice Target"), + 'create_uid': fields.many2one('res.users', 'Create User'), } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': value}, context=context) + return self.write(cr, uid, [id], {'invoiced_forecast': int(value)}, context=context) + def write(self, cr, uid, ids, vals, context=None): + currency_obj = self.pool.get('res.currency') + user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id + for item in self.browse(cr, uid, ids, context=context): + base_currency_id = item.user_id.company_id.currency_id.id if item.user_id else item.create_uid.company_id.currency_id.id + if vals.get('invoiced_forecast'): + vals['base_invoiced_forecast'] = currency_obj.compute(cr, uid, user_currency_id, base_currency_id, vals['invoiced_forecast'], context=context) + if vals.get('invoiced_target'): + vals['base_invoiced_target'] = currency_obj.compute(cr, uid, user_currency_id, base_currency_id, vals['invoiced_target'], context=context) + return super(crm_case_section, self).write(cr, uid, ids, vals, context=context) class res_users(osv.Model): _inherit = 'res.users' diff --git a/addons/sale_crm/sale_crm_demo.xml b/addons/sale_crm/sale_crm_demo.xml index 0c2822719dd..3eff4061aa1 100644 --- a/addons/sale_crm/sale_crm_demo.xml +++ b/addons/sale_crm/sale_crm_demo.xml @@ -3,15 +3,15 @@ - 52700 - 60000 + 52700 + 60000 Indirect Sales IM - 36000 - 40000 + 36000 + 40000 diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 73057e63bfc..22dff55531d 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -248,8 +248,8 @@ - - + + From a24a72e3d3f2bdc1b0a1968e9796c81d51d0a6ae Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 6 Aug 2013 12:04:16 +0530 Subject: [PATCH 018/130] [IMP] improved view by css bzr revid: tpa@tinyerp.com-20130806063416-8rrqtf3qb7j01sb2 --- addons/crm/static/src/css/crm.css | 11 +++++++++-- addons/sale_crm/sale_crm_view.xml | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index 680c3a9bd88..8e8d95fe2f7 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -1,6 +1,7 @@ .openerp .oe_kanban_view .oe_kanban_crm_salesteams { width: 345px; cursor: default; + min-height: 240px !important; } .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars { text-align: right; @@ -55,12 +56,18 @@ font-size: 10px; } -.openerp .oe_kanban_view .oe_justgage { +.openerp .oe_kanban_view .oe_salesteams_justgage { color: black; display: inline-block; + bottom: 0; } .openerp .oe_kanban_view .oe_sparkline_bar { height: 20px; width: 36px !important; -} \ No newline at end of file +} + +.openerp .oe_kanban_view .oe_salesteams_help { + display: inline-block; +} + diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 22dff55531d..1546fbc27e6 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -297,11 +297,11 @@
-
+
Invoiced Forecast
-
+

Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
From 9daabe375448d13f03b9be7f154f01b9ee349108 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 6 Aug 2013 14:41:24 +0530 Subject: [PATCH 019/130] [IMP] show sales analysis based on cretion date for quatation and for sales based on confirm date. bzr revid: tpa@tinyerp.com-20130806091124-650f98bm7d1e004e --- addons/crm/crm_demo.xml | 2 ++ addons/crm/report/crm_lead_report.py | 11 ++++++----- addons/sale/report/sale_report.py | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml index 2858250e86a..2cf7b104a3f 100644 --- a/addons/crm/crm_demo.xml +++ b/addons/crm/crm_demo.xml @@ -9,11 +9,13 @@ Indirect Sales IM + Marketing SPD + diff --git a/addons/crm/report/crm_lead_report.py b/addons/crm/report/crm_lead_report.py index 23347988573..ed4b6c4310a 100644 --- a/addons/crm/report/crm_lead_report.py +++ b/addons/crm/report/crm_lead_report.py @@ -116,11 +116,12 @@ class crm_lead_report(osv.osv): for group in res: group['user_currency_probable_revenue'] = 0 group['user_currency_planned_revenue'] = 0 - group_ids = self.search(cr, uid, group.get('__domain'),context=context) - record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) - for id, rec in record.iteritems(): - group['user_currency_planned_revenue'] += rec['user_currency_planned_revenue'] - group['user_currency_probable_revenue'] += rec['user_currency_probable_revenue'] + if group.get('__domain'): + group_ids = self.search(cr, uid, group.get('__domain'),context=context) + record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) + for id, rec in record.iteritems(): + group['user_currency_planned_revenue'] += rec['user_currency_planned_revenue'] + group['user_currency_probable_revenue'] += rec['user_currency_probable_revenue'] return res def init(self, cr): diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index d2f7849a958..9b2728c1668 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -70,9 +70,9 @@ class sale_report(osv.osv): 1 as nbr, s.date_order as date, s.date_confirm as date_confirm, - to_char(s.date_order, 'YYYY') as year, - to_char(s.date_order, 'MM') as month, - to_char(s.date_order, 'YYYY-MM-DD') as day, + case when (s.state='draft') then to_char(s.date_order, 'YYYY') else to_char(s.date_confirm, 'YYYY') end as year, + case when (s.state='draft') then to_char(s.date_order, 'MM') else to_char(s.date_confirm, 'MM') end as month, + case when (s.state='draft') then to_char(s.date_order, 'YYYY-MM-DD') else to_char(s.date_confirm, 'YYYY-MM-DD') end as day, s.partner_id as partner_id, s.user_id as user_id, s.company_id as company_id, From 5323abfafce039fe14162cc1e1c4e3cd3df2d6ad Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP Trainee)" Date: Tue, 6 Aug 2013 14:44:13 +0530 Subject: [PATCH 020/130] [IMP] Improved css for kanbanview of Sales Teams bzr revid: chiragdd7@gmail.com-20130806091413-yf8xyc5xvcpcc3m2 --- addons/crm/static/src/css/crm.css | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index 8e8d95fe2f7..ddb60fb2e98 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -57,6 +57,7 @@ } .openerp .oe_kanban_view .oe_salesteams_justgage { + position: absolute; color: black; display: inline-block; bottom: 0; From 90983ace491a1d73c500b31f74d4762927bbcdfc Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 6 Aug 2013 16:57:13 +0530 Subject: [PATCH 021/130] [IMP] add demo user in direct sales team also. bzr revid: tpa@tinyerp.com-20130806112713-l11uio6f6sgzzfc5 --- addons/crm/crm_demo.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml index 2cf7b104a3f..82bdcd8fcd9 100644 --- a/addons/crm/crm_demo.xml +++ b/addons/crm/crm_demo.xml @@ -6,6 +6,10 @@ + + + + Indirect Sales IM From ac12aa981a4fc7c2dbbabaa65831f5415751a128 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP Trainee)" Date: Thu, 8 Aug 2013 11:19:40 +0530 Subject: [PATCH 022/130] [IMP] Improved Sale Analysis report bzr revid: chiragdd7@gmail.com-20130808054940-b1a8ikj4hd49rpru --- addons/sale/report/sale_report.py | 22 ++++++++++++++++++++++ addons/sale/report/sale_report_view.xml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index d2f7849a958..42a3c3d7cbf 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -27,6 +27,17 @@ class sale_report(osv.osv): _description = "Sales Orders Statistics" _auto = False _rec_name = 'date' + def _compute_total_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): + """Compute the Price total in the currency of the user + """ + res = {} + currency_obj = self.pool.get('res.currency') + user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id + for item in self.browse(cr, uid, ids, context=context): + res[item.id] = { + 'user_price_total': currency_obj.compute(cr, uid, item.pricelist_id.currency_id.id, user_currency_id, item.price_total, context=context), + } + return res _columns = { 'date': fields.date('Date Order', readonly=True), 'date_confirm': fields.date('Date Confirm', readonly=True), @@ -57,6 +68,7 @@ class sale_report(osv.osv): ], 'Order Status', readonly=True), 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', readonly=True), 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), + 'user_price_total': fields.function(_compute_total_in_user_currency, string="Total Price", type='float',digits=(16,2), multi="_compute_amounts", readonly=True), } _order = 'date desc' @@ -113,6 +125,16 @@ class sale_report(osv.osv): """ return group_by_str + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): + res = super(sale_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) + for group in res: + group['user_price_total'] = 0 + group_ids = self.search(cr, uid, group.get('__domain'),context=context) + record = self._compute_total_in_user_currency(cr, uid, group_ids, context=context) + for id, rec in record.iteritems(): + group['user_price_total'] += rec['user_price_total'] + return res + def init(self, cr): # self._table = sale_report tools.drop_view_if_exists(cr, self._table) diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 28a6f83936a..1b22f9b75d6 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -20,7 +20,7 @@ - + From c80f7589895fd2b53c4d32e73bc432648e46490c Mon Sep 17 00:00:00 2001 From: "Sunil Sharma (OpenERP Trainee)" Date: Thu, 8 Aug 2013 11:23:54 +0530 Subject: [PATCH 023/130] [IMP]:Account:account_invoice_report.py bzr revid: sunilsharma.sharma07@gmail.com-20130808055354-0379xbn60gbcmvsc --- addons/account/report/account_invoice_report.py | 15 ++++++++++++++- .../report/account_invoice_report_view.xml | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index d8dc45f9a3b..8674e71e109 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -29,7 +29,7 @@ class account_invoice_report(osv.osv): _auto = False _rec_name = 'date' - def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): + def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): """Compute the amounts in the currency of the user """ if context is None: @@ -103,6 +103,19 @@ class account_invoice_report(osv.osv): } _order = 'date desc' + #FIX:To show sum of values of function fields in groupby + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): + res = super(account_invoice_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) + for group in res: + group['user_currency_price_total'] = 0 + group_ids = self.search(cr, uid, group.get('__domain'),context=context) + record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) + for id, rec in record.iteritems(): + group['user_currency_price_total'] += rec['user_currency_price_total'] + + return res + + def _select(self): select_str = """ SELECT sub.id, sub.date, sub.year, sub.month, sub.day, sub.product_id, sub.partner_id, sub.country_id, diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index 5f38db5e71f..4c297fd52e8 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -30,7 +30,7 @@ - + @@ -41,7 +41,7 @@ - + From 3fa52479281b91684075c3e307a80616b57d8d49 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Thu, 8 Aug 2013 14:46:45 +0530 Subject: [PATCH 024/130] [IMP] improved code to show invoice analysis report amounts in current user currency. bzr revid: tpa@tinyerp.com-20130808091645-8loajee0ss41d1u4 --- addons/account/report/account_invoice_report.py | 12 ++++++++++++ .../account/report/account_invoice_report_view.xml | 4 ++-- addons/crm/static/src/css/crm.css | 1 - 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index d8dc45f9a3b..6fac59607c2 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -103,6 +103,18 @@ class account_invoice_report(osv.osv): } _order = 'date desc' + #FIX:To show sum of values of function fields in groupby + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): + res = super(account_invoice_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) + for group in res: + group['user_currency_price_total'] = 0 + if group.get('__domain'): + group_ids = self.search(cr, uid, group.get('__domain'),context=context) + record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) + for id, rec in record.iteritems(): + group['user_currency_price_total'] += rec['user_currency_price_total'] + return res + def _select(self): select_str = """ SELECT sub.id, sub.date, sub.year, sub.month, sub.day, sub.product_id, sub.partner_id, sub.country_id, diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index 5f38db5e71f..4c297fd52e8 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -30,7 +30,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index 8e8d95fe2f7..09d8bb30229 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -1,7 +1,6 @@ .openerp .oe_kanban_view .oe_kanban_crm_salesteams { width: 345px; cursor: default; - min-height: 240px !important; } .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars { text-align: right; From 38f4192a91774a9eb957134d5eb8c43ba26b5e91 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Thu, 8 Aug 2013 14:54:29 +0530 Subject: [PATCH 025/130] [IMP] pass default value for arguments bzr revid: tpa@tinyerp.com-20130808092429-h1nvz0gtn5mqzm5w --- addons/account/report/account_invoice_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 6fac59607c2..ef9478faf0a 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -29,7 +29,7 @@ class account_invoice_report(osv.osv): _auto = False _rec_name = 'date' - def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): + def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): """Compute the amounts in the currency of the user """ if context is None: From 418c77da3026df95004ce98eca8a305fc04c0f42 Mon Sep 17 00:00:00 2001 From: "Sunil Sharma (OpenERP Trainee)" Date: Tue, 13 Aug 2013 12:03:15 +0530 Subject: [PATCH 026/130] [IMP]:User_Currency: bzr revid: sunilsharma.sharma07@gmail.com-20130813063315-i45q7dykm6sp3a94 --- addons/crm/crm.py | 10 ++++++++-- addons/sale_crm/sale_crm.py | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 137f619f2e3..0ac2e887e72 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -144,9 +144,15 @@ class crm_case_section(osv.osv): if inner_groupby: groupby_company = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) for groupby in groupby_company: - company = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context) + if inner_groupby[0] == 'company_id': + company = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context) + base_currency_id = company.currency_id.id + elif inner_groupby[0] == 'pricelist_id': + base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id + else : + base_currency_id = groupby['__domain'][0][2] user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - value = self.pool.get('res.currency').compute(cr, uid, company.currency_id.id, user.company_id.currency_id.id, groupby.get(value_field, 0)) + value = self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, groupby.get(value_field, 0)) section_result[month]['value'] = section_result[month]['value'] + value return section_result diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 634df104d34..5333016b620 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -52,9 +52,9 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() created_domain = [('section_id', '=', id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', groupby_begin)] - res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) + res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order','pricelist_id'], 'amount_total', ['date_order','pricelist_id'], context=context) validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', groupby_begin)] - res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm'], 'amount_total', 'date_confirm', context=context) + res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm','pricelist_id'], 'amount_total', ['date_confirm','pricelist_id'], context=context) return res def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): @@ -64,7 +64,7 @@ class crm_case_section(osv.osv): groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) for id in ids: created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', groupby_begin)] - res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) + res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date','currency_id'], 'price_total', ['date','currency_id'], context=context) return res def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): From 73ff1e657dcd2f4e4a53cf040692e9bd20c73775 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 16 Aug 2013 18:37:10 +0530 Subject: [PATCH 027/130] [FIX] show error messge when we move recored in kanban. bzr revid: tpa@tinyerp.com-20130816130710-ec5x1k7tn86dejk1 --- addons/web_kanban/static/src/js/kanban.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 6f47f9c128d..7dd7c56a0b6 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -436,7 +436,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ new_group.do_save_sequences(); }).fail(function(error, evt) { evt.preventDefault(); - alert(_t("An error has occured while moving the record to this group: ") + data.message); + alert(_t("An error has occured while moving the record to this group: ") + error.data.message); self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails }); } From a3508b45d7a23053a95468a1a9560951283378bf Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 16 Aug 2013 18:57:54 +0530 Subject: [PATCH 028/130] [IMP] improved code to show invoice bar value bzr revid: tpa@tinyerp.com-20130816132754-tgnd3xoi9v3o5711 --- addons/crm/crm.py | 2 -- addons/sale_crm/sale_crm.py | 4 ++-- addons/sale_crm/sale_crm_view.xml | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 0f9759a77d5..a9479000b3e 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -136,8 +136,6 @@ class crm_case_section(osv.osv): base_currency_id = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id elif inner_groupby[0] == 'pricelist_id': base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id - else : - base_currency_id = groupby['__domain'][0][2] user = self.pool.get('res.users').browse(cr, uid, uid, context=context) value = self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, groupby.get(value_field, 0)) section_result[month]['value'] = section_result[month]['value'] + value diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 5333016b620..c48c874b48d 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -64,7 +64,7 @@ class crm_case_section(osv.osv): groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) for id in ids: created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', groupby_begin)] - res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date','currency_id'], 'price_total', ['date','currency_id'], context=context) + res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date'], 'user_currency_price_total', 'date', context=context) return res def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): @@ -103,7 +103,7 @@ class crm_case_section(osv.osv): } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': int(value)}, context=context) + return self.write(cr, uid, [id], {'invoiced_forecast': int(float(value))}, context=context) def write(self, cr, uid, ids, vals, context=None): currency_obj = self.pool.get('res.currency') diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 0526efebd7d..3656fa0719a 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -236,7 +236,7 @@ account.invoice.report form tree,graph - [('state','=','paid'),('section_id', '=', active_id)] + [('section_id', '=', active_id)] {'search_default_month':1} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. From f76b2078aa312b952a97552add00fc24d46fdaa3 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 19 Aug 2013 11:15:16 +0530 Subject: [PATCH 029/130] [IMP] imrpved code to remove error of undefined record bzr revid: tpa@tinyerp.com-20130819054516-je0uzg0jlsap909r --- addons/crm/static/src/js/crm_case_section.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index a281e4bb0f1..5c76668849e 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -17,10 +17,14 @@ var _t = openerp.web._t; setTimeout(function () { var value = _.pluck(self.field.value, 'value'); var tooltips = _.pluck(self.field.value, 'tooltip'); + var currency_symbol = ""; + if (self.getParent()){ + currency_symbol = self.getParent().record.currency_symbol.raw_value; + }; self.$el.sparkline(value, { type: 'bar', barWidth: 5, - tooltipFormat: _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? _t('Lead(s)') : self.getParent().record.currency_symbol.raw_value), + tooltipFormat: _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? _t('Lead(s)') : currency_symbol), tooltipValueLookups: { 'offset': tooltips }, From ee9b75603f0dc777736a039dbf3ebb04c9e1062e Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 21 Aug 2013 16:41:13 +0530 Subject: [PATCH 030/130] [IMP] insted of replacing int field with function and add new int fied add new function field only, use fnct_inv insted of overwriting write method. bzr revid: tpa@tinyerp.com-20130821111113-5n022roofguvea0f --- addons/sale_crm/sale_crm.py | 33 ++++++++++++++----------------- addons/sale_crm/sale_crm_demo.xml | 8 ++++---- addons/sale_crm/sale_crm_view.xml | 16 +++++++-------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index b463eca6bb4..af5973b8e3e 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -75,17 +75,27 @@ class crm_case_section(osv.osv): for item in self.browse(cr, uid, ids, context=context): base_currency_id = item.user_id.company_id.currency_id.id if item.user_id else item.create_uid.company_id.currency_id.id res[item.id] = { - 'invoiced_forecast': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.base_invoiced_forecast, context=context), - 'invoiced_target': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.base_invoiced_target, context=context), + 'user_currency_invoiced_forecast': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.invoiced_forecast, context=context), + 'user_currency_invoiced_target': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.invoiced_target, context=context), } return res + def _set_forecast_target(self, cr, uid, team_id, name, value, arg, context=None): + user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id + team = self.browse(cr, uid, team_id, context=context) + base_currency_id = team.user_id.company_id.currency_id.id if team.user_id else team.create_uid.company_id.currency_id.id + amount = self.pool.get('res.currency').compute(cr, uid, user_currency_id, base_currency_id, value, context=context) + self.write(cr, uid, [team_id], {name.strip("user_currency"): amount}, context=context) + return True + _columns = { - 'invoiced_forecast': fields.function(_compute_amounts_in_user_currency, string='Invoice Forecast',type='integer', multi="_compute_amounts", + 'invoiced_forecast': fields.integer("Invoice Forecast"), + 'user_currency_invoiced_forecast': fields.function(_compute_amounts_in_user_currency, fnct_inv=_set_forecast_target, string='Invoice Forecast',type='integer', multi="_compute_amounts", help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" "team should invoice this month. It is used to compute the progression ratio \n" " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.function(_compute_amounts_in_user_currency,string='Invoice Target',type='integer', multi="_compute_amounts", + 'invoiced_target': fields.integer("Invoice Target"), + 'user_currency_invoiced_target': fields.function(_compute_amounts_in_user_currency, fnct_inv=_set_forecast_target, string='Invoice Target',type='integer', multi="_compute_amounts", help="Target of invoice revenue for the current month. This is the amount the sales \n" "team estimates to be able to invoice this month."), 'monthly_quoted': fields.function(_get_sale_orders_data, @@ -97,24 +107,11 @@ class crm_case_section(osv.osv): 'monthly_invoiced': fields.function(_get_invoices_data, type='string', readonly=True, string='Rate of sent invoices per duration'), - 'base_invoiced_forecast': fields.integer(string="Invoice Forecast"), - 'base_invoiced_target': fields.integer(string="Invoice Target"), 'create_uid': fields.many2one('res.users', 'Create User'), } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': int(float(value))}, context=context) - - def write(self, cr, uid, ids, vals, context=None): - currency_obj = self.pool.get('res.currency') - user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id - for item in self.browse(cr, uid, ids, context=context): - base_currency_id = item.user_id.company_id.currency_id.id if item.user_id else item.create_uid.company_id.currency_id.id - if vals.get('invoiced_forecast'): - vals['base_invoiced_forecast'] = currency_obj.compute(cr, uid, user_currency_id, base_currency_id, vals['invoiced_forecast'], context=context) - if vals.get('invoiced_target'): - vals['base_invoiced_target'] = currency_obj.compute(cr, uid, user_currency_id, base_currency_id, vals['invoiced_target'], context=context) - return super(crm_case_section, self).write(cr, uid, ids, vals, context=context) + return self.write(cr, uid, [id], {'user_currency_invoiced_forecast': int(float(value))}, context=context) class res_users(osv.Model): _inherit = 'res.users' diff --git a/addons/sale_crm/sale_crm_demo.xml b/addons/sale_crm/sale_crm_demo.xml index 3eff4061aa1..0c2822719dd 100644 --- a/addons/sale_crm/sale_crm_demo.xml +++ b/addons/sale_crm/sale_crm_demo.xml @@ -3,15 +3,15 @@ - 52700 - 60000 + 52700 + 60000 Indirect Sales IM - 36000 - 40000 + 36000 + 40000 diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 7e4bca1d817..47d850c96fe 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -248,8 +248,8 @@ - - + + @@ -265,8 +265,8 @@ - - + +
@@ -297,11 +297,11 @@
-
- Invoiced - Forecast +
+ Invoiced + Forecast
-
+

Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
From 89d03a854ada9de9cc14c896805f0daa08cfbdfe Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Thu, 22 Aug 2013 18:05:34 +0530 Subject: [PATCH 031/130] [IMP]base_calander: Add create method for crm.meeting Update the Doc remove crm.meeting inheritance from .py file bzr revid: dbr@tinyerp.com-20130822123534-zx3uxdjojoj55bqn --- addons/base_calendar/crm_meeting.py | 9 +++++++++ addons/hr_recruitment/doc/changelog.rst | 2 ++ addons/hr_recruitment/hr_recruitment.py | 10 +--------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index 66823e0b8f4..38d3683fabd 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -64,6 +64,15 @@ class crm_meeting(osv.Model): 'state': 'open', } + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + res = super(crm_meeting, self).create(cr, uid, vals, context=context) + obj = self.browse(cr, uid, res, context=context) + if 'active_model' in context and context['active_model'] == 'hr.applicant': + self.pool.get('hr.applicant').log_meeting_in_parent(cr, uid, context['active_ids'], vals['name'], vals['date'], vals['duration'], context=context) + return res + def message_get_subscription_data(self, cr, uid, ids, context=None): res = {} for virtual_id in ids: diff --git a/addons/hr_recruitment/doc/changelog.rst b/addons/hr_recruitment/doc/changelog.rst index 404b94ee6d1..7fb714c8ea0 100644 --- a/addons/hr_recruitment/doc/changelog.rst +++ b/addons/hr_recruitment/doc/changelog.rst @@ -14,3 +14,5 @@ Changelog - ``hr.recruitment.stage``: removed ``state`` field. - Removed ``hired.employee`` wizard. + +- Add new method ``log_meeting_in_parent``. diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index cf091925c92..e168749ef35 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -281,7 +281,7 @@ class hr_applicant(osv.Model): return stage_ids[0] return False - def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): + def log_meeting_in_parent(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): if not duration: message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting_date, meeting_subject) else: @@ -518,12 +518,4 @@ class applicant_category(osv.osv): 'name': fields.char('Name', size=64, required=True, translate=True), } -class crm_meeting(osv.Model): - _inherit = 'crm.meeting' - - def create(self, cr, uid, vals, context=None): - if 'active_ids' in context and 'active_model' in context and context['active_model'] == 'hr.applicant': - self.pool.get('hr.applicant').log_meeting(cr, uid, context['active_ids'], vals['name'], vals['date'], vals['duration'], context=context) - return super(crm_meeting, self).create(cr, uid, vals, context=context) - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From cb15c354dd73e638c1eaf0287d6aabec7f53daea Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Fri, 23 Aug 2013 15:06:17 +0530 Subject: [PATCH 032/130] [IMP]:Update label bzr revid: dbr@tinyerp.com-20130823093617-3aa3h798n6ydt0m1 --- addons/hr_recruitment/hr_recruitment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 443ec911e0d..00646c2dc46 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -171,7 +171,7 @@ class hr_applicant(osv.Model): return res _columns = { - 'name': fields.char('Subject / Application Name', size=128, required=True), + 'name': fields.char('Subject', size=128, required=True), 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."), 'description': fields.text('Description'), 'email_from': fields.char('Email', size=128, help="These people will receive email."), From d7f420c586893ca792b11f4bdad15a9de38382d5 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Thu, 29 Aug 2013 18:47:19 +0530 Subject: [PATCH 033/130] [IMP]set log_meeting_in_parent method in base_calender bzr revid: dbr@tinyerp.com-20130829131719-2nho1zsqv1ccsj6w --- addons/base_calendar/crm_meeting.py | 23 ++++++++++++++++++----- addons/hr_recruitment/hr_recruitment.py | 8 +------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index 38d3683fabd..d54df072f0e 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -67,11 +67,24 @@ class crm_meeting(osv.Model): def create(self, cr, uid, vals, context=None): if context is None: context = {} - res = super(crm_meeting, self).create(cr, uid, vals, context=context) - obj = self.browse(cr, uid, res, context=context) - if 'active_model' in context and context['active_model'] == 'hr.applicant': - self.pool.get('hr.applicant').log_meeting_in_parent(cr, uid, context['active_ids'], vals['name'], vals['date'], vals['duration'], context=context) - return res + meeting_id = super(crm_meeting, self).create(cr, uid, vals, context=context) + if context.get('log_meeting_in_parent'): + self.log_meeting_in_parent(cr, uid, [meeting_id], context=context) + return meeting_id + + def log_meeting_in_parent(self, cr, uid, ids, context=None): + if context is None: + context = {} + parent_model = context.get('active_model',False) + parent_ids = context.get('active_ids', False) + for meeting in self.browse(cr, uid, ids, context=context): + message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) + if meeting.duration: + message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting.date, meeting.name, str(meeting.duration)) + if parent_model and parent_ids: + self.pool.get(parent_model).message_post(cr, uid, parent_ids, body=message,context=context) + return True + def message_get_subscription_data(self, cr, uid, ids, context=None): res = {} diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 00646c2dc46..df55a1942f3 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -281,13 +281,6 @@ class hr_applicant(osv.Model): return stage_ids[0] return False - def log_meeting_in_parent(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): - if not duration: - message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting_date, meeting_subject) - else: - message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting_date, meeting_subject, str(duration)) - return self.message_post(cr, uid, ids, body=message,context=context) - def action_makeMeeting(self, cr, uid, ids, context=None): """ This opens Meeting's calendar view to schedule meeting on current applicant @return: Dictionary value for created Meeting view @@ -300,6 +293,7 @@ class hr_applicant(osv.Model): 'default_user_id': uid, 'default_name': applicant.name, 'default_categ_ids': category and [category.id] or False, + 'log_meeting_in_parent': True, } return res From 1de73ce6dea8b1be5936cb4059d46e76d1c94736 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Fri, 30 Aug 2013 13:54:04 +0530 Subject: [PATCH 034/130] [IMP]base_calendar: Improve code bzr revid: dbr@tinyerp.com-20130830082404-wgk67mq1sltcg2xi --- addons/base_calendar/crm_meeting.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index d54df072f0e..ba4b21a65d6 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -77,11 +77,11 @@ class crm_meeting(osv.Model): context = {} parent_model = context.get('active_model',False) parent_ids = context.get('active_ids', False) - for meeting in self.browse(cr, uid, ids, context=context): - message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) - if meeting.duration: - message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting.date, meeting.name, str(meeting.duration)) - if parent_model and parent_ids: + if parent_model and parent_ids: + for meeting in self.browse(cr, uid, ids, context=context): + message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) + if meeting.duration: + message = message+_("
Duration: %s hour(s)") % (str(meeting.duration)) self.pool.get(parent_model).message_post(cr, uid, parent_ids, body=message,context=context) return True From b63f89a74ea0c0ab8ef811069164fd6396e61ee0 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 4 Sep 2013 16:55:40 +0530 Subject: [PATCH 035/130] [RMV] remove improvemets to calculate amount based on currency from analysis reports. bzr revid: tpa@tinyerp.com-20130904112540-4ovecz1mpjkok7ge --- .../account/report/account_invoice_report.py | 14 +-------- .../report/account_invoice_report_view.xml | 4 +-- addons/crm/report/crm_lead_report.py | 29 ------------------- addons/crm/report/crm_lead_report_view.xml | 8 ++--- addons/sale/report/sale_report.py | 23 +-------------- 5 files changed, 8 insertions(+), 70 deletions(-) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index ef9478faf0a..c08ea91e2ea 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -29,7 +29,7 @@ class account_invoice_report(osv.osv): _auto = False _rec_name = 'date' - def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): + def def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): """Compute the amounts in the currency of the user """ if context is None: @@ -103,18 +103,6 @@ class account_invoice_report(osv.osv): } _order = 'date desc' - #FIX:To show sum of values of function fields in groupby - def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): - res = super(account_invoice_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) - for group in res: - group['user_currency_price_total'] = 0 - if group.get('__domain'): - group_ids = self.search(cr, uid, group.get('__domain'),context=context) - record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) - for id, rec in record.iteritems(): - group['user_currency_price_total'] += rec['user_currency_price_total'] - return res - def _select(self): select_str = """ SELECT sub.id, sub.date, sub.year, sub.month, sub.day, sub.product_id, sub.partner_id, sub.country_id, diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index 4c297fd52e8..5f38db5e71f 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -30,7 +30,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/addons/crm/report/crm_lead_report.py b/addons/crm/report/crm_lead_report.py index c2f1c0b496e..f379e068b30 100644 --- a/addons/crm/report/crm_lead_report.py +++ b/addons/crm/report/crm_lead_report.py @@ -45,19 +45,6 @@ class crm_lead_report(osv.osv): _description = "CRM Lead Analysis" _rec_name = 'deadline_day' - def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): - """Compute the amounts in the currency of the user - """ - res = {} - currency_obj = self.pool.get('res.currency') - user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id - for item in self.browse(cr, uid, ids, context=context): - res[item.id] = { - 'user_currency_planned_revenue': currency_obj.compute(cr, uid, item.company_id.currency_id.id, user_currency_id, item.planned_revenue, context=context), - 'user_currency_probable_revenue': currency_obj.compute(cr, uid, item.company_id.currency_id.id, user_currency_id, item.probable_revenue, context=context), - } - return res - _columns = { # grouping fields based on Deadline Date 'deadline_year': fields.char('Ex. Closing Year', size=10, readonly=True, help="Expected closing year"), @@ -88,9 +75,7 @@ class crm_lead_report(osv.osv): 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"), 'planned_revenue': fields.float('Planned Revenue',digits=(16,2),readonly=True), - 'user_currency_planned_revenue': fields.function(_compute_amounts_in_user_currency, string="Planned Revenue", type='float',digits=(16,2), multi="_compute_amounts", readonly=True), 'probable_revenue': fields.float('Probable Revenue', digits=(16,2),readonly=True), - 'user_currency_probable_revenue': fields.function(_compute_amounts_in_user_currency, string="Probable Revenue", type='float',digits=(16,2), multi="_compute_amounts",readonly=True), 'stage_id': fields.many2one ('crm.case.stage', 'Stage', readonly=True, domain="[('section_ids', '=', section_id)]"), 'partner_id': fields.many2one('res.partner', 'Partner' , readonly=True), 'nbr': fields.integer('# of Cases', readonly=True), @@ -101,20 +86,6 @@ class crm_lead_report(osv.osv): ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), } - - #FIX:To show sum of values of function fields in groupby - def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): - res = super(crm_lead_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) - for group in res: - group['user_currency_probable_revenue'] = 0 - group['user_currency_planned_revenue'] = 0 - if group.get('__domain'): - group_ids = self.search(cr, uid, group.get('__domain'),context=context) - record = self._compute_amounts_in_user_currency(cr, uid, group_ids, context=context) - for id, rec in record.iteritems(): - group['user_currency_planned_revenue'] += rec['user_currency_planned_revenue'] - group['user_currency_probable_revenue'] += rec['user_currency_probable_revenue'] - return res def init(self, cr): """ diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml index f718d7a9bf8..c7220fe66f2 100644 --- a/addons/crm/report/crm_lead_report_view.xml +++ b/addons/crm/report/crm_lead_report_view.xml @@ -28,7 +28,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -148,13 +148,13 @@ - + - + diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index fb7ac78c3e0..969df76398c 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -27,17 +27,7 @@ class sale_report(osv.osv): _description = "Sales Orders Statistics" _auto = False _rec_name = 'date' - def _compute_total_in_user_currency(self, cr, uid, ids, field_names=[], args={}, context=None): - """Compute the Price total in the currency of the user - """ - res = {} - currency_obj = self.pool.get('res.currency') - user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id - for item in self.browse(cr, uid, ids, context=context): - res[item.id] = { - 'user_price_total': currency_obj.compute(cr, uid, item.pricelist_id.currency_id.id, user_currency_id, item.price_total, context=context), - } - return res + _columns = { 'date': fields.date('Date Order', readonly=True), 'date_confirm': fields.date('Date Confirm', readonly=True), @@ -68,7 +58,6 @@ class sale_report(osv.osv): ], 'Order Status', readonly=True), 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', readonly=True), 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), - 'user_price_total': fields.function(_compute_total_in_user_currency, string="Total Price", type='float',digits=(16,2), multi="_compute_amounts", readonly=True), } _order = 'date desc' @@ -125,16 +114,6 @@ class sale_report(osv.osv): """ return group_by_str - def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): - res = super(sale_report, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby) - for group in res: - group['user_price_total'] = 0 - group_ids = self.search(cr, uid, group.get('__domain'),context=context) - record = self._compute_total_in_user_currency(cr, uid, group_ids, context=context) - for id, rec in record.iteritems(): - group['user_price_total'] += rec['user_price_total'] - return res - def init(self, cr): # self._table = sale_report tools.drop_view_if_exists(cr, self._table) From 6e1b899aa98cc250ed82d0540eb9f1c7e8a93a9d Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 4 Sep 2013 17:13:35 +0530 Subject: [PATCH 036/130] [IMP] improved code bzr revid: tpa@tinyerp.com-20130904114335-jfptxzwdgt2xjzm1 --- addons/account/report/account_invoice_report.py | 2 +- addons/crm/report/crm_lead_report.py | 3 ++- addons/sale/report/sale_report_view.xml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index c08ea91e2ea..d8dc45f9a3b 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -29,7 +29,7 @@ class account_invoice_report(osv.osv): _auto = False _rec_name = 'date' - def def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): + def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): """Compute the amounts in the currency of the user """ if context is None: diff --git a/addons/crm/report/crm_lead_report.py b/addons/crm/report/crm_lead_report.py index f379e068b30..ac1e10a84c6 100644 --- a/addons/crm/report/crm_lead_report.py +++ b/addons/crm/report/crm_lead_report.py @@ -86,8 +86,9 @@ class crm_lead_report(osv.osv): ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), } - + def init(self, cr): + """ CRM Lead Report @param cr: the current row, from the database cursor diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 1b22f9b75d6..28a6f83936a 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -20,7 +20,7 @@ - + From e7764209b74cd68bc561e1ee3ab83ce06862e63f Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Thu, 5 Sep 2013 12:44:04 +0530 Subject: [PATCH 037/130] [FIX] improved code for invoice sparkline bar bzr revid: tpa@tinyerp.com-20130905071404-dlgdeovxarfa9pg6 --- addons/crm/crm.py | 8 +++++--- addons/sale_crm/sale_crm.py | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index a9479000b3e..b3c560f084d 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -129,13 +129,15 @@ class crm_case_section(osv.osv): if type == "Lead" or not inner_groupby: section_result[month]['value'] = group.get(value_field, 0) continue - if inner_groupby: - groupby_company = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) - for groupby in groupby_company: + elif inner_groupby: + inner_group_obj = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) + for groupby in inner_group_obj: if inner_groupby[0] == 'company_id': base_currency_id = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id elif inner_groupby[0] == 'pricelist_id': base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id + elif inner_groupby[0] == 'currency_id': + base_currency_id = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context)[0]['currency_id'][0] user = self.pool.get('res.users').browse(cr, uid, uid, context=context) value = self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, groupby.get(value_field, 0)) section_result[month]['value'] = section_result[month]['value'] + value diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index af5973b8e3e..63017171322 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -52,9 +52,9 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() created_domain = [('section_id', '=', id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', groupby_begin)] - res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order','pricelist_id'], 'amount_total', ['date_order','pricelist_id'], context=context) + res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order', 'pricelist_id'], 'amount_total', ['date_order', 'pricelist_id'], context=context) validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', groupby_begin)] - res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm','pricelist_id'], 'amount_total', ['date_confirm','pricelist_id'], context=context) + res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm', 'pricelist_id'], 'amount_total', ['date_confirm', 'pricelist_id'], context=context) return res def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): @@ -64,7 +64,7 @@ class crm_case_section(osv.osv): groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) for id in ids: created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', groupby_begin)] - res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date'], 'user_currency_price_total', 'date', context=context) + res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date', 'currency_id'], 'price_total', ['date', 'currency_id'], context=context) return res def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): From 17b89198ab3d59c17ce3e57d635d27756c7c91b3 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 6 Sep 2013 16:46:55 +0530 Subject: [PATCH 038/130] [FIX] round amount insted of converting in to int. bzr revid: tpa@tinyerp.com-20130906111655-f6na9nvtot6t7wdd --- addons/sale_crm/sale_crm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 63017171322..0369a62b7fa 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -111,7 +111,7 @@ class crm_case_section(osv.osv): } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'user_currency_invoiced_forecast': int(float(value))}, context=context) + return self.write(cr, uid, [id], {'user_currency_invoiced_forecast': round(float(value))}, context=context) class res_users(osv.Model): _inherit = 'res.users' From 3413d5364fe1eddafd467b69782e96877d8d3a49 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 9 Sep 2013 11:28:58 +0530 Subject: [PATCH 039/130] [IMP] improved domain on invoice analysis report action bzr revid: tpa@tinyerp.com-20130909055858-yvf28wwpixak1kdy --- addons/sale_crm/sale_crm_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 47d850c96fe..c3458f3f1a3 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -236,7 +236,7 @@ account.invoice.report form tree,graph - [('section_id', '=', active_id)] + [('section_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])] {'search_default_month':1} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. From efc8bb37a92fe2079eca0d163e7236b1fb33519f Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Tue, 10 Sep 2013 18:42:05 +0530 Subject: [PATCH 040/130] [IMP]Improve code for crm also bzr revid: dbr@tinyerp.com-20130910131205-39cwpiik2ncs6wcn --- addons/base_calendar/crm_meeting.py | 4 +++- addons/crm/crm_lead.py | 8 -------- addons/crm/crm_meeting.py | 8 -------- addons/hr_recruitment/hr_recruitment.py | 1 - 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index ba4b21a65d6..11e1794f862 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -68,7 +68,9 @@ class crm_meeting(osv.Model): if context is None: context = {} meeting_id = super(crm_meeting, self).create(cr, uid, vals, context=context) - if context.get('log_meeting_in_parent'): + parent_model = context.get('active_model',False) + parent_ids = context.get('active_ids', False) + if parent_model and parent_ids: self.log_meeting_in_parent(cr, uid, [meeting_id], context=context) return meeting_id diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index c4e2cbdc91d..260e634af9b 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -1008,14 +1008,6 @@ class crm_lead(format_address, osv.osv): message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix) return self.message_post(cr, uid, ids, body=message, context=context) - def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): - if not duration: - duration = _('unknown') - else: - duration = str(duration) - message = _("Meeting scheduled at '%s'
Subject: %s
Duration: %s hour(s)") % (meeting_date, meeting_subject, duration) - return self.message_post(cr, uid, ids, body=message, context=context) - def onchange_state(self, cr, uid, ids, state_id, context=None): if state_id: country_id=self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py index c31907bebe2..c65576f16c6 100644 --- a/addons/crm/crm_meeting.py +++ b/addons/crm/crm_meeting.py @@ -34,14 +34,6 @@ class crm_meeting(osv.Model): 'opportunity_id': fields.many2one ('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"), } - def create(self, cr, uid, vals, context=None): - res = super(crm_meeting, self).create(cr, uid, vals, context=context) - obj = self.browse(cr, uid, res, context=context) - if obj.opportunity_id: - self.pool.get('crm.lead').log_meeting(cr, uid, [obj.opportunity_id.id], obj.name, obj.date, obj.duration, context=context) - return res - - class calendar_attendee(osv.osv): """ Calendar Attendee """ diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 1cb75c37953..344e10260de 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -295,7 +295,6 @@ class hr_applicant(osv.Model): 'default_user_id': uid, 'default_name': applicant.name, 'default_categ_ids': category and [category.id] or False, - 'log_meeting_in_parent': True, } return res From 1339e242263fd94cf6b3a5658b647a7656a5f1c4 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Tue, 10 Sep 2013 18:47:04 +0530 Subject: [PATCH 041/130] [IMP] Label name improve bzr revid: dbr@tinyerp.com-20130910131704-mawfghrz42jow31r --- addons/hr_recruitment/hr_recruitment.py | 2 +- addons/hr_recruitment/hr_recruitment_view.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 344e10260de..ff922a1cee3 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -171,7 +171,7 @@ class hr_applicant(osv.Model): return res _columns = { - 'name': fields.char('Subject', size=128, required=True), + 'name': fields.char('Subject / Application Name', size=128, required=True), 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."), 'description': fields.text('Description'), 'email_from': fields.char('Email', size=128, help="These people will receive email."), diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 3bf09d21a17..98f1170ecb7 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -44,7 +44,7 @@ - + From d0923892c2eb2c2995a3c67191a933273e989935 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Tue, 10 Sep 2013 18:55:23 +0530 Subject: [PATCH 042/130] [IMP]Improve code bzr revid: dbr@tinyerp.com-20130910132523-5xbkmrn1gss7p2tv --- addons/base_calendar/crm_meeting.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index 11e1794f862..93bbe66389b 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -77,14 +77,13 @@ class crm_meeting(osv.Model): def log_meeting_in_parent(self, cr, uid, ids, context=None): if context is None: context = {} - parent_model = context.get('active_model',False) - parent_ids = context.get('active_ids', False) - if parent_model and parent_ids: - for meeting in self.browse(cr, uid, ids, context=context): - message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) - if meeting.duration: - message = message+_("
Duration: %s hour(s)") % (str(meeting.duration)) - self.pool.get(parent_model).message_post(cr, uid, parent_ids, body=message,context=context) + parent_model = context.get('active_model') + parent_ids = context.get('active_ids') + for meeting in self.browse(cr, uid, ids, context=context): + message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) + if meeting.duration: + message = message+_("
Duration: %s hour(s)") % (str(meeting.duration)) + self.pool.get(parent_model).message_post(cr, uid, parent_ids, body=message,context=context) return True From 6f44a53ca847a76dcb74510225365e84cdff1114 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Wed, 11 Sep 2013 14:38:55 +0530 Subject: [PATCH 043/130] [IMP] Add condition bzr revid: dbr@tinyerp.com-20130911090855-98upgcnn57bbx63y --- addons/base_calendar/crm_meeting.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index 93bbe66389b..b5757f34c3f 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -79,11 +79,12 @@ class crm_meeting(osv.Model): context = {} parent_model = context.get('active_model') parent_ids = context.get('active_ids') - for meeting in self.browse(cr, uid, ids, context=context): - message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) - if meeting.duration: - message = message+_("
Duration: %s hour(s)") % (str(meeting.duration)) - self.pool.get(parent_model).message_post(cr, uid, parent_ids, body=message,context=context) + if parent_model in self.pool and hasattr(self.pool[parent_model], 'message_post'): + for meeting in self.browse(cr, uid, ids, context=context): + message = _("Meeting scheduled at '%s'
Subject: %s") % (meeting.date, meeting.name) + if meeting.duration: + message = message+_("
Duration: %s hour(s)") % (str(meeting.duration)) + self.pool.get(parent_model).message_post(cr, uid, parent_ids, body=message,context=context) return True From da358b54ab12b175d673a067132eaa5df16af449 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 16 Sep 2013 18:37:00 +0530 Subject: [PATCH 044/130] [IMP] improved code to show bar required help and with delay bzr revid: tpa@tinyerp.com-20130916130700-e9ipoezcjeyva3mb --- addons/crm/crm_case_section_view.xml | 4 +-- addons/crm/static/src/js/crm_case_section.js | 37 +++++++------------- addons/sale_crm/sale_crm_view.xml | 6 ++-- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index b5e23fec324..78727b396d3 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -144,11 +144,11 @@
diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index 5c76668849e..83e4205f4e5 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -9,30 +9,17 @@ var _t = openerp.web._t; } }, }); - openerp.crm.SparklineBarWidget = openerp.web_kanban.AbstractField.extend({ - className: "oe_sparkline_bar", - start: function() { - var self = this; - var title = this.$node.html(); - setTimeout(function () { - var value = _.pluck(self.field.value, 'value'); - var tooltips = _.pluck(self.field.value, 'tooltip'); - var currency_symbol = ""; - if (self.getParent()){ - currency_symbol = self.getParent().record.currency_symbol.raw_value; - }; - self.$el.sparkline(value, { - type: 'bar', - barWidth: 5, - tooltipFormat: _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? _t('Lead(s)') : currency_symbol), - tooltipValueLookups: { - 'offset': tooltips - }, - }); - self.$el.tipsy({'delayIn': 3000, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); - }, 0); - }, - }); - openerp.web_kanban.fields_registry.add("sparkline_bar", "openerp.crm.SparklineBarWidget"); + openerp.web_kanban.SparklineBarWidgetSalesTeam = openerp.web_kanban.SparklineBarWidget.extend({ + set_offset:function(){ + var self = this; + var currency_symbol = ""; + if (self.getParent()){ + currency_symbol = self.getParent().record.currency_symbol.raw_value; + }; + return _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? _t('Lead(s)') : currency_symbol) + } + }); + +openerp.web_kanban.fields_registry.add("sparkline_bar_sales", "openerp.web_kanban.SparklineBarWidgetSalesTeam"); }; diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index c7d3dc42292..c31e87bdf28 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -272,7 +272,7 @@
Sales Orders - + Revenue of confirmed sales orders per month.
Click to acces the Sales Analysis.
@@ -282,7 +282,7 @@
Invoices - + Revenue of sent invoices per month.
Click to see a detailed analysis of invoices.
@@ -290,7 +290,7 @@
Quotations - + Revenue of created quotations per month.
Click to see a detailed analysis.
From 3cdc986c0c7b7908c8c97176086b599cfa97f531 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Mon, 16 Sep 2013 18:38:25 +0530 Subject: [PATCH 045/130] [IMP] improved code to pass tooltip delay in option and added function so we can set requred help in inherited modules. bzr revid: tpa@tinyerp.com-20130916130825-pciwsn0doshstrp2 --- .../static/src/js/kanban_gauge.js | 28 +++++++++++++------ .../static/src/js/kanban_sparkline.js | 7 +++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/addons/web_kanban_gauge/static/src/js/kanban_gauge.js b/addons/web_kanban_gauge/static/src/js/kanban_gauge.js index cb2faf6874b..60cbdf1ddd8 100644 --- a/addons/web_kanban_gauge/static/src/js/kanban_gauge.js +++ b/addons/web_kanban_gauge/static/src/js/kanban_gauge.js @@ -4,7 +4,8 @@ openerp.web_kanban_gauge = function (instance) { * Kanban widgets: GaugeWidget * */ - +var _t = instance.web._t, + _lt = instance.web._lt; instance.web_kanban.GaugeWidget = instance.web_kanban.AbstractField.extend({ className: "oe_gauge", start: function() { @@ -77,13 +78,19 @@ instance.web_kanban.GaugeWidget = instance.web_kanban.AbstractField.extend({ $input.focus() .keydown(function (event) { event.stopPropagation(); - if (event.keyCode == 13 || event.keyCode == 9) { - if ($input.val() != value) { - parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () { - parent.do_reload(); - }); - } else { - $div.remove(); + if(isNaN($input.val())){ + self.do_warn(_t("Wrong value entered!"), _t("Only Integer Value should be valid.")); + $div.remove(); + } else { + if (event.keyCode == 13 || event.keyCode == 9) { + var val = self.parse_client($input.val()); + if ($input.val() != value) { + parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () { + parent.do_reload(); + }); + } else { + $div.remove(); + } } } }) @@ -112,6 +119,11 @@ instance.web_kanban.GaugeWidget = instance.web_kanban.AbstractField.extend({ } } }, + + parse_client: function(value) { + return openerp.web.parse_value(value, { type:"integer" }); + }, + }); instance.web_kanban.fields_registry.add("gauge", "instance.web_kanban.GaugeWidget"); diff --git a/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js b/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js index de21cbba945..68703d90585 100644 --- a/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js +++ b/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js @@ -20,15 +20,18 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten barWidth: 4, barSpacing: 1, barColor: '#96d854', - tooltipFormat: '{{offset:offset}} {{value}}', + tooltipFormat: self.set_offset(), tooltipValueLookups: { 'offset': tooltips } }, self.options); self.$el.sparkline(value, sparkline_options); - self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); + self.$el.tipsy({'delayIn': self.options.delayIn || 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); }, 0); }, + set_offset: function(){ + return '{{offset:offset}} {{value}}'; + } }); instance.web_kanban.fields_registry.add("sparkline_bar", "instance.web_kanban.SparklineBarWidget"); From cce3b9138bc58afbd3ed9502c7ce50d588230e2d Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 11 Oct 2013 12:57:44 +0530 Subject: [PATCH 046/130] [IMP] improved code to set suffix in tooltip. bzr revid: tpa@tinyerp.com-20131011072744-pz4mcxbr4qbdrg79 --- .../static/src/js/kanban_sparkline.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js b/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js index db10299ddd2..630d5812f39 100644 --- a/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js +++ b/addons/web_kanban_sparkline/static/src/js/kanban_sparkline.js @@ -13,6 +13,11 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten setTimeout(function () { var value = _.pluck(self.field.value, 'value'); var tooltips = _.pluck(self.field.value, 'tooltip'); + var suffix = self.options.tooltip_suffix || ""; + if (self.options.tooltip_suffix_field) { + suffix = self.getParent().record[self.options.tooltip_suffix_field].raw_value; + } + var tooltipformat = _.str.sprintf("{{offset:offset}}: {{value}} %s", suffix); var sparkline_options = _.extend({ type: 'bar', barWidth: 5, @@ -20,7 +25,7 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten barWidth: 4, barSpacing: 1, barColor: '#96d854', - tooltipFormat: self.set_offset(), + tooltipFormat: tooltipformat, chartRangeMin: 0, tooltipValueLookups: { 'offset': tooltips @@ -30,9 +35,6 @@ instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.exten self.$el.tipsy({'delayIn': self.options.delayIn || 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); }, 0); }, - set_offset: function(){ - return '{{offset:offset}} {{value}}'; - } }); instance.web_kanban.fields_registry.add("sparkline_bar", "instance.web_kanban.SparklineBarWidget"); From 6279bcf4b3b37ec61c159e77a2603e8c61b592fd Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 11 Oct 2013 13:00:04 +0530 Subject: [PATCH 047/130] [IMP] improved code to set tooltip suffix in sparkline bar, pass suffix in opetion insted of overriding widget. bzr revid: tpa@tinyerp.com-20131011073004-0swv6l1aomjt71xp --- addons/crm/crm_case_section_view.xml | 4 ++-- addons/crm/static/src/js/crm_case_section.js | 14 -------------- addons/sale_crm/sale_crm_view.xml | 6 +++--- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 78727b396d3..7bc2b914e2c 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -144,11 +144,11 @@
diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index 83e4205f4e5..e027313d6dc 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -1,5 +1,4 @@ openerp.crm = function(openerp) { -var _t = openerp.web._t; openerp.web_kanban.KanbanRecord.include({ on_card_clicked: function() { if (this.view.dataset.model === 'crm.case.section') { @@ -9,17 +8,4 @@ var _t = openerp.web._t; } }, }); - - openerp.web_kanban.SparklineBarWidgetSalesTeam = openerp.web_kanban.SparklineBarWidget.extend({ - set_offset:function(){ - var self = this; - var currency_symbol = ""; - if (self.getParent()){ - currency_symbol = self.getParent().record.currency_symbol.raw_value; - }; - return _.str.sprintf("{{offset:offset}}: {{value}} %s",self.$el.closest("div[class='oe_salesteams_leads']").attr('class') ? _t('Lead(s)') : currency_symbol) - } - }); - -openerp.web_kanban.fields_registry.add("sparkline_bar_sales", "openerp.web_kanban.SparklineBarWidgetSalesTeam"); }; diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 9e9cdbd415c..2e55ec8fa89 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -272,7 +272,7 @@
Sales Orders - + Revenue of confirmed sales orders per month.
Click to acces the Sales Analysis.
@@ -282,7 +282,7 @@
Invoices - + Revenue of sent invoices per month.
Click to see a detailed analysis of invoices.
@@ -290,7 +290,7 @@
Quotations - + Revenue of created quotations per month.
Click to see a detailed analysis.
From 62ba3155a0bc37579bc0ae184fcac8c588455ffa Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 11 Oct 2013 18:14:04 +0530 Subject: [PATCH 048/130] [IMP] remove type parameter from __get_bar_values and make base_currency_id alwas defined bzr revid: tpa@tinyerp.com-20131011124404-xoytwxfso8rg5lmb --- addons/crm/crm.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index bb4d13cf5aa..8f0e6a82591 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -100,7 +100,7 @@ class crm_case_section(osv.osv): def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) - def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, type=None, context=None): + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): """ Generic method to generate data for bar chart values using SparklineBarWidget. This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). @@ -127,21 +127,22 @@ class crm_case_section(osv.osv): month = self._period_number - (relativedelta.relativedelta(month_begin, group_begin_date).months + 1) section_result[month] = {'value': 0, 'tooltip': group_begin_date.strftime('%B')} inner_groupby = (group.get('__context', {})).get('group_by',[]) - if type == "Lead" or not inner_groupby: + if value_field == 'create_date_count' or not inner_groupby: section_result[month]['value'] = group.get(value_field, 0) continue - elif inner_groupby: + if inner_groupby: inner_group_obj = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) for groupby in inner_group_obj: + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + base_currency_id = user.company_id.currency_id.id if inner_groupby[0] == 'company_id': base_currency_id = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id elif inner_groupby[0] == 'pricelist_id': base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id elif inner_groupby[0] == 'currency_id': base_currency_id = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context)[0]['currency_id'][0] - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) value = self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, groupby.get(value_field, 0)) - section_result[month]['value'] = section_result[month]['value'] + value + section_result[month]['value'] += value return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): @@ -158,7 +159,7 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() lead_domain = date_domain + [('type', '=', 'lead'), ('section_id', '=', id)] - res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], type="Lead", context=context) + res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], context=context) opp_domain = date_domain + [('type', '=', 'opportunity'), ('section_id', '=', id)] res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date', 'company_id'], 'planned_revenue', ['create_date', 'company_id'], context=context) return res From adbffd1e14e0a47b847361f01e4533e6df8e491e Mon Sep 17 00:00:00 2001 From: "Sunil Sharma (OpenERP)" Date: Tue, 15 Oct 2013 16:08:18 +0530 Subject: [PATCH 049/130] [ADD]:sale_crrm: Added:sale_crm/tests/__init__.py sale_crm/tests/test_sale_crm_case.py bzr revid: sunilsharma.sharma07@gmail.com-20131015103818-ai34ff5rbv86wtgl --- addons/sale_crm/tests/__init__.py | 28 +++++ addons/sale_crm/tests/test_sale_crm_case.py | 120 ++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 addons/sale_crm/tests/__init__.py create mode 100644 addons/sale_crm/tests/test_sale_crm_case.py diff --git a/addons/sale_crm/tests/__init__.py b/addons/sale_crm/tests/__init__.py new file mode 100644 index 00000000000..fc04080875f --- /dev/null +++ b/addons/sale_crm/tests/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from . import test_sale_crm_case + +checks = [ + test_sale_crm_case, +] + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/tests/test_sale_crm_case.py b/addons/sale_crm/tests/test_sale_crm_case.py new file mode 100644 index 00000000000..bfdbee312c7 --- /dev/null +++ b/addons/sale_crm/tests/test_sale_crm_case.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.addons.mail.tests.common import TestMail +from openerp.osv.orm import except_orm + +class TestSaleCrm(TestMail): + + def setUp(self): + super(TestSaleCrm, self).setUp() + cr, uid = self.cr, self.uid + +class TestSaleCrmCase(TestSaleCrm): + + def test_sale_crm_case(self): + """ Testing Sale Crm """ + + cr, uid, = self.cr, self.uid + + # Usefull models + ir_model_obj = self.registry('ir.model.data') + res_company_obj = self.registry('res.company') + res_users_obj = self.registry('res.users') + crm_case_section_obj = self.registry('crm.case.section') + crm_lead_obj = self.registry('crm.lead') + + # Find Direct Sales + direct_sales_ref = ir_model_obj.get_object_reference(cr, uid, 'crm' , 'section_sales_department') + + # for Opportunities + usd_ref = ir_model_obj.get_object_reference(cr, uid, 'base' , 'USD') + your_company_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'main_company') + + # Before opportunities + opportunities_before = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + + #I Create company with USD currency. + res_company_id = res_company_obj.create(cr, uid,{ + 'name': 'New Company', + 'currency_id': usd_ref and usd_ref[1] or False, + 'parent_id': your_company_ref and your_company_ref[1] or False + }) + + # Find Sale Manager group + group_sale_manager_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'group_sale_manager')[1] + + #I Create User of new company. + res_users_id = res_users_obj.create(cr, uid,{ + 'name': 'User', + 'login': 'admin@example.com', + 'company_id': res_company_id, + 'company_ids': [(6, 0, [res_company_id])], + 'email': 'admin@gmail.com', + 'groups_id': [(6, 0, [group_sale_manager_id])] + }) + + #I create Opportunitie for new created user + opportunities_id = crm_lead_obj.create(cr, res_users_id,{ + 'name': 'Opportunities', + 'type': 'opportunity', + 'planned_revenue': 10000, + 'user_id' : res_users_id, + 'section_id': direct_sales_ref and direct_sales_ref[1] or False + }) + + user_rate = res_company_obj.browse(cr, uid ,[res_company_id])[0].currency_id.rate_silent + + # After opportunities + opportunities_after = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + + #Assertion Error + self.assertTrue(int(opportunities_after[1]['monthly_planned_revenue'][4]['value']) == int(opportunities_before[1]['monthly_planned_revenue'][4]['value'] + (10000 / user_rate)), "Not Proper Currency Conversion for Opportunities") + + # Before Sale or Quotations + sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + + # Before Invoice + invoice_before = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + + #I Write company . + company_id = res_users_obj._get_company(cr, uid, context=False, uid2=False) + in_ref = ir_model_obj.get_object_reference(cr, uid, 'base' , 'INR') + res_company_obj.write(cr, uid,[company_id], { + 'currency_id': in_ref and in_ref[1] or False + }) + + # After Sale or Quotations + sale_order_after = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + + # After Invoice + invoice_after = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + + new_rate = res_company_obj.browse(cr, uid , [company_id])[0].currency_id.rate_silent + + #Assertion Error + for i in range(0,5): + # for Quotations + self.assertTrue(sale_order_before[1]['monthly_quoted'][i]['value'] * new_rate == sale_order_after[1]['monthly_quoted'][i]['value'], "Not Proper Currency Conversion for Quotations ") + # for sale odrer + self.assertTrue(sale_order_before[1]['monthly_confirmed'][i]['value'] * new_rate == sale_order_after[1]['monthly_confirmed'][i]['value'], "Not Proper Currency Conversion for Sale Order") + # for Invoice + self.assertTrue(invoice_before[1][i]['value'] * new_rate == invoice_after[1][i]['value'], "Not Proper Currency Conversion for Invoice") From e52f18cc9ab6d3cd5229ac650404a1020a439ac2 Mon Sep 17 00:00:00 2001 From: "Sunil Sharma (OpenERP)" Date: Thu, 17 Oct 2013 12:07:49 +0530 Subject: [PATCH 050/130] [IMP]:sale_crm:add Round function and improve Assertion msg bzr revid: sunilsharma.sharma07@gmail.com-20131017063749-mexdixqszsnwtu4q --- addons/sale_crm/tests/test_sale_crm_case.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/sale_crm/tests/test_sale_crm_case.py b/addons/sale_crm/tests/test_sale_crm_case.py index bfdbee312c7..1171c0759ed 100644 --- a/addons/sale_crm/tests/test_sale_crm_case.py +++ b/addons/sale_crm/tests/test_sale_crm_case.py @@ -87,7 +87,7 @@ class TestSaleCrmCase(TestSaleCrm): opportunities_after = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) #Assertion Error - self.assertTrue(int(opportunities_after[1]['monthly_planned_revenue'][4]['value']) == int(opportunities_before[1]['monthly_planned_revenue'][4]['value'] + (10000 / user_rate)), "Not Proper Currency Conversion for Opportunities") + self.assertTrue(round(opportunities_after[1]['monthly_planned_revenue'][4]['value']) == round(opportunities_before[1]['monthly_planned_revenue'][4]['value'] + (10000 / user_rate)), "Not Proper Currency Conversion for Opportunities") # Before Sale or Quotations sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) @@ -113,8 +113,9 @@ class TestSaleCrmCase(TestSaleCrm): #Assertion Error for i in range(0,5): # for Quotations - self.assertTrue(sale_order_before[1]['monthly_quoted'][i]['value'] * new_rate == sale_order_after[1]['monthly_quoted'][i]['value'], "Not Proper Currency Conversion for Quotations ") - # for sale odrer - self.assertTrue(sale_order_before[1]['monthly_confirmed'][i]['value'] * new_rate == sale_order_after[1]['monthly_confirmed'][i]['value'], "Not Proper Currency Conversion for Sale Order") + self.assertTrue(round(sale_order_before[1]['monthly_quoted'][i]['value'] * new_rate) == round(sale_order_after[1]['monthly_quoted'][i]['value']), "Currency Conversion For Quotations Is Wrong") + # for sale order + self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][i]['value'] * new_rate) == round(sale_order_after[1]['monthly_confirmed'][i]['value']), "Currency Conversion For Sale Order Is Wrong") # for Invoice - self.assertTrue(invoice_before[1][i]['value'] * new_rate == invoice_after[1][i]['value'], "Not Proper Currency Conversion for Invoice") + self.assertTrue(round(invoice_before[1][i]['value'] * new_rate) == round(invoice_after[1][i]['value']), "Currency Conversion For Invoice Is Wrong") + From 09789afc2bef4c2c77bd6ebcd722eaa7be4fd40b Mon Sep 17 00:00:00 2001 From: "Sunil Sharma (OpenERP)" Date: Thu, 17 Oct 2013 12:53:00 +0530 Subject: [PATCH 051/130] [IMP]:sale_crm:improve code standard bzr revid: sunilsharma.sharma07@gmail.com-20131017072300-vt3y65394wv5tl8q --- addons/sale_crm/tests/test_sale_crm_case.py | 23 ++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/addons/sale_crm/tests/test_sale_crm_case.py b/addons/sale_crm/tests/test_sale_crm_case.py index 1171c0759ed..3081b9c08a7 100644 --- a/addons/sale_crm/tests/test_sale_crm_case.py +++ b/addons/sale_crm/tests/test_sale_crm_case.py @@ -43,10 +43,10 @@ class TestSaleCrmCase(TestSaleCrm): crm_lead_obj = self.registry('crm.lead') # Find Direct Sales - direct_sales_ref = ir_model_obj.get_object_reference(cr, uid, 'crm' , 'section_sales_department') + direct_sales_ref = ir_model_obj.get_object_reference(cr, uid, 'crm', 'section_sales_department') # for Opportunities - usd_ref = ir_model_obj.get_object_reference(cr, uid, 'base' , 'USD') + usd_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'USD') your_company_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'main_company') # Before opportunities @@ -78,7 +78,7 @@ class TestSaleCrmCase(TestSaleCrm): 'type': 'opportunity', 'planned_revenue': 10000, 'user_id' : res_users_id, - 'section_id': direct_sales_ref and direct_sales_ref[1] or False + 'section_id': direct_sales_ref and direct_sales_ref[1] or False }) user_rate = res_company_obj.browse(cr, uid ,[res_company_id])[0].currency_id.rate_silent @@ -97,9 +97,9 @@ class TestSaleCrmCase(TestSaleCrm): #I Write company . company_id = res_users_obj._get_company(cr, uid, context=False, uid2=False) - in_ref = ir_model_obj.get_object_reference(cr, uid, 'base' , 'INR') - res_company_obj.write(cr, uid,[company_id], { - 'currency_id': in_ref and in_ref[1] or False + inr_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'INR') + res_company_obj.write(cr, uid, [company_id], { + 'currency_id': inr_ref and inr_ref[1] or False }) # After Sale or Quotations @@ -108,14 +108,13 @@ class TestSaleCrmCase(TestSaleCrm): # After Invoice invoice_after = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) - new_rate = res_company_obj.browse(cr, uid , [company_id])[0].currency_id.rate_silent + new_rate = res_company_obj.browse(cr, uid, [company_id])[0].currency_id.rate_silent #Assertion Error - for i in range(0,5): + for month in range(0,5): # for Quotations - self.assertTrue(round(sale_order_before[1]['monthly_quoted'][i]['value'] * new_rate) == round(sale_order_after[1]['monthly_quoted'][i]['value']), "Currency Conversion For Quotations Is Wrong") + self.assertTrue(round(sale_order_before[1]['monthly_quoted'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_quoted'][month]['value']), "Currency Conversion For Quotations Is Wrong") # for sale order - self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][i]['value'] * new_rate) == round(sale_order_after[1]['monthly_confirmed'][i]['value']), "Currency Conversion For Sale Order Is Wrong") + self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_confirmed'][month]['value']), "Currency Conversion For Sale Order Is Wrong") # for Invoice - self.assertTrue(round(invoice_before[1][i]['value'] * new_rate) == round(invoice_after[1][i]['value']), "Currency Conversion For Invoice Is Wrong") - + self.assertTrue(round(invoice_before[1][month]['value'] * new_rate) == round(invoice_after[1][month]['value']), "Currency Conversion For Invoice Is Wrong") From b686ee51af6a709722b5a3d2280d827eb65750db Mon Sep 17 00:00:00 2001 From: "Sunil Sharma (OpenERP)" Date: Thu, 17 Oct 2013 16:14:42 +0530 Subject: [PATCH 052/130] [IMP]:sale_crm:remove setup method and improve msg bzr revid: sunilsharma.sharma07@gmail.com-20131017104442-6kw85h9524grftsw --- addons/sale_crm/tests/test_sale_crm_case.py | 80 +++++++++------------ 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/addons/sale_crm/tests/test_sale_crm_case.py b/addons/sale_crm/tests/test_sale_crm_case.py index 3081b9c08a7..71cad1aff93 100644 --- a/addons/sale_crm/tests/test_sale_crm_case.py +++ b/addons/sale_crm/tests/test_sale_crm_case.py @@ -20,49 +20,38 @@ ############################################################################## from openerp.addons.mail.tests.common import TestMail -from openerp.osv.orm import except_orm class TestSaleCrm(TestMail): - def setUp(self): - super(TestSaleCrm, self).setUp() - cr, uid = self.cr, self.uid - -class TestSaleCrmCase(TestSaleCrm): - def test_sale_crm_case(self): - """ Testing Sale Crm """ + """Testing __get_bar_values method""" cr, uid, = self.cr, self.uid - # Usefull models + #Usefull models ir_model_obj = self.registry('ir.model.data') res_company_obj = self.registry('res.company') res_users_obj = self.registry('res.users') crm_case_section_obj = self.registry('crm.case.section') crm_lead_obj = self.registry('crm.lead') - # Find Direct Sales - direct_sales_ref = ir_model_obj.get_object_reference(cr, uid, 'crm', 'section_sales_department') + #Get required ids + direct_sales_id = ir_model_obj.get_object_reference(cr, uid, 'crm', 'section_sales_department')[1] + usd_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'USD')[1] + your_company_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'main_company')[1] - # for Opportunities - usd_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'USD') - your_company_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'main_company') + #Call _get_opportunities_data method before creating new opportunity in another currency. + opportunities_before = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - # Before opportunities - opportunities_before = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) - - #I Create company with USD currency. + #Create new company with USD currency. res_company_id = res_company_obj.create(cr, uid,{ 'name': 'New Company', - 'currency_id': usd_ref and usd_ref[1] or False, - 'parent_id': your_company_ref and your_company_ref[1] or False + 'currency_id': usd_id, + 'parent_id': your_company_id }) - # Find Sale Manager group + #Create sales manager of new company. group_sale_manager_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'group_sale_manager')[1] - - #I Create User of new company. res_users_id = res_users_obj.create(cr, uid,{ 'name': 'User', 'login': 'admin@example.com', @@ -72,49 +61,44 @@ class TestSaleCrmCase(TestSaleCrm): 'groups_id': [(6, 0, [group_sale_manager_id])] }) - #I create Opportunitie for new created user + #Create Opportunitie by new created user. opportunities_id = crm_lead_obj.create(cr, res_users_id,{ 'name': 'Opportunities', 'type': 'opportunity', 'planned_revenue': 10000, 'user_id' : res_users_id, - 'section_id': direct_sales_ref and direct_sales_ref[1] or False + 'section_id': direct_sales_id }) - user_rate = res_company_obj.browse(cr, uid ,[res_company_id])[0].currency_id.rate_silent + user_rate = res_company_obj.browse(cr, uid, [res_company_id])[0].currency_id.rate_silent - # After opportunities - opportunities_after = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) - - #Assertion Error + #Call _get_opportunities_data method and check Currency Conversion of amount for new created opportunity which was in another currency. + opportunities_after = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) self.assertTrue(round(opportunities_after[1]['monthly_planned_revenue'][4]['value']) == round(opportunities_before[1]['monthly_planned_revenue'][4]['value'] + (10000 / user_rate)), "Not Proper Currency Conversion for Opportunities") - # Before Sale or Quotations - sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + #Call _get_sale_orders_data method before changing currency of user. + sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - # Before Invoice - invoice_before = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + #Call _get_invoices_data method before changing currency of user. + invoice_before = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - #I Write company . + #Change currency of company to INR. company_id = res_users_obj._get_company(cr, uid, context=False, uid2=False) - inr_ref = ir_model_obj.get_object_reference(cr, uid, 'base', 'INR') + inr_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'INR')[1] res_company_obj.write(cr, uid, [company_id], { - 'currency_id': inr_ref and inr_ref[1] or False + 'currency_id': inr_id, }) - # After Sale or Quotations - sale_order_after = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + #Call _get_sale_orders_data method after changing currency of user. + sale_order_after = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - # After Invoice - invoice_after = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_ref[1]], field_name=False, arg=False, context=False) + #Call _get_invoices_data method after changing currency of user. + invoice_after = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) new_rate = res_company_obj.browse(cr, uid, [company_id])[0].currency_id.rate_silent - #Assertion Error + #Check currency conversion for Quotations,Sale orders and Invoices in to the current users currency for month in range(0,5): - # for Quotations - self.assertTrue(round(sale_order_before[1]['monthly_quoted'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_quoted'][month]['value']), "Currency Conversion For Quotations Is Wrong") - # for sale order - self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_confirmed'][month]['value']), "Currency Conversion For Sale Order Is Wrong") - # for Invoice - self.assertTrue(round(invoice_before[1][month]['value'] * new_rate) == round(invoice_after[1][month]['value']), "Currency Conversion For Invoice Is Wrong") + self.assertTrue(round(sale_order_before[1]['monthly_quoted'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_quoted'][month]['value']), "Currency conversion for quotations is wrong") + self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_confirmed'][month]['value']), "Currency conversion for sale orders is wrong") + self.assertTrue(round(invoice_before[1][month]['value'] * new_rate) == round(invoice_after[1][month]['value']), "Currency conversion for invoices is wrong") From f684f68173a31455d6113ce6a8cdced749233642 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 23 Oct 2013 11:12:54 +0530 Subject: [PATCH 053/130] [RMV] removed code to convert forcast and target amount in user currency. bzr revid: tpa@tinyerp.com-20131023054254-gw4mjjighbbn1unm --- addons/sale_crm/sale_crm.py | 31 +++++-------------------------- addons/sale_crm/sale_crm_view.xml | 17 +++++++++-------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index fa8a1f8c467..29aa81a3c35 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -70,35 +70,12 @@ class crm_case_section(osv.osv): res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date', 'currency_id'], 'price_total', ['date', 'currency_id'], context=context) return res - def _compute_amounts_in_user_currency(self, cr, uid, ids, field_names, args, context=None): - """Compute the amounts in the currency of the user """ - res = {} - currency_obj = self.pool.get('res.currency') - user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id - for item in self.browse(cr, uid, ids, context=context): - base_currency_id = item.user_id.company_id.currency_id.id if item.user_id else item.create_uid.company_id.currency_id.id - res[item.id] = { - 'user_currency_invoiced_forecast': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.invoiced_forecast, context=context), - 'user_currency_invoiced_target': currency_obj.compute(cr, uid, base_currency_id, user_currency_id, item.invoiced_target, context=context), - } - return res - - def _set_forecast_target(self, cr, uid, team_id, name, value, arg, context=None): - user_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id - team = self.browse(cr, uid, team_id, context=context) - base_currency_id = team.user_id.company_id.currency_id.id if team.user_id else team.create_uid.company_id.currency_id.id - amount = self.pool.get('res.currency').compute(cr, uid, user_currency_id, base_currency_id, value, context=context) - self.write(cr, uid, [team_id], {name.strip("user_currency"): amount}, context=context) - return True - _columns = { - 'invoiced_forecast': fields.integer("Invoice Forecast"), - 'user_currency_invoiced_forecast': fields.function(_compute_amounts_in_user_currency, fnct_inv=_set_forecast_target, string='Invoice Forecast',type='integer', multi="_compute_amounts", + 'invoiced_forecast': fields.integer("Invoice Forecast", help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" "team should invoice this month. It is used to compute the progression ratio \n" " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.integer("Invoice Target"), - 'user_currency_invoiced_target': fields.function(_compute_amounts_in_user_currency, fnct_inv=_set_forecast_target, string='Invoice Target',type='integer', multi="_compute_amounts", + 'invoiced_target': fields.integer("Invoice Target", help="Target of invoice revenue for the current month. This is the amount the sales \n" "team estimates to be able to invoice this month."), 'monthly_quoted': fields.function(_get_sale_orders_data, @@ -111,10 +88,12 @@ class crm_case_section(osv.osv): type='string', readonly=True, string='Rate of sent invoices per duration'), 'create_uid': fields.many2one('res.users', 'Create User'), + 'company_id': fields.related('create_uid', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True), + 'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', required=True, readonly=True, string='Currency'), } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'user_currency_invoiced_forecast': round(float(value))}, context=context) + return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) class res_users(osv.Model): _inherit = 'res.users' diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 2e55ec8fa89..afaa6a3578c 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -248,8 +248,9 @@ - - + + + @@ -265,8 +266,8 @@ - - + +
@@ -297,11 +298,11 @@
-
- Invoiced - Forecast +
+ Invoiced + Forecast
-
+

Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
From 44eaa430cc780f72f21ab62d658ce24a07d57cd0 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 23 Oct 2013 11:40:53 +0530 Subject: [PATCH 054/130] [IMP] add direct currency_id field insted of related field. bzr revid: tpa@tinyerp.com-20131023061053-3avp9mp31v7vl9o6 --- addons/crm/crm.py | 4 +++- addons/sale_crm/sale_crm.py | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 8f0e6a82591..fe67db23aed 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -200,7 +200,8 @@ class crm_case_section(osv.osv): 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', string='Planned Revenue per Month'), - 'currency_symbol': fields.function(_get_currency_symbol, string='Currency Symbol', method=True, type='char'), + 'currency_id': fields.many2one('res.currency', 'Currency', required=True), + 'currency_symbol': fields.function(_get_currency_symbol, string="Current User's Currency Symbol", method=True, type='char'), } def _get_stage_common(self, cr, uid, context): @@ -211,6 +212,7 @@ class crm_case_section(osv.osv): 'active': 1, 'stage_ids': _get_stage_common, 'use_leads': True, + 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id, } _sql_constraints = [ diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 29aa81a3c35..7287e7dc1a8 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -88,8 +88,6 @@ class crm_case_section(osv.osv): type='string', readonly=True, string='Rate of sent invoices per duration'), 'create_uid': fields.many2one('res.users', 'Create User'), - 'company_id': fields.related('create_uid', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True), - 'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', required=True, readonly=True, string='Currency'), } def action_forecast(self, cr, uid, id, value, context=None): From 4e0a8f917378f5fc18b56868bc8597b4c3dd7a13 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 23 Oct 2013 12:14:55 +0530 Subject: [PATCH 055/130] [IMP] removed unused create_uid field. bzr revid: tpa@tinyerp.com-20131023064455-ge7pxrmz1qskh6jf --- addons/sale_crm/sale_crm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 7287e7dc1a8..67a7c9d39f9 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -87,7 +87,6 @@ class crm_case_section(osv.osv): 'monthly_invoiced': fields.function(_get_invoices_data, type='string', readonly=True, string='Rate of sent invoices per duration'), - 'create_uid': fields.many2one('res.users', 'Create User'), } def action_forecast(self, cr, uid, id, value, context=None): From cabc3bbece5646d11048e2a34b676fd80515091f Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 23 Oct 2013 15:20:00 +0530 Subject: [PATCH 056/130] [IMP] added new _currency_conversation method. bzr revid: tpa@tinyerp.com-20131023095000-082hgraqi6i7h3o6 --- addons/crm/crm.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index fe67db23aed..f0b4ffeb0cd 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -100,6 +100,19 @@ class crm_case_section(osv.osv): def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) + def _currency_conversation(self, cr, uid, amount, relation_id, relation_field, context=None): + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + base_currency_id = user.company_id.currency_id.id + if relation_field == 'company_id': + base_currency_id = self.pool.get('res.company').browse(cr, uid, relation_id, context=context).currency_id.id + elif relation_field == 'pricelist_id': + base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, relation_id, context=context).currency_id.id + elif relation_field == 'currency_id': + currency_rates = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context) + if currency_rates: + base_currency_id = currency_rates[0]['currency_id'][0] + return self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, amount, context=context) + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): """ Generic method to generate data for bar chart values using SparklineBarWidget. This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). @@ -133,16 +146,7 @@ class crm_case_section(osv.osv): if inner_groupby: inner_group_obj = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) for groupby in inner_group_obj: - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - base_currency_id = user.company_id.currency_id.id - if inner_groupby[0] == 'company_id': - base_currency_id = self.pool.get('res.company').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id - elif inner_groupby[0] == 'pricelist_id': - base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, groupby['__domain'][0][2], context=context).currency_id.id - elif inner_groupby[0] == 'currency_id': - base_currency_id = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context)[0]['currency_id'][0] - value = self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, groupby.get(value_field, 0)) - section_result[month]['value'] += value + section_result[month]['value'] += self._currency_conversation(cr, uid, groupby.get(value_field, 0), groupby['__domain'][0][2],inner_groupby[0], context=context) return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): From 416c47f3244ee6c01cd9f5a7406879fe9c99ec69 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 23 Oct 2013 17:06:52 +0530 Subject: [PATCH 057/130] [FIX] replace integer with float in validation bzr revid: tpa@tinyerp.com-20131023113652-rj74krv1lecyp2xf --- addons/web_kanban_gauge/static/src/js/kanban_gauge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_kanban_gauge/static/src/js/kanban_gauge.js b/addons/web_kanban_gauge/static/src/js/kanban_gauge.js index 60cbdf1ddd8..0d8a21ef638 100644 --- a/addons/web_kanban_gauge/static/src/js/kanban_gauge.js +++ b/addons/web_kanban_gauge/static/src/js/kanban_gauge.js @@ -121,7 +121,7 @@ instance.web_kanban.GaugeWidget = instance.web_kanban.AbstractField.extend({ }, parse_client: function(value) { - return openerp.web.parse_value(value, { type:"integer" }); + return openerp.web.parse_value(value, { type:"float" }); }, }); From f48db35788352e1dc53646739ad75f50a0537730 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 23 Oct 2013 17:07:42 +0530 Subject: [PATCH 058/130] [FIX] sale_crmconvert target and forcat fields to float from int bzr revid: tpa@tinyerp.com-20131023113742-55szrqznr1imequi --- addons/sale_crm/sale_crm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 67a7c9d39f9..2e7d61a6487 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -71,11 +71,11 @@ class crm_case_section(osv.osv): return res _columns = { - 'invoiced_forecast': fields.integer("Invoice Forecast", + 'invoiced_forecast': fields.float("Invoice Forecast", help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" "team should invoice this month. It is used to compute the progression ratio \n" " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.integer("Invoice Target", + 'invoiced_target': fields.float("Invoice Target", help="Target of invoice revenue for the current month. This is the amount the sales \n" "team estimates to be able to invoice this month."), 'monthly_quoted': fields.function(_get_sale_orders_data, @@ -90,7 +90,7 @@ class crm_case_section(osv.osv): } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) + return self.write(cr, uid, [id], {'invoiced_forecast': value}, context=context) class res_users(osv.Model): _inherit = 'res.users' From 29214f08942c6cf44810f48f3a1d6497b147b6b1 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 25 Oct 2013 15:15:12 +0530 Subject: [PATCH 059/130] [IMP] improved code. bzr revid: tpa@tinyerp.com-20131025094512-m0mx0a0s94h1ldjg --- addons/crm/crm.py | 8 +------- addons/sale_crm/sale_crm.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index f0b4ffeb0cd..9e1a9abfe5e 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -105,12 +105,6 @@ class crm_case_section(osv.osv): base_currency_id = user.company_id.currency_id.id if relation_field == 'company_id': base_currency_id = self.pool.get('res.company').browse(cr, uid, relation_id, context=context).currency_id.id - elif relation_field == 'pricelist_id': - base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, relation_id, context=context).currency_id.id - elif relation_field == 'currency_id': - currency_rates = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context) - if currency_rates: - base_currency_id = currency_rates[0]['currency_id'][0] return self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, amount, context=context) def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): @@ -146,7 +140,7 @@ class crm_case_section(osv.osv): if inner_groupby: inner_group_obj = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) for groupby in inner_group_obj: - section_result[month]['value'] += self._currency_conversation(cr, uid, groupby.get(value_field, 0), groupby['__domain'][0][2],inner_groupby[0], context=context) + section_result[month]['value'] += self._currency_conversation(cr, uid, groupby.get(value_field, 0), groupby['__domain'][0][2], inner_groupby[0], context=context) return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 2e7d61a6487..26b2aa05b94 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -45,6 +45,19 @@ class sale_order(osv.osv): class crm_case_section(osv.osv): _inherit = 'crm.case.section' + def _currency_conversation(self, cr, uid, amount, relation_id, relation_field, context=None): + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + base_currency_id = user.company_id.currency_id.id + if relation_field not in ['pricelist_id', 'currency_id']: + return super(crm_case_section, self)._currency_conversation(cr, uid, amount, relation_id, relation_field, context=context) + if relation_field == 'pricelist_id': + base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, relation_id, context=context).currency_id.id + elif relation_field == 'currency_id': + currency_rates = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context) + if currency_rates: + base_currency_id = currency_rates[0]['currency_id'][0] + return self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, amount, context=context) + def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): obj = self.pool.get('sale.order') res = dict.fromkeys(ids, False) From c05369d2b282cfad15182fe6bd16ba12b3e80366 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 29 Oct 2013 16:52:01 +0530 Subject: [PATCH 060/130] [ADD] added onchange on manger to change currency_id bzr revid: tpa@tinyerp.com-20131029112201-fs1us3vi4bhofplx --- addons/crm/crm.py | 8 ++++++++ addons/crm/crm_case_section_view.xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 9e1a9abfe5e..f01c023329a 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -198,6 +198,7 @@ class crm_case_section(osv.osv): 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', string='Planned Revenue per Month'), + 'create_uid': fields.many2one('res.users', 'Create User'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True), 'currency_symbol': fields.function(_get_currency_symbol, string="Current User's Currency Symbol", method=True, type='char'), } @@ -254,6 +255,13 @@ class crm_case_section(osv.osv): mail_alias.unlink(cr, uid, alias_ids, context=context) return res + def onchange_user(self, cr, uid, ids, user_id, context=None): + if user_id: + currency_id = self.pool.get('res.users').browse(cr, uid, user_id, context=context).company_id.currency_id.id + else: + currency_id = self.browse(cr, uid, ids[0], context=context).create_uid.company_id.currency_id.id + return {'value': {'currency_id': currency_id}} + class crm_case_categ(osv.osv): """ Category of Case """ _name = "crm.case.categ" diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 7bc2b914e2c..4b3c01e3bbf 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -219,7 +219,7 @@
- + From ecdb4abbd1d43922fff11b028b17dbd2afde22ed Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 29 Oct 2013 18:21:28 +0530 Subject: [PATCH 061/130] [IMP] improved code to show all amounts in sales teams currency instead on current users currency bzr revid: tpa@tinyerp.com-20131029125128-9n4myfklstueqq0v --- addons/crm/crm.py | 25 +++++++++---------------- addons/sale_crm/sale_crm.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index f01c023329a..3db95cee3ba 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -100,14 +100,14 @@ class crm_case_section(osv.osv): def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) - def _currency_conversation(self, cr, uid, amount, relation_id, relation_field, context=None): - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - base_currency_id = user.company_id.currency_id.id + def _currency_conversation(self, cr, uid, id, amount, relation_id, relation_field, context=None): + team = self.browse(cr, uid, id, context=context) + base_currency_id = team.currency_id.id if relation_field == 'company_id': base_currency_id = self.pool.get('res.company').browse(cr, uid, relation_id, context=context).currency_id.id - return self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, amount, context=context) + return self.pool.get('res.currency').compute(cr, uid, base_currency_id, team.currency_id.id, amount, context=context) - def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): + def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None): """ Generic method to generate data for bar chart values using SparklineBarWidget. This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). @@ -140,7 +140,7 @@ class crm_case_section(osv.osv): if inner_groupby: inner_group_obj = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) for groupby in inner_group_obj: - section_result[month]['value'] += self._currency_conversation(cr, uid, groupby.get(value_field, 0), groupby['__domain'][0][2], inner_groupby[0], context=context) + section_result[month]['value'] += self._currency_conversation(cr, uid, id, groupby.get(value_field, 0), groupby['__domain'][0][2], inner_groupby[0], context=context) return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): @@ -157,16 +157,9 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() lead_domain = date_domain + [('type', '=', 'lead'), ('section_id', '=', id)] - res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], context=context) + res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, id, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], context=context) opp_domain = date_domain + [('type', '=', 'opportunity'), ('section_id', '=', id)] - res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date', 'company_id'], 'planned_revenue', ['create_date', 'company_id'], context=context) - return res - - def _get_currency_symbol(self, cr, uid, ids, name, arg, context=None): - """Set currency symbole of company currency""" - res = {} - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - res = dict.fromkeys(ids, user.company_id.currency_id.symbol) + res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, id, obj, opp_domain, ['planned_revenue', 'create_date', 'company_id'], 'planned_revenue', ['create_date', 'company_id'], context=context) return res _columns = { @@ -200,7 +193,7 @@ class crm_case_section(osv.osv): string='Planned Revenue per Month'), 'create_uid': fields.many2one('res.users', 'Create User'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True), - 'currency_symbol': fields.function(_get_currency_symbol, string="Current User's Currency Symbol", method=True, type='char'), + 'currency_symbol': fields.related('currency_id', 'symbol', type='char', string='Currency Symbol'), } def _get_stage_common(self, cr, uid, context): diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 26b2aa05b94..1fedc6a9945 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -45,18 +45,18 @@ class sale_order(osv.osv): class crm_case_section(osv.osv): _inherit = 'crm.case.section' - def _currency_conversation(self, cr, uid, amount, relation_id, relation_field, context=None): - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - base_currency_id = user.company_id.currency_id.id + def _currency_conversation(self, cr, uid, id, amount, relation_id, relation_field, context=None): + team = self.browse(cr, uid, id, context=context) + base_currency_id = team.currency_id.id if relation_field not in ['pricelist_id', 'currency_id']: - return super(crm_case_section, self)._currency_conversation(cr, uid, amount, relation_id, relation_field, context=context) + return super(crm_case_section, self)._currency_conversation(cr, uid, id, amount, relation_id, relation_field, context=context) if relation_field == 'pricelist_id': base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, relation_id, context=context).currency_id.id elif relation_field == 'currency_id': currency_rates = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context) if currency_rates: base_currency_id = currency_rates[0]['currency_id'][0] - return self.pool.get('res.currency').compute(cr, uid, base_currency_id, user.company_id.currency_id.id, amount, context=context) + return self.pool.get('res.currency').compute(cr, uid, base_currency_id, team.currency_id.id, amount, context=context) def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): obj = self.pool.get('sale.order') @@ -67,9 +67,9 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() created_domain = [('section_id', '=', id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order', 'pricelist_id'], 'amount_total', ['date_order', 'pricelist_id'], context=context) + res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, id, obj, created_domain, ['amount_total', 'date_order', 'pricelist_id'], 'amount_total', ['date_order', 'pricelist_id'], context=context) validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm', 'pricelist_id'], 'amount_total', ['date_confirm', 'pricelist_id'], context=context) + res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, id, obj, validated_domain, ['amount_total', 'date_confirm', 'pricelist_id'], 'amount_total', ['date_confirm', 'pricelist_id'], context=context) return res def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): @@ -80,7 +80,7 @@ class crm_case_section(osv.osv): date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) for id in ids: created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] - res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date', 'currency_id'], 'price_total', ['date', 'currency_id'], context=context) + res[id] = self.__get_bar_values(cr, uid, id, obj, created_domain, ['price_total', 'date', 'currency_id'], 'price_total', ['date', 'currency_id'], context=context) return res _columns = { From 850593c39ad51066b30c92a0e68a2d7ddd26af73 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 30 Oct 2013 12:16:09 +0530 Subject: [PATCH 062/130] [IMP] improved test cases related to changes of currency conversation to show amounts in teams curreny instead current users currency bzr revid: tpa@tinyerp.com-20131030064609-rzkoul8d44cp93dv --- addons/sale_crm/tests/test_sale_crm_case.py | 46 ++++++--------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/addons/sale_crm/tests/test_sale_crm_case.py b/addons/sale_crm/tests/test_sale_crm_case.py index 55aea92ce41..6fb7d4c555a 100644 --- a/addons/sale_crm/tests/test_sale_crm_case.py +++ b/addons/sale_crm/tests/test_sale_crm_case.py @@ -39,8 +39,10 @@ class TestSaleCrm(TestMail): usd_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'USD')[1] your_company_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'main_company')[1] - # Call _get_opportunities_data method before creating new opportunity in another currency. + # Call relevant methods before changing manager of sales team. opportunities_before = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) + sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) + invoice_before = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) # Create new company with USD currency. res_company_id = res_company_obj.create(cr, uid,{ @@ -49,7 +51,7 @@ class TestSaleCrm(TestMail): 'parent_id': your_company_id }) - # Create sales manager of new company. + # Create new user with sales manager access rights for new company. group_sale_manager_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'group_sale_manager')[1] res_users_id = res_users_obj.create(cr, uid,{ 'name': 'User', @@ -60,44 +62,22 @@ class TestSaleCrm(TestMail): 'groups_id': [(6, 0, [group_sale_manager_id])] }) - # Create Opportunitie by new created user. - opportunities_id = crm_lead_obj.create(cr, res_users_id,{ - 'name': 'Opportunities', - 'type': 'opportunity', - 'planned_revenue': 10000, + # Change manager of sales team with USD currency. + crm_case_section_obj.write(cr, uid, [direct_sales_id], { 'user_id' : res_users_id, - 'section_id': direct_sales_id + 'currency_id': usd_id, }) - user_rate = res_company_obj.browse(cr, uid, [res_company_id])[0].currency_id.rate_silent - - # Call _get_opportunities_data method and check Currency Conversion of amount for new created opportunity which was in another currency. + # Call relevant methods and get converted data in another currency. opportunities_after = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - self.assertTrue(round(opportunities_after[1]['monthly_planned_revenue'][4]['value']) == round(opportunities_before[1]['monthly_planned_revenue'][4]['value'] + (10000 / user_rate)), "Not Proper Currency Conversion for Opportunities") - - # Call _get_sale_orders_data method before changing currency of user. - sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - - # Call _get_invoices_data method before changing currency of user. - invoice_before = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - - # Change currency of company to INR. - company_id = res_users_obj._get_company(cr, uid, context=False, uid2=False) - inr_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'INR')[1] - res_company_obj.write(cr, uid, [company_id], { - 'currency_id': inr_id, - }) - - # Call _get_sale_orders_data method after changing currency of user. sale_order_after = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - - # Call _get_invoices_data method after changing currency of user. invoice_after = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - new_rate = res_company_obj.browse(cr, uid, [company_id])[0].currency_id.rate_silent + team_currency_rate = crm_case_section_obj.browse(cr, uid, direct_sales_id).currency_id.rate_silent # Check currency conversion for Quotations,Sale orders and Invoices in to the current users currency for month in range(0,5): - self.assertTrue(round(sale_order_before[1]['monthly_quoted'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_quoted'][month]['value']), "Currency conversion for quotations is wrong") - self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][month]['value'] * new_rate) == round(sale_order_after[1]['monthly_confirmed'][month]['value']), "Currency conversion for sale orders is wrong") - self.assertTrue(round(invoice_before[1][month]['value'] * new_rate) == round(invoice_after[1][month]['value']), "Currency conversion for invoices is wrong") + self.assertTrue(round(opportunities_before[1]['monthly_planned_revenue'][month]['value']) == round(opportunities_after[1]['monthly_planned_revenue'][month]['value'] / team_currency_rate), "Not Proper Currency Conversion for Opportunities") + self.assertTrue(round(sale_order_before[1]['monthly_quoted'][month]['value']) == round(sale_order_after[1]['monthly_quoted'][month]['value'] / team_currency_rate), "Currency conversion for quotations is wrong") + self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][month]['value']) == round(sale_order_after[1]['monthly_confirmed'][month]['value'] / team_currency_rate), "Currency conversion for sale orders is wrong") + self.assertTrue(round(invoice_before[1][month]['value']) == round(invoice_after[1][month]['value'] / team_currency_rate), "Currency conversion for invoices is wrong") From 72ffc9a842ce97f5acff4f008fc5753706041f17 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Thu, 31 Oct 2013 12:47:28 +0530 Subject: [PATCH 063/130] [IMP] improved css. bzr revid: tpa@tinyerp.com-20131031071728-1n22alzz56ud0wl1 --- addons/crm/static/src/css/crm.css | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index 09d8bb30229..ebd83861538 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -23,6 +23,7 @@ .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list { position: relative; margin: 10px; + min-height: 10px; } .openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div { width: 160px; From 05266e0a0f5382c17ec3c0a2aa8d5d8d7cdd4fac Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 1 Nov 2013 14:32:22 +0530 Subject: [PATCH 064/130] [ADD] show avtars of members in sales team kanban if only crm is installed. bzr revid: tpa@tinyerp.com-20131101090222-f0ewbmvc4kc2vqap --- addons/crm/crm_case_section_view.xml | 5 +++ addons/crm/static/src/css/crm.css | 7 ++-- addons/crm/static/src/js/crm_case_section.js | 36 ++++++++++++++++++++ addons/sale_crm/sale_crm_view.xml | 2 +- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 4b3c01e3bbf..e032376cb05 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -151,6 +151,11 @@ Planned Revenue per Month
Click to see a detailed analysis of opportunities.
+
+ + + +
diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index ebd83861538..311727fe51d 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -2,12 +2,11 @@ width: 345px; cursor: default; } -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars { - text-align: right; - margin: -5px 0 -10px 0; +.openerp .oe_kanban_view .oe_kanban_salesteams_avatars { + margin-top: 20px; } -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars img { +.openerp .oe_kanban_view .oe_kanban_salesteams_avatars img { width: 30px; height: 30px; padding-left: 0px; diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index e027313d6dc..02f42288880 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -1,4 +1,40 @@ openerp.crm = function(openerp) { + openerp.web_kanban.KanbanView.include({ + crm_display_members_names: function() { + /* + * Set avatar title for members. + * In kanban views, many2many fields only return a list of ids. + * We can implement return value of m2m fields like [(1,"Adminstration"),...]. + */ + var self = this; + var members_ids = []; + + // Collect members ids + self.$el.find('img[data-member_id]').each(function() { + members_ids.push($(this).data('member_id')); + }); + + // Find their matching names + var dataset = new openerp.web.DataSetSearch(self, 'res.users', self.session.context, [['id', 'in', _.uniq(members_ids)]]); + dataset.read_slice(['id', 'name']).done(function(result) { + _.each(result, function(v, k) { + // Set the proper value in the DOM + self.$el.find('img[data-member_id=' + v.id + ']').attr('title', v.name).tipsy({ + offset: 10 + }); + }); + }); + }, + on_groups_started: function() { + var self = this; + self._super.apply(self, arguments); + + if (self.dataset.model === 'crm.case.section') { + self.crm_display_members_names(); + } + }, + }); + openerp.web_kanban.KanbanRecord.include({ on_card_clicked: function() { if (this.view.dataset.model === 'crm.case.section') { diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index afaa6a3578c..8d0d6b1d817 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -297,7 +297,7 @@
- +
Invoiced Forecast From a254e5fca9b5b436dbc5f1207ea64585bd6c96e2 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Fri, 15 Nov 2013 16:19:09 +0530 Subject: [PATCH 065/130] [ADD] added record rules for analysis report objects. bzr revid: tpa@tinyerp.com-20131115104909-izjhuoy3eyy1vfjk --- addons/crm/security/crm_security.xml | 14 ++++++++++++++ addons/sale/security/sale_security.xml | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/addons/crm/security/crm_security.xml b/addons/crm/security/crm_security.xml index ef7eb8b24db..efc335391f8 100644 --- a/addons/crm/security/crm_security.xml +++ b/addons/crm/security/crm_security.xml @@ -91,5 +91,19 @@ + + Personal Leads Analysis + + ['|',('user_id','=',user.id),('user_id','=',False)] + + + + + All Leads Analysis + + [(1,'=',1)] + + + diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml index 557562275d7..c34f29fc81b 100644 --- a/addons/sale/security/sale_security.xml +++ b/addons/sale/security/sale_security.xml @@ -78,6 +78,13 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + Sales Order Analysis multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + @@ -93,6 +100,19 @@ + + Personal Orders Analysis + + ['|',('user_id','=',user.id),('user_id','=',False)] + + + + + All Orders Analysis + + [(1,'=',1)] + + Personal Order Lines From a68dbfe28693fc732eb05eacf6175e0608d8f7d9 Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Tue, 26 Nov 2013 12:12:38 +0530 Subject: [PATCH 066/130] [RMV] remove all currecny related stuff as per the comments of fp sir. bzr revid: tpa@tinyerp.com-20131126064238-odztahk0kofak3vh --- addons/crm/crm.py | 38 ++-------- addons/crm/crm_case_section_view.xml | 5 +- addons/crm/static/src/css/crm.css | 11 --- addons/sale_crm/sale_crm.py | 26 ++----- addons/sale_crm/sale_crm_view.xml | 11 ++- addons/sale_crm/tests/__init__.py | 28 ------- addons/sale_crm/tests/test_sale_crm_case.py | 83 --------------------- 7 files changed, 20 insertions(+), 182 deletions(-) delete mode 100644 addons/sale_crm/tests/__init__.py delete mode 100644 addons/sale_crm/tests/test_sale_crm_case.py diff --git a/addons/crm/crm.py b/addons/crm/crm.py index cddd2a166de..a98fcadb959 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -101,14 +101,7 @@ class crm_case_section(osv.osv): def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) - def _currency_conversation(self, cr, uid, id, amount, relation_id, relation_field, context=None): - team = self.browse(cr, uid, id, context=context) - base_currency_id = team.currency_id.id - if relation_field == 'company_id': - base_currency_id = self.pool.get('res.company').browse(cr, uid, relation_id, context=context).currency_id.id - return self.pool.get('res.currency').compute(cr, uid, base_currency_id, team.currency_id.id, amount, context=context) - - def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None): + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): """ Generic method to generate data for bar chart values using SparklineBarWidget. This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). @@ -132,16 +125,8 @@ class crm_case_section(osv.osv): group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) for group in group_obj: group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT) - month = self._period_number - (relativedelta.relativedelta(month_begin, group_begin_date).months + 1) - section_result[month] = {'value': 0, 'tooltip': group_begin_date.strftime('%B')} - inner_groupby = (group.get('__context', {})).get('group_by',[]) - if value_field == 'create_date_count' or not inner_groupby: - section_result[month]['value'] = group.get(value_field, 0) - continue - if inner_groupby: - inner_group_obj = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context) - for groupby in inner_group_obj: - section_result[month]['value'] += self._currency_conversation(cr, uid, id, groupby.get(value_field, 0), groupby['__domain'][0][2], inner_groupby[0], context=context) + month_delta = relativedelta.relativedelta(month_begin, group_begin_date) + section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')} return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): @@ -158,9 +143,9 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() lead_domain = date_domain + [('type', '=', 'lead'), ('section_id', '=', id)] - res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, id, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], context=context) + res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context) opp_domain = date_domain + [('type', '=', 'opportunity'), ('section_id', '=', id)] - res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, id, obj, opp_domain, ['planned_revenue', 'create_date', 'company_id'], 'planned_revenue', ['create_date', 'company_id'], context=context) + res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date'], 'planned_revenue', 'create_date', context=context) return res _columns = { @@ -191,10 +176,7 @@ class crm_case_section(osv.osv): string='Open Leads per Month'), 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', - string='Planned Revenue per Month'), - 'create_uid': fields.many2one('res.users', 'Create User'), - 'currency_id': fields.many2one('res.currency', 'Currency', required=True), - 'currency_symbol': fields.related('currency_id', 'symbol', type='char', string='Currency Symbol'), + string='Planned Revenue per Month') } def _get_stage_common(self, cr, uid, context): @@ -205,7 +187,6 @@ class crm_case_section(osv.osv): 'active': 1, 'stage_ids': _get_stage_common, 'use_leads': True, - 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id, } _sql_constraints = [ @@ -249,13 +230,6 @@ class crm_case_section(osv.osv): mail_alias.unlink(cr, uid, alias_ids, context=context) return res - def onchange_user(self, cr, uid, ids, user_id, context=None): - if user_id: - currency_id = self.pool.get('res.users').browse(cr, uid, user_id, context=context).company_id.currency_id.id - else: - currency_id = self.browse(cr, uid, ids[0], context=context).create_uid.company_id.currency_id.id - return {'value': {'currency_id': currency_id}} - class crm_case_categ(osv.osv): """ Category of Case """ _name = "crm.case.categ" diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index e032376cb05..b674bf95061 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -124,7 +124,6 @@ -
@@ -148,7 +147,7 @@
@@ -224,7 +223,7 @@
- + diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css index 311727fe51d..f11e932504e 100644 --- a/addons/crm/static/src/css/crm.css +++ b/addons/crm/static/src/css/crm.css @@ -55,17 +55,6 @@ font-size: 10px; } -.openerp .oe_kanban_view .oe_salesteams_justgage { - color: black; - display: inline-block; - bottom: 0; -} - -.openerp .oe_kanban_view .oe_sparkline_bar { - height: 20px; - width: 36px !important; -} - .openerp .oe_kanban_view .oe_salesteams_help { display: inline-block; } diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 1fedc6a9945..a2c2b7e2af3 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -45,19 +45,6 @@ class sale_order(osv.osv): class crm_case_section(osv.osv): _inherit = 'crm.case.section' - def _currency_conversation(self, cr, uid, id, amount, relation_id, relation_field, context=None): - team = self.browse(cr, uid, id, context=context) - base_currency_id = team.currency_id.id - if relation_field not in ['pricelist_id', 'currency_id']: - return super(crm_case_section, self)._currency_conversation(cr, uid, id, amount, relation_id, relation_field, context=context) - if relation_field == 'pricelist_id': - base_currency_id = self.pool.get('product.pricelist').browse(cr, uid, relation_id, context=context).currency_id.id - elif relation_field == 'currency_id': - currency_rates = self.pool.get('res.currency.rate').search_read(cr, uid, [('rate', '=', 1)], ['currency_id'], limit=1, context=context) - if currency_rates: - base_currency_id = currency_rates[0]['currency_id'][0] - return self.pool.get('res.currency').compute(cr, uid, base_currency_id, team.currency_id.id, amount, context=context) - def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): obj = self.pool.get('sale.order') res = dict.fromkeys(ids, False) @@ -67,9 +54,9 @@ class crm_case_section(osv.osv): for id in ids: res[id] = dict() created_domain = [('section_id', '=', id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, id, obj, created_domain, ['amount_total', 'date_order', 'pricelist_id'], 'amount_total', ['date_order', 'pricelist_id'], context=context) + res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, id, obj, validated_domain, ['amount_total', 'date_confirm', 'pricelist_id'], 'amount_total', ['date_confirm', 'pricelist_id'], context=context) + res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm'], 'amount_total', 'date_confirm', context=context) return res def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): @@ -80,15 +67,15 @@ class crm_case_section(osv.osv): date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) for id in ids: created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] - res[id] = self.__get_bar_values(cr, uid, id, obj, created_domain, ['price_total', 'date', 'currency_id'], 'price_total', ['date', 'currency_id'], context=context) + res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) return res _columns = { - 'invoiced_forecast': fields.float("Invoice Forecast", + 'invoiced_forecast': fields.integer(string='Invoice Forecast', help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" "team should invoice this month. It is used to compute the progression ratio \n" " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.float("Invoice Target", + 'invoiced_target': fields.integer(string='Invoice Target', help="Target of invoice revenue for the current month. This is the amount the sales \n" "team estimates to be able to invoice this month."), 'monthly_quoted': fields.function(_get_sale_orders_data, @@ -103,7 +90,8 @@ class crm_case_section(osv.osv): } def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': value}, context=context) + return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) + class res_users(osv.Model): _inherit = 'res.users' diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index ce8c5a26efb..4669f669276 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -248,9 +248,8 @@ - - - + + @@ -273,7 +272,7 @@
Sales Orders - + Revenue of confirmed sales orders per month.
Click to acces the Sales Analysis.
@@ -283,7 +282,7 @@
Invoices - + Revenue of sent invoices per month.
Click to see a detailed analysis of invoices.
@@ -291,7 +290,7 @@
Quotations - + Revenue of created quotations per month.
Click to see a detailed analysis.
diff --git a/addons/sale_crm/tests/__init__.py b/addons/sale_crm/tests/__init__.py deleted file mode 100644 index fc04080875f..00000000000 --- a/addons/sale_crm/tests/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2013-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from . import test_sale_crm_case - -checks = [ - test_sale_crm_case, -] - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/tests/test_sale_crm_case.py b/addons/sale_crm/tests/test_sale_crm_case.py deleted file mode 100644 index 6fb7d4c555a..00000000000 --- a/addons/sale_crm/tests/test_sale_crm_case.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2013-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.addons.mail.tests.common import TestMail - -class TestSaleCrm(TestMail): - - def test_sale_crm_case(self): - """Testing __get_bar_values method""" - cr, uid, = self.cr, self.uid - - # Usefull models - ir_model_obj = self.registry('ir.model.data') - res_company_obj = self.registry('res.company') - res_users_obj = self.registry('res.users') - crm_case_section_obj = self.registry('crm.case.section') - crm_lead_obj = self.registry('crm.lead') - - # Get required ids - direct_sales_id = ir_model_obj.get_object_reference(cr, uid, 'crm', 'section_sales_department')[1] - usd_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'USD')[1] - your_company_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'main_company')[1] - - # Call relevant methods before changing manager of sales team. - opportunities_before = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - sale_order_before = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - invoice_before = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - - # Create new company with USD currency. - res_company_id = res_company_obj.create(cr, uid,{ - 'name': 'New Company', - 'currency_id': usd_id, - 'parent_id': your_company_id - }) - - # Create new user with sales manager access rights for new company. - group_sale_manager_id = ir_model_obj.get_object_reference(cr, uid, 'base', 'group_sale_manager')[1] - res_users_id = res_users_obj.create(cr, uid,{ - 'name': 'User', - 'login': 'admin@example.com', - 'company_id': res_company_id, - 'company_ids': [(6, 0, [res_company_id])], - 'email': 'admin@gmail.com', - 'groups_id': [(6, 0, [group_sale_manager_id])] - }) - - # Change manager of sales team with USD currency. - crm_case_section_obj.write(cr, uid, [direct_sales_id], { - 'user_id' : res_users_id, - 'currency_id': usd_id, - }) - - # Call relevant methods and get converted data in another currency. - opportunities_after = crm_case_section_obj._get_opportunities_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - sale_order_after = crm_case_section_obj._get_sale_orders_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - invoice_after = crm_case_section_obj._get_invoices_data(cr, uid, [direct_sales_id], field_name=False, arg=False, context=False) - - team_currency_rate = crm_case_section_obj.browse(cr, uid, direct_sales_id).currency_id.rate_silent - - # Check currency conversion for Quotations,Sale orders and Invoices in to the current users currency - for month in range(0,5): - self.assertTrue(round(opportunities_before[1]['monthly_planned_revenue'][month]['value']) == round(opportunities_after[1]['monthly_planned_revenue'][month]['value'] / team_currency_rate), "Not Proper Currency Conversion for Opportunities") - self.assertTrue(round(sale_order_before[1]['monthly_quoted'][month]['value']) == round(sale_order_after[1]['monthly_quoted'][month]['value'] / team_currency_rate), "Currency conversion for quotations is wrong") - self.assertTrue(round(sale_order_before[1]['monthly_confirmed'][month]['value']) == round(sale_order_after[1]['monthly_confirmed'][month]['value'] / team_currency_rate), "Currency conversion for sale orders is wrong") - self.assertTrue(round(invoice_before[1][month]['value']) == round(invoice_after[1][month]['value'] / team_currency_rate), "Currency conversion for invoices is wrong") From 4f2e951cde47c1b8245a147937e90e9420c572ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Nov 2013 11:39:21 +0100 Subject: [PATCH 067/130] [IMP] mail: mail_thread: routing: check for exact message_id. WIP: will be improved, notably about what to do for partial match. bzr revid: tde@openerp.com-20131128103921-q1a36otv9ptazehd --- addons/mail/mail_message.py | 4 ++++ addons/mail/mail_thread.py | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index de8e4c68fda..657c0f68e46 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -816,6 +816,10 @@ class mail_message(osv.Model): return email_reply_to + # def baseN(num, b, numerals=string.digits + string.letters): + # return ((num == 0) and "0") or (baseN(num // b, b, numerals).lstrip("0") + numerals[num % b]) + # message-id with db + def _get_message_id(self, cr, uid, values, context=None): message_id = None if not values.get('message_id') and values.get('reply_to'): diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index c169f1dc3f3..0f89159e20a 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -829,9 +829,26 @@ class mail_thread(osv.AbstractModel): email_to = decode_header(message, 'To') references = decode_header(message, 'References') in_reply_to = decode_header(message, 'In-Reply-To') + thread_references = references or in_reply_to + + # 1. message is a reply to an existing message (exact match of message_id) + msg_references = thread_references.split() + for msg_reference in msg_references: + mail_message_ids = self.pool['mail.message'].search( + cr, uid, [('message_id', '=', msg_reference)], context=context) + if mail_message_ids: + original_msg = self.pool['mail.message'].browse(cr, SUPERUSER_ID, mail_message_ids[0], context=context) + model, thread_id = original_msg.model, original_msg.res_id + _logger.info( + 'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s', + email_from, email_to, message_id, model, thread_id, custom_values, uid) + route = self.message_route_verify( + cr, uid, message, message_dict, + (model, thread_id, custom_values, uid, None), + update_author=True, assert_model=True, create_fallback=True, context=context) + return route and [route] or [] # 1. Verify if this is a reply to an existing thread - thread_references = references or in_reply_to ref_match = thread_references and tools.reference_re.search(thread_references) if ref_match: thread_id = int(ref_match.group(1)) From c553167ff87ed3b077d0cec83d82c23c432f4831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Nov 2013 14:31:32 +0100 Subject: [PATCH 068/130] [FIX] mail_message: do not overwrite 'False' message_id when creating a new mail message bzr revid: tde@openerp.com-20131128133132-8oevlk1wyoqytebo --- addons/mail/mail_message.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 657c0f68e46..6b431f4ef3e 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -821,12 +821,11 @@ class mail_message(osv.Model): # message-id with db def _get_message_id(self, cr, uid, values, context=None): - message_id = None - if not values.get('message_id') and values.get('reply_to'): + if values.get('reply_to'): message_id = tools.generate_tracking_message_id('reply_to') - elif not values.get('message_id') and values.get('res_id') and values.get('model'): + elif values.get('res_id') and values.get('model'): message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) - elif not values.get('message_id'): + else: message_id = tools.generate_tracking_message_id('private') return message_id @@ -837,7 +836,7 @@ class mail_message(osv.Model): if 'email_from' not in values: # needed to compute reply_to values['email_from'] = self._get_default_from(cr, uid, context=context) - if not values.get('message_id'): + if 'message_id' not in values: values['message_id'] = self._get_message_id(cr, uid, values, context=context) if 'reply_to' not in values: values['reply_to'] = self._get_reply_to(cr, uid, values, context=context) From 8253453d6439d97f272407f02eadafc99f0df796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Nov 2013 14:32:26 +0100 Subject: [PATCH 069/130] [IMP] mail: mail_thread: routing: implement compat-mode routing It now accepts <111...222..openerp-res_id-model@mydomain.com> partial matching only if the target record has messages without message_id, aka <= 6.1 record. Updated and added tests bzr revid: tde@openerp.com-20131128133226-ce1qyeswi9n83q8v --- addons/mail/mail_thread.py | 37 +++++++++++++--------- addons/mail/tests/test_mail_gateway.py | 43 ++++++++++++++++++++------ 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 0f89159e20a..4bd0ff4a94e 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -723,8 +723,8 @@ class mail_thread(osv.AbstractModel): # Private message: should not contain any thread_id if not model and thread_id: if assert_model: - assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message).' - _warn('posting a message without model should be with a null res_id (private message), resetting thread_id') + assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message), received %s.' % thread_id + _warn('posting a message without model should be with a null res_id (private message), received %s, resetting thread_id' % thread_id) thread_id = 0 # Private message: should have a parent_id (only answers) if not model and not message_dict.get('parent_id'): @@ -821,6 +821,7 @@ class mail_thread(osv.AbstractModel): :return: list of [model, thread_id, custom_values, user_id, alias] """ assert isinstance(message, Message), 'message must be an email.message.Message at this point' + mail_msg_obj = self.pool['mail.message'] fallback_model = model # Get email.message.Message variables for future processing @@ -834,10 +835,9 @@ class mail_thread(osv.AbstractModel): # 1. message is a reply to an existing message (exact match of message_id) msg_references = thread_references.split() for msg_reference in msg_references: - mail_message_ids = self.pool['mail.message'].search( - cr, uid, [('message_id', '=', msg_reference)], context=context) + mail_message_ids = mail_msg_obj.search(cr, uid, [('message_id', '=', msg_reference)], context=context) if mail_message_ids: - original_msg = self.pool['mail.message'].browse(cr, SUPERUSER_ID, mail_message_ids[0], context=context) + original_msg = mail_msg_obj.browse(cr, SUPERUSER_ID, mail_message_ids[0], context=context) model, thread_id = original_msg.model, original_msg.res_id _logger.info( 'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s', @@ -848,29 +848,38 @@ class mail_thread(osv.AbstractModel): update_author=True, assert_model=True, create_fallback=True, context=context) return route and [route] or [] - # 1. Verify if this is a reply to an existing thread + # 2. message is a reply to an existign thread (6.1 compatibility) ref_match = thread_references and tools.reference_re.search(thread_references) if ref_match: thread_id = int(ref_match.group(1)) model = ref_match.group(2) or fallback_model if thread_id and model in self.pool: model_obj = self.pool[model] - if model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'): - _logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s', - email_from, email_to, message_id, model, thread_id, custom_values, uid) - route = self.message_route_verify(cr, uid, message, message_dict, - (model, thread_id, custom_values, uid, None), - update_author=True, assert_model=True, create_fallback=True, context=context) + compat_mail_msg_ids = mail_msg_obj.search( + cr, uid, [ + ('message_id', '=', False), + ('model', '=', model), + ('res_id', '=', thread_id), + ], context=context) + print 'cacaprout', compat_mail_msg_ids + if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'): + _logger.info( + 'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s', + email_from, email_to, message_id, model, thread_id, custom_values, uid) + route = self.message_route_verify( + cr, uid, message, message_dict, + (model, thread_id, custom_values, uid, None), + update_author=True, assert_model=True, create_fallback=True, context=context) return route and [route] or [] # 2. Reply to a private message if in_reply_to: - mail_message_ids = self.pool.get('mail.message').search(cr, uid, [ + mail_message_ids = mail_msg_obj.search(cr, uid, [ ('message_id', '=', in_reply_to), '!', ('message_id', 'ilike', 'reply_to') ], limit=1, context=context) if mail_message_ids: - mail_message = self.pool.get('mail.message').browse(cr, uid, mail_message_ids[0], context=context) + mail_message = mail_msg_obj.browse(cr, uid, mail_message_ids[0], context=context) _logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s', email_from, email_to, message_id, mail_message.id, custom_values, uid) route = self.message_route_verify(cr, uid, message, message_dict, diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py index b2a1c3250aa..c88ed6555d6 100644 --- a/addons/mail/tests/test_mail_gateway.py +++ b/addons/mail/tests/test_mail_gateway.py @@ -163,7 +163,7 @@ class TestMailgateway(TestMail): self.assertIn('
Should create a multipart/mixed: from gmail, bold, with attachment.

', res.get('body', ''), 'message_parse: html version should be in body after parsing multipart/mixed') - @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') + # @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') def test_10_message_process(self): """ Testing incoming emails processing. """ cr, uid, user_raoul = self.cr, self.uid, self.user_raoul @@ -378,7 +378,7 @@ class TestMailgateway(TestMail): frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com', msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>', to='erroneous@example.com>', subject='Re: news', - extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id) + extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n') # Test: no group 'Re: news' created, still only 1 Frogs group self.assertEqual(len(frog_groups), 0, 'message_process: reply on Frogs should not have created a new group with new subject') @@ -387,16 +387,41 @@ class TestMailgateway(TestMail): 'message_process: reply on Frogs should not have created a duplicate group with old subject') frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) # Test: one new message - self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 2 messages after reply') + self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 3 messages after reply') # Test: author (and not recipient) added as follower frog_follower_ids = set([p.id for p in frog_group.message_follower_ids]) self.assertEqual(frog_follower_ids, set([p1id, p2id]), 'message_process: after reply, group should have 2 followers') + # Do: incoming email with ref holding model / res_id but that does not match any message in the thread: must raise since OpenERP saas-3 + self.assertRaises(AssertionError, + format_and_process, + MAIL_TEMPLATE, email_from='other5@gmail.com', + to='noone@example.com', subject='spam', + extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id, + msg_id='<1.1.JavaMail.new@agrolait.com>') + # There are 6.1 messages, activate compat mode + tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id}) + # Do: compat mode accepts partial-matching emails + frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com', + msg_id='<1.2.JavaMail.new@agrolait.com>', + to='noone@example.com>', subject='spam', + extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id) + self.mail_message.unlink(cr, uid, [tmp_msg_id]) + # Test: no group 'Re: news' created, still only 1 Frogs group + self.assertEqual(len(frog_groups), 0, + 'message_process: reply on Frogs should not have created a new group with new subject') + frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) + self.assertEqual(len(frog_groups), 1, + 'message_process: reply on Frogs should not have created a duplicate group with old subject') + frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) + # Test: one new message + self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply') + # Do: due to some issue, same email goes back into the mailgateway frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com', msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>', - subject='Re: news', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id) + subject='Re: news', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n') # Test: no group 'Re: news' created, still only 1 Frogs group self.assertEqual(len(frog_groups), 0, 'message_process: reply on Frogs should not have created a new group with new subject') @@ -405,7 +430,7 @@ class TestMailgateway(TestMail): 'message_process: reply on Frogs should not have created a duplicate group with old subject') frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) # Test: no new message - self.assertEqual(len(frog_group.message_ids), 3, 'message_process: message with already existing message_id should not have been duplicated') + self.assertEqual(len(frog_group.message_ids), 4, 'message_process: message with already existing message_id should not have been duplicated') # Test: message_id is still unique msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')]) self.assertEqual(len(msg_ids), 1, @@ -422,7 +447,7 @@ class TestMailgateway(TestMail): format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik ', to='erroneous@example.com>', subject='Re: news (2)', msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>', - extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id) + extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>') frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) # Test: author is A-Raoul (only existing) @@ -436,7 +461,7 @@ class TestMailgateway(TestMail): format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik ', to='erroneous@example.com>', subject='Re: news (3)', msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>', - extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id) + extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>') frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) # Test: author is Raoul (user), not A-Raoul @@ -451,7 +476,7 @@ class TestMailgateway(TestMail): format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik ', to='erroneous@example.com>', subject='Re: news (3)', msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>', - extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id) + extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>') frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) # Test: author is Raoul (user), not A-Raoul @@ -498,7 +523,7 @@ class TestMailgateway(TestMail): self.assertIn('
\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n
', msg.body, 'message_process: plaintext incoming email incorrectly parsed') - @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') + # @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') def test_20_thread_parent_resolution(self): """ Testing parent/child relationships are correctly established when processing incoming mails """ cr, uid = self.cr, self.uid From e057a64a02d77e87bbef6e58caebf8aac129e839 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Mon, 2 Dec 2013 14:38:03 +0100 Subject: [PATCH 070/130] [IMP] update fontawseome to 4.0.3 bzr revid: al@openerp.com-20131202133803-1muybwahwjo9e5sm --- .../lib/fontawesome/css/font-awesome.css | 1401 ++++++++--------- .../lib/fontawesome/font/FontAwesome.otf | Bin 61896 -> 0 bytes .../fontawesome/font/fontawesome-webfont.eot | Bin 37405 -> 0 bytes .../fontawesome/font/fontawesome-webfont.woff | Bin 43572 -> 0 bytes .../lib/fontawesome/fonts/FontAwesome.otf | Bin 0 -> 62856 bytes .../fontawesome/fonts/fontawesome-webfont.eot | Bin 0 -> 38205 bytes .../{font => fonts}/fontawesome-webfont.svg | 31 +- .../{font => fonts}/fontawesome-webfont.ttf | Bin 79076 -> 80652 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 44432 bytes 9 files changed, 653 insertions(+), 779 deletions(-) delete mode 100644 addons/web/static/lib/fontawesome/font/FontAwesome.otf delete mode 100755 addons/web/static/lib/fontawesome/font/fontawesome-webfont.eot delete mode 100755 addons/web/static/lib/fontawesome/font/fontawesome-webfont.woff create mode 100644 addons/web/static/lib/fontawesome/fonts/FontAwesome.otf create mode 100755 addons/web/static/lib/fontawesome/fonts/fontawesome-webfont.eot rename addons/web/static/lib/fontawesome/{font => fonts}/fontawesome-webfont.svg (96%) rename addons/web/static/lib/fontawesome/{font => fonts}/fontawesome-webfont.ttf (91%) create mode 100755 addons/web/static/lib/fontawesome/fonts/fontawesome-webfont.woff diff --git a/addons/web/static/lib/fontawesome/css/font-awesome.css b/addons/web/static/lib/fontawesome/css/font-awesome.css index 7ede1828a78..048cff97398 100644 --- a/addons/web/static/lib/fontawesome/css/font-awesome.css +++ b/addons/web/static/lib/fontawesome/css/font-awesome.css @@ -1,145 +1,69 @@ /*! - * Font Awesome 3.2.1 - * the iconic font designed for Bootstrap - * ------------------------------------------------------------------------------ - * The full suite of pictographic icons, examples, and documentation can be - * found at http://fontawesome.io. Stay up to date on Twitter at - * http://twitter.com/fontawesome. - * - * License - * ------------------------------------------------------------------------------ - * - The Font Awesome font is licensed under SIL OFL 1.1 - - * http://scripts.sil.org/OFL - * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - - * http://opensource.org/licenses/mit-license.html - * - Font Awesome documentation licensed under CC BY 3.0 - - * http://creativecommons.org/licenses/by/3.0/ - * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: - * "Font Awesome by Dave Gandy - http://fontawesome.io" - * - * Author - Dave Gandy - * ------------------------------------------------------------------------------ - * Email: dave@fontawesome.io - * Twitter: http://twitter.com/davegandy - * Work: Lead Product Designer @ Kyruus - http://kyruus.com + * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('../font/fontawesome-webfont.eot?v=3.2.1'); - src: url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'), url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'), url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'), url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg'); + src: url('../fonts/fontawesome-webfont.eot?v=4.0.3'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } -/* FONT AWESOME CORE - * -------------------------- */ -[class^="icon-"], -[class*=" icon-"] { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; -} -[class^="icon-"]:before, -[class*=" icon-"]:before { - text-decoration: inherit; +.fa { display: inline-block; - speak: none; + font-family: FontAwesome; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } /* makes the font 33% larger relative to the icon container */ -.icon-large:before { - vertical-align: -10%; +.fa-lg { font-size: 1.3333333333333333em; + line-height: 0.75em; + vertical-align: -15%; } -/* makes sure icons active on rollover in links */ -a [class^="icon-"], -a [class*=" icon-"] { - display: inline; +.fa-2x { + font-size: 2em; } -/* increased font size for icon-large */ -[class^="icon-"].icon-fixed-width, -[class*=" icon-"].icon-fixed-width { - display: inline-block; - width: 1.1428571428571428em; - text-align: right; - padding-right: 0.2857142857142857em; +.fa-3x { + font-size: 3em; } -[class^="icon-"].icon-fixed-width.icon-large, -[class*=" icon-"].icon-fixed-width.icon-large { - width: 1.4285714285714286em; +.fa-4x { + font-size: 4em; } -.icons-ul { +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.2857142857142858em; + text-align: center; +} +.fa-ul { + padding-left: 0; margin-left: 2.142857142857143em; list-style-type: none; } -.icons-ul > li { +.fa-ul > li { position: relative; } -.icons-ul .icon-li { +.fa-li { position: absolute; left: -2.142857142857143em; width: 2.142857142857143em; + top: 0.14285714285714285em; text-align: center; - line-height: inherit; } -[class^="icon-"].hide, -[class*=" icon-"].hide { - display: none; +.fa-li.fa-lg { + left: -1.8571428571428572em; } -.icon-muted { - color: #eeeeee; -} -.icon-light { - color: #ffffff; -} -.icon-dark { - color: #333333; -} -.icon-border { - border: solid 1px #eeeeee; +.fa-border { padding: .2em .25em .15em; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -.icon-2x { - font-size: 2em; -} -.icon-2x.icon-border { - border-width: 2px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.icon-3x { - font-size: 3em; -} -.icon-3x.icon-border { - border-width: 3px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} -.icon-4x { - font-size: 4em; -} -.icon-4x.icon-border { - border-width: 4px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} -.icon-5x { - font-size: 5em; -} -.icon-5x.icon-border { - border-width: 5px; - -webkit-border-radius: 7px; - -moz-border-radius: 7px; - border-radius: 7px; + border: solid 0.08em #eeeeee; + border-radius: .1em; } .pull-right { float: right; @@ -147,147 +71,18 @@ a [class*=" icon-"] { .pull-left { float: left; } -[class^="icon-"].pull-left, -[class*=" icon-"].pull-left { +.fa.pull-left { margin-right: .3em; } -[class^="icon-"].pull-right, -[class*=" icon-"].pull-right { +.fa.pull-right { margin-left: .3em; } -/* BOOTSTRAP SPECIFIC CLASSES - * -------------------------- */ -/* Bootstrap 2.0 sprites.less reset */ -[class^="icon-"], -[class*=" icon-"] { - display: inline; - width: auto; - height: auto; - line-height: normal; - vertical-align: baseline; - background-image: none; - background-position: 0% 0%; - background-repeat: repeat; - margin-top: 0; -} -/* more sprites.less reset */ -.icon-white, -.nav-pills > .active > a > [class^="icon-"], -.nav-pills > .active > a > [class*=" icon-"], -.nav-list > .active > a > [class^="icon-"], -.nav-list > .active > a > [class*=" icon-"], -.navbar-inverse .nav > .active > a > [class^="icon-"], -.navbar-inverse .nav > .active > a > [class*=" icon-"], -.dropdown-menu > li > a:hover > [class^="icon-"], -.dropdown-menu > li > a:hover > [class*=" icon-"], -.dropdown-menu > .active > a > [class^="icon-"], -.dropdown-menu > .active > a > [class*=" icon-"], -.dropdown-submenu:hover > a > [class^="icon-"], -.dropdown-submenu:hover > a > [class*=" icon-"] { - background-image: none; -} -/* keeps Bootstrap styles with and without icons the same */ -.btn [class^="icon-"].icon-large, -.nav [class^="icon-"].icon-large, -.btn [class*=" icon-"].icon-large, -.nav [class*=" icon-"].icon-large { - line-height: .9em; -} -.btn [class^="icon-"].icon-spin, -.nav [class^="icon-"].icon-spin, -.btn [class*=" icon-"].icon-spin, -.nav [class*=" icon-"].icon-spin { - display: inline-block; -} -.nav-tabs [class^="icon-"], -.nav-pills [class^="icon-"], -.nav-tabs [class*=" icon-"], -.nav-pills [class*=" icon-"], -.nav-tabs [class^="icon-"].icon-large, -.nav-pills [class^="icon-"].icon-large, -.nav-tabs [class*=" icon-"].icon-large, -.nav-pills [class*=" icon-"].icon-large { - line-height: .9em; -} -.btn [class^="icon-"].pull-left.icon-2x, -.btn [class*=" icon-"].pull-left.icon-2x, -.btn [class^="icon-"].pull-right.icon-2x, -.btn [class*=" icon-"].pull-right.icon-2x { - margin-top: .18em; -} -.btn [class^="icon-"].icon-spin.icon-large, -.btn [class*=" icon-"].icon-spin.icon-large { - line-height: .8em; -} -.btn.btn-small [class^="icon-"].pull-left.icon-2x, -.btn.btn-small [class*=" icon-"].pull-left.icon-2x, -.btn.btn-small [class^="icon-"].pull-right.icon-2x, -.btn.btn-small [class*=" icon-"].pull-right.icon-2x { - margin-top: .25em; -} -.btn.btn-large [class^="icon-"], -.btn.btn-large [class*=" icon-"] { - margin-top: 0; -} -.btn.btn-large [class^="icon-"].pull-left.icon-2x, -.btn.btn-large [class*=" icon-"].pull-left.icon-2x, -.btn.btn-large [class^="icon-"].pull-right.icon-2x, -.btn.btn-large [class*=" icon-"].pull-right.icon-2x { - margin-top: .05em; -} -.btn.btn-large [class^="icon-"].pull-left.icon-2x, -.btn.btn-large [class*=" icon-"].pull-left.icon-2x { - margin-right: .2em; -} -.btn.btn-large [class^="icon-"].pull-right.icon-2x, -.btn.btn-large [class*=" icon-"].pull-right.icon-2x { - margin-left: .2em; -} -/* Fixes alignment in nav lists */ -.nav-list [class^="icon-"], -.nav-list [class*=" icon-"] { - line-height: inherit; -} -/* EXTRAS - * -------------------------- */ -/* Stacked and layered icon */ -.icon-stack { - position: relative; - display: inline-block; - width: 2em; - height: 2em; - line-height: 2em; - vertical-align: -35%; -} -.icon-stack [class^="icon-"], -.icon-stack [class*=" icon-"] { - display: block; - text-align: center; - position: absolute; - width: 100%; - height: 100%; - font-size: 1em; - line-height: inherit; - *line-height: 2em; -} -.icon-stack .icon-stack-base { - font-size: 2em; - *line-height: 1em; -} -/* Animated rotating icon */ -.icon-spin { - display: inline-block; +.fa-spin { + -webkit-animation: spin 2s infinite linear; -moz-animation: spin 2s infinite linear; -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; animation: spin 2s infinite linear; } -/* Prevent stack and spinners from being taken inline when inside a link */ -a .icon-stack, -a .icon-spin { - display: inline-block; - text-decoration: none; -} @-moz-keyframes spin { 0% { -moz-transform: rotate(0deg); @@ -328,1152 +123,1216 @@ a .icon-spin { transform: rotate(359deg); } } -/* Icon rotations and mirroring */ -.icon-rotate-90:before { +.fa-rotate-90 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); -ms-transform: rotate(90deg); -o-transform: rotate(90deg); transform: rotate(90deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); } -.icon-rotate-180:before { +.fa-rotate-180 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); } -.icon-rotate-270:before { +.fa-rotate-270 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); -webkit-transform: rotate(270deg); -moz-transform: rotate(270deg); -ms-transform: rotate(270deg); -o-transform: rotate(270deg); transform: rotate(270deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); } -.icon-flip-horizontal:before { +.fa-flip-horizontal { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); -webkit-transform: scale(-1, 1); -moz-transform: scale(-1, 1); -ms-transform: scale(-1, 1); -o-transform: scale(-1, 1); transform: scale(-1, 1); } -.icon-flip-vertical:before { +.fa-flip-vertical { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); -webkit-transform: scale(1, -1); -moz-transform: scale(1, -1); -ms-transform: scale(1, -1); -o-transform: scale(1, -1); transform: scale(1, -1); } -/* ensure rotation occurs inside anchor tags */ -a .icon-rotate-90:before, -a .icon-rotate-180:before, -a .icon-rotate-270:before, -a .icon-flip-horizontal:before, -a .icon-flip-vertical:before { +.fa-stack { + position: relative; display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #ffffff; } /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ -.icon-glass:before { +.fa-glass:before { content: "\f000"; } -.icon-music:before { +.fa-music:before { content: "\f001"; } -.icon-search:before { +.fa-search:before { content: "\f002"; } -.icon-envelope-alt:before { +.fa-envelope-o:before { content: "\f003"; } -.icon-heart:before { +.fa-heart:before { content: "\f004"; } -.icon-star:before { +.fa-star:before { content: "\f005"; } -.icon-star-empty:before { +.fa-star-o:before { content: "\f006"; } -.icon-user:before { +.fa-user:before { content: "\f007"; } -.icon-film:before { +.fa-film:before { content: "\f008"; } -.icon-th-large:before { +.fa-th-large:before { content: "\f009"; } -.icon-th:before { +.fa-th:before { content: "\f00a"; } -.icon-th-list:before { +.fa-th-list:before { content: "\f00b"; } -.icon-ok:before { +.fa-check:before { content: "\f00c"; } -.icon-remove:before { +.fa-times:before { content: "\f00d"; } -.icon-zoom-in:before { +.fa-search-plus:before { content: "\f00e"; } -.icon-zoom-out:before { +.fa-search-minus:before { content: "\f010"; } -.icon-power-off:before, -.icon-off:before { +.fa-power-off:before { content: "\f011"; } -.icon-signal:before { +.fa-signal:before { content: "\f012"; } -.icon-gear:before, -.icon-cog:before { +.fa-gear:before, +.fa-cog:before { content: "\f013"; } -.icon-trash:before { +.fa-trash-o:before { content: "\f014"; } -.icon-home:before { +.fa-home:before { content: "\f015"; } -.icon-file-alt:before { +.fa-file-o:before { content: "\f016"; } -.icon-time:before { +.fa-clock-o:before { content: "\f017"; } -.icon-road:before { +.fa-road:before { content: "\f018"; } -.icon-download-alt:before { +.fa-download:before { content: "\f019"; } -.icon-download:before { +.fa-arrow-circle-o-down:before { content: "\f01a"; } -.icon-upload:before { +.fa-arrow-circle-o-up:before { content: "\f01b"; } -.icon-inbox:before { +.fa-inbox:before { content: "\f01c"; } -.icon-play-circle:before { +.fa-play-circle-o:before { content: "\f01d"; } -.icon-rotate-right:before, -.icon-repeat:before { +.fa-rotate-right:before, +.fa-repeat:before { content: "\f01e"; } -.icon-refresh:before { +.fa-refresh:before { content: "\f021"; } -.icon-list-alt:before { +.fa-list-alt:before { content: "\f022"; } -.icon-lock:before { +.fa-lock:before { content: "\f023"; } -.icon-flag:before { +.fa-flag:before { content: "\f024"; } -.icon-headphones:before { +.fa-headphones:before { content: "\f025"; } -.icon-volume-off:before { +.fa-volume-off:before { content: "\f026"; } -.icon-volume-down:before { +.fa-volume-down:before { content: "\f027"; } -.icon-volume-up:before { +.fa-volume-up:before { content: "\f028"; } -.icon-qrcode:before { +.fa-qrcode:before { content: "\f029"; } -.icon-barcode:before { +.fa-barcode:before { content: "\f02a"; } -.icon-tag:before { +.fa-tag:before { content: "\f02b"; } -.icon-tags:before { +.fa-tags:before { content: "\f02c"; } -.icon-book:before { +.fa-book:before { content: "\f02d"; } -.icon-bookmark:before { +.fa-bookmark:before { content: "\f02e"; } -.icon-print:before { +.fa-print:before { content: "\f02f"; } -.icon-camera:before { +.fa-camera:before { content: "\f030"; } -.icon-font:before { +.fa-font:before { content: "\f031"; } -.icon-bold:before { +.fa-bold:before { content: "\f032"; } -.icon-italic:before { +.fa-italic:before { content: "\f033"; } -.icon-text-height:before { +.fa-text-height:before { content: "\f034"; } -.icon-text-width:before { +.fa-text-width:before { content: "\f035"; } -.icon-align-left:before { +.fa-align-left:before { content: "\f036"; } -.icon-align-center:before { +.fa-align-center:before { content: "\f037"; } -.icon-align-right:before { +.fa-align-right:before { content: "\f038"; } -.icon-align-justify:before { +.fa-align-justify:before { content: "\f039"; } -.icon-list:before { +.fa-list:before { content: "\f03a"; } -.icon-indent-left:before { +.fa-dedent:before, +.fa-outdent:before { content: "\f03b"; } -.icon-indent-right:before { +.fa-indent:before { content: "\f03c"; } -.icon-facetime-video:before { +.fa-video-camera:before { content: "\f03d"; } -.icon-picture:before { +.fa-picture-o:before { content: "\f03e"; } -.icon-pencil:before { +.fa-pencil:before { content: "\f040"; } -.icon-map-marker:before { +.fa-map-marker:before { content: "\f041"; } -.icon-adjust:before { +.fa-adjust:before { content: "\f042"; } -.icon-tint:before { +.fa-tint:before { content: "\f043"; } -.icon-edit:before { +.fa-edit:before, +.fa-pencil-square-o:before { content: "\f044"; } -.icon-share:before { +.fa-share-square-o:before { content: "\f045"; } -.icon-check:before { +.fa-check-square-o:before { content: "\f046"; } -.icon-move:before { +.fa-arrows:before { content: "\f047"; } -.icon-step-backward:before { +.fa-step-backward:before { content: "\f048"; } -.icon-fast-backward:before { +.fa-fast-backward:before { content: "\f049"; } -.icon-backward:before { +.fa-backward:before { content: "\f04a"; } -.icon-play:before { +.fa-play:before { content: "\f04b"; } -.icon-pause:before { +.fa-pause:before { content: "\f04c"; } -.icon-stop:before { +.fa-stop:before { content: "\f04d"; } -.icon-forward:before { +.fa-forward:before { content: "\f04e"; } -.icon-fast-forward:before { +.fa-fast-forward:before { content: "\f050"; } -.icon-step-forward:before { +.fa-step-forward:before { content: "\f051"; } -.icon-eject:before { +.fa-eject:before { content: "\f052"; } -.icon-chevron-left:before { +.fa-chevron-left:before { content: "\f053"; } -.icon-chevron-right:before { +.fa-chevron-right:before { content: "\f054"; } -.icon-plus-sign:before { +.fa-plus-circle:before { content: "\f055"; } -.icon-minus-sign:before { +.fa-minus-circle:before { content: "\f056"; } -.icon-remove-sign:before { +.fa-times-circle:before { content: "\f057"; } -.icon-ok-sign:before { +.fa-check-circle:before { content: "\f058"; } -.icon-question-sign:before { +.fa-question-circle:before { content: "\f059"; } -.icon-info-sign:before { +.fa-info-circle:before { content: "\f05a"; } -.icon-screenshot:before { +.fa-crosshairs:before { content: "\f05b"; } -.icon-remove-circle:before { +.fa-times-circle-o:before { content: "\f05c"; } -.icon-ok-circle:before { +.fa-check-circle-o:before { content: "\f05d"; } -.icon-ban-circle:before { +.fa-ban:before { content: "\f05e"; } -.icon-arrow-left:before { +.fa-arrow-left:before { content: "\f060"; } -.icon-arrow-right:before { +.fa-arrow-right:before { content: "\f061"; } -.icon-arrow-up:before { +.fa-arrow-up:before { content: "\f062"; } -.icon-arrow-down:before { +.fa-arrow-down:before { content: "\f063"; } -.icon-mail-forward:before, -.icon-share-alt:before { +.fa-mail-forward:before, +.fa-share:before { content: "\f064"; } -.icon-resize-full:before { +.fa-expand:before { content: "\f065"; } -.icon-resize-small:before { +.fa-compress:before { content: "\f066"; } -.icon-plus:before { +.fa-plus:before { content: "\f067"; } -.icon-minus:before { +.fa-minus:before { content: "\f068"; } -.icon-asterisk:before { +.fa-asterisk:before { content: "\f069"; } -.icon-exclamation-sign:before { +.fa-exclamation-circle:before { content: "\f06a"; } -.icon-gift:before { +.fa-gift:before { content: "\f06b"; } -.icon-leaf:before { +.fa-leaf:before { content: "\f06c"; } -.icon-fire:before { +.fa-fire:before { content: "\f06d"; } -.icon-eye-open:before { +.fa-eye:before { content: "\f06e"; } -.icon-eye-close:before { +.fa-eye-slash:before { content: "\f070"; } -.icon-warning-sign:before { +.fa-warning:before, +.fa-exclamation-triangle:before { content: "\f071"; } -.icon-plane:before { +.fa-plane:before { content: "\f072"; } -.icon-calendar:before { +.fa-calendar:before { content: "\f073"; } -.icon-random:before { +.fa-random:before { content: "\f074"; } -.icon-comment:before { +.fa-comment:before { content: "\f075"; } -.icon-magnet:before { +.fa-magnet:before { content: "\f076"; } -.icon-chevron-up:before { +.fa-chevron-up:before { content: "\f077"; } -.icon-chevron-down:before { +.fa-chevron-down:before { content: "\f078"; } -.icon-retweet:before { +.fa-retweet:before { content: "\f079"; } -.icon-shopping-cart:before { +.fa-shopping-cart:before { content: "\f07a"; } -.icon-folder-close:before { +.fa-folder:before { content: "\f07b"; } -.icon-folder-open:before { +.fa-folder-open:before { content: "\f07c"; } -.icon-resize-vertical:before { +.fa-arrows-v:before { content: "\f07d"; } -.icon-resize-horizontal:before { +.fa-arrows-h:before { content: "\f07e"; } -.icon-bar-chart:before { +.fa-bar-chart-o:before { content: "\f080"; } -.icon-twitter-sign:before { +.fa-twitter-square:before { content: "\f081"; } -.icon-facebook-sign:before { +.fa-facebook-square:before { content: "\f082"; } -.icon-camera-retro:before { +.fa-camera-retro:before { content: "\f083"; } -.icon-key:before { +.fa-key:before { content: "\f084"; } -.icon-gears:before, -.icon-cogs:before { +.fa-gears:before, +.fa-cogs:before { content: "\f085"; } -.icon-comments:before { +.fa-comments:before { content: "\f086"; } -.icon-thumbs-up-alt:before { +.fa-thumbs-o-up:before { content: "\f087"; } -.icon-thumbs-down-alt:before { +.fa-thumbs-o-down:before { content: "\f088"; } -.icon-star-half:before { +.fa-star-half:before { content: "\f089"; } -.icon-heart-empty:before { +.fa-heart-o:before { content: "\f08a"; } -.icon-signout:before { +.fa-sign-out:before { content: "\f08b"; } -.icon-linkedin-sign:before { +.fa-linkedin-square:before { content: "\f08c"; } -.icon-pushpin:before { +.fa-thumb-tack:before { content: "\f08d"; } -.icon-external-link:before { +.fa-external-link:before { content: "\f08e"; } -.icon-signin:before { +.fa-sign-in:before { content: "\f090"; } -.icon-trophy:before { +.fa-trophy:before { content: "\f091"; } -.icon-github-sign:before { +.fa-github-square:before { content: "\f092"; } -.icon-upload-alt:before { +.fa-upload:before { content: "\f093"; } -.icon-lemon:before { +.fa-lemon-o:before { content: "\f094"; } -.icon-phone:before { +.fa-phone:before { content: "\f095"; } -.icon-unchecked:before, -.icon-check-empty:before { +.fa-square-o:before { content: "\f096"; } -.icon-bookmark-empty:before { +.fa-bookmark-o:before { content: "\f097"; } -.icon-phone-sign:before { +.fa-phone-square:before { content: "\f098"; } -.icon-twitter:before { +.fa-twitter:before { content: "\f099"; } -.icon-facebook:before { +.fa-facebook:before { content: "\f09a"; } -.icon-github:before { +.fa-github:before { content: "\f09b"; } -.icon-unlock:before { +.fa-unlock:before { content: "\f09c"; } -.icon-credit-card:before { +.fa-credit-card:before { content: "\f09d"; } -.icon-rss:before { +.fa-rss:before { content: "\f09e"; } -.icon-hdd:before { +.fa-hdd-o:before { content: "\f0a0"; } -.icon-bullhorn:before { +.fa-bullhorn:before { content: "\f0a1"; } -.icon-bell:before { - content: "\f0a2"; -} -.icon-certificate:before { - content: "\f0a3"; -} -.icon-hand-right:before { - content: "\f0a4"; -} -.icon-hand-left:before { - content: "\f0a5"; -} -.icon-hand-up:before { - content: "\f0a6"; -} -.icon-hand-down:before { - content: "\f0a7"; -} -.icon-circle-arrow-left:before { - content: "\f0a8"; -} -.icon-circle-arrow-right:before { - content: "\f0a9"; -} -.icon-circle-arrow-up:before { - content: "\f0aa"; -} -.icon-circle-arrow-down:before { - content: "\f0ab"; -} -.icon-globe:before { - content: "\f0ac"; -} -.icon-wrench:before { - content: "\f0ad"; -} -.icon-tasks:before { - content: "\f0ae"; -} -.icon-filter:before { - content: "\f0b0"; -} -.icon-briefcase:before { - content: "\f0b1"; -} -.icon-fullscreen:before { - content: "\f0b2"; -} -.icon-group:before { - content: "\f0c0"; -} -.icon-link:before { - content: "\f0c1"; -} -.icon-cloud:before { - content: "\f0c2"; -} -.icon-beaker:before { - content: "\f0c3"; -} -.icon-cut:before { - content: "\f0c4"; -} -.icon-copy:before { - content: "\f0c5"; -} -.icon-paperclip:before, -.icon-paper-clip:before { - content: "\f0c6"; -} -.icon-save:before { - content: "\f0c7"; -} -.icon-sign-blank:before { - content: "\f0c8"; -} -.icon-reorder:before { - content: "\f0c9"; -} -.icon-list-ul:before { - content: "\f0ca"; -} -.icon-list-ol:before { - content: "\f0cb"; -} -.icon-strikethrough:before { - content: "\f0cc"; -} -.icon-underline:before { - content: "\f0cd"; -} -.icon-table:before { - content: "\f0ce"; -} -.icon-magic:before { - content: "\f0d0"; -} -.icon-truck:before { - content: "\f0d1"; -} -.icon-pinterest:before { - content: "\f0d2"; -} -.icon-pinterest-sign:before { - content: "\f0d3"; -} -.icon-google-plus-sign:before { - content: "\f0d4"; -} -.icon-google-plus:before { - content: "\f0d5"; -} -.icon-money:before { - content: "\f0d6"; -} -.icon-caret-down:before { - content: "\f0d7"; -} -.icon-caret-up:before { - content: "\f0d8"; -} -.icon-caret-left:before { - content: "\f0d9"; -} -.icon-caret-right:before { - content: "\f0da"; -} -.icon-columns:before { - content: "\f0db"; -} -.icon-sort:before { - content: "\f0dc"; -} -.icon-sort-down:before { - content: "\f0dd"; -} -.icon-sort-up:before { - content: "\f0de"; -} -.icon-envelope:before { - content: "\f0e0"; -} -.icon-linkedin:before { - content: "\f0e1"; -} -.icon-rotate-left:before, -.icon-undo:before { - content: "\f0e2"; -} -.icon-legal:before { - content: "\f0e3"; -} -.icon-dashboard:before { - content: "\f0e4"; -} -.icon-comment-alt:before { - content: "\f0e5"; -} -.icon-comments-alt:before { - content: "\f0e6"; -} -.icon-bolt:before { - content: "\f0e7"; -} -.icon-sitemap:before { - content: "\f0e8"; -} -.icon-umbrella:before { - content: "\f0e9"; -} -.icon-paste:before { - content: "\f0ea"; -} -.icon-lightbulb:before { - content: "\f0eb"; -} -.icon-exchange:before { - content: "\f0ec"; -} -.icon-cloud-download:before { - content: "\f0ed"; -} -.icon-cloud-upload:before { - content: "\f0ee"; -} -.icon-user-md:before { - content: "\f0f0"; -} -.icon-stethoscope:before { - content: "\f0f1"; -} -.icon-suitcase:before { - content: "\f0f2"; -} -.icon-bell-alt:before { +.fa-bell:before { content: "\f0f3"; } -.icon-coffee:before { +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-asc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-desc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { content: "\f0f4"; } -.icon-food:before { +.fa-cutlery:before { content: "\f0f5"; } -.icon-file-text-alt:before { +.fa-file-text-o:before { content: "\f0f6"; } -.icon-building:before { +.fa-building-o:before { content: "\f0f7"; } -.icon-hospital:before { +.fa-hospital-o:before { content: "\f0f8"; } -.icon-ambulance:before { +.fa-ambulance:before { content: "\f0f9"; } -.icon-medkit:before { +.fa-medkit:before { content: "\f0fa"; } -.icon-fighter-jet:before { +.fa-fighter-jet:before { content: "\f0fb"; } -.icon-beer:before { +.fa-beer:before { content: "\f0fc"; } -.icon-h-sign:before { +.fa-h-square:before { content: "\f0fd"; } -.icon-plus-sign-alt:before { +.fa-plus-square:before { content: "\f0fe"; } -.icon-double-angle-left:before { +.fa-angle-double-left:before { content: "\f100"; } -.icon-double-angle-right:before { +.fa-angle-double-right:before { content: "\f101"; } -.icon-double-angle-up:before { +.fa-angle-double-up:before { content: "\f102"; } -.icon-double-angle-down:before { +.fa-angle-double-down:before { content: "\f103"; } -.icon-angle-left:before { +.fa-angle-left:before { content: "\f104"; } -.icon-angle-right:before { +.fa-angle-right:before { content: "\f105"; } -.icon-angle-up:before { +.fa-angle-up:before { content: "\f106"; } -.icon-angle-down:before { +.fa-angle-down:before { content: "\f107"; } -.icon-desktop:before { +.fa-desktop:before { content: "\f108"; } -.icon-laptop:before { +.fa-laptop:before { content: "\f109"; } -.icon-tablet:before { +.fa-tablet:before { content: "\f10a"; } -.icon-mobile-phone:before { +.fa-mobile-phone:before, +.fa-mobile:before { content: "\f10b"; } -.icon-circle-blank:before { +.fa-circle-o:before { content: "\f10c"; } -.icon-quote-left:before { +.fa-quote-left:before { content: "\f10d"; } -.icon-quote-right:before { +.fa-quote-right:before { content: "\f10e"; } -.icon-spinner:before { +.fa-spinner:before { content: "\f110"; } -.icon-circle:before { +.fa-circle:before { content: "\f111"; } -.icon-mail-reply:before, -.icon-reply:before { +.fa-mail-reply:before, +.fa-reply:before { content: "\f112"; } -.icon-github-alt:before { +.fa-github-alt:before { content: "\f113"; } -.icon-folder-close-alt:before { +.fa-folder-o:before { content: "\f114"; } -.icon-folder-open-alt:before { +.fa-folder-open-o:before { content: "\f115"; } -.icon-expand-alt:before { - content: "\f116"; -} -.icon-collapse-alt:before { - content: "\f117"; -} -.icon-smile:before { +.fa-smile-o:before { content: "\f118"; } -.icon-frown:before { +.fa-frown-o:before { content: "\f119"; } -.icon-meh:before { +.fa-meh-o:before { content: "\f11a"; } -.icon-gamepad:before { +.fa-gamepad:before { content: "\f11b"; } -.icon-keyboard:before { +.fa-keyboard-o:before { content: "\f11c"; } -.icon-flag-alt:before { +.fa-flag-o:before { content: "\f11d"; } -.icon-flag-checkered:before { +.fa-flag-checkered:before { content: "\f11e"; } -.icon-terminal:before { +.fa-terminal:before { content: "\f120"; } -.icon-code:before { +.fa-code:before { content: "\f121"; } -.icon-reply-all:before { +.fa-reply-all:before { content: "\f122"; } -.icon-mail-reply-all:before { +.fa-mail-reply-all:before { content: "\f122"; } -.icon-star-half-full:before, -.icon-star-half-empty:before { +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { content: "\f123"; } -.icon-location-arrow:before { +.fa-location-arrow:before { content: "\f124"; } -.icon-crop:before { +.fa-crop:before { content: "\f125"; } -.icon-code-fork:before { +.fa-code-fork:before { content: "\f126"; } -.icon-unlink:before { +.fa-unlink:before, +.fa-chain-broken:before { content: "\f127"; } -.icon-question:before { +.fa-question:before { content: "\f128"; } -.icon-info:before { +.fa-info:before { content: "\f129"; } -.icon-exclamation:before { +.fa-exclamation:before { content: "\f12a"; } -.icon-superscript:before { +.fa-superscript:before { content: "\f12b"; } -.icon-subscript:before { +.fa-subscript:before { content: "\f12c"; } -.icon-eraser:before { +.fa-eraser:before { content: "\f12d"; } -.icon-puzzle-piece:before { +.fa-puzzle-piece:before { content: "\f12e"; } -.icon-microphone:before { +.fa-microphone:before { content: "\f130"; } -.icon-microphone-off:before { +.fa-microphone-slash:before { content: "\f131"; } -.icon-shield:before { +.fa-shield:before { content: "\f132"; } -.icon-calendar-empty:before { +.fa-calendar-o:before { content: "\f133"; } -.icon-fire-extinguisher:before { +.fa-fire-extinguisher:before { content: "\f134"; } -.icon-rocket:before { +.fa-rocket:before { content: "\f135"; } -.icon-maxcdn:before { +.fa-maxcdn:before { content: "\f136"; } -.icon-chevron-sign-left:before { +.fa-chevron-circle-left:before { content: "\f137"; } -.icon-chevron-sign-right:before { +.fa-chevron-circle-right:before { content: "\f138"; } -.icon-chevron-sign-up:before { +.fa-chevron-circle-up:before { content: "\f139"; } -.icon-chevron-sign-down:before { +.fa-chevron-circle-down:before { content: "\f13a"; } -.icon-html5:before { +.fa-html5:before { content: "\f13b"; } -.icon-css3:before { +.fa-css3:before { content: "\f13c"; } -.icon-anchor:before { +.fa-anchor:before { content: "\f13d"; } -.icon-unlock-alt:before { +.fa-unlock-alt:before { content: "\f13e"; } -.icon-bullseye:before { +.fa-bullseye:before { content: "\f140"; } -.icon-ellipsis-horizontal:before { +.fa-ellipsis-h:before { content: "\f141"; } -.icon-ellipsis-vertical:before { +.fa-ellipsis-v:before { content: "\f142"; } -.icon-rss-sign:before { +.fa-rss-square:before { content: "\f143"; } -.icon-play-sign:before { +.fa-play-circle:before { content: "\f144"; } -.icon-ticket:before { +.fa-ticket:before { content: "\f145"; } -.icon-minus-sign-alt:before { +.fa-minus-square:before { content: "\f146"; } -.icon-check-minus:before { +.fa-minus-square-o:before { content: "\f147"; } -.icon-level-up:before { +.fa-level-up:before { content: "\f148"; } -.icon-level-down:before { +.fa-level-down:before { content: "\f149"; } -.icon-check-sign:before { +.fa-check-square:before { content: "\f14a"; } -.icon-edit-sign:before { +.fa-pencil-square:before { content: "\f14b"; } -.icon-external-link-sign:before { +.fa-external-link-square:before { content: "\f14c"; } -.icon-share-sign:before { +.fa-share-square:before { content: "\f14d"; } -.icon-compass:before { +.fa-compass:before { content: "\f14e"; } -.icon-collapse:before { +.fa-toggle-down:before, +.fa-caret-square-o-down:before { content: "\f150"; } -.icon-collapse-top:before { +.fa-toggle-up:before, +.fa-caret-square-o-up:before { content: "\f151"; } -.icon-expand:before { +.fa-toggle-right:before, +.fa-caret-square-o-right:before { content: "\f152"; } -.icon-euro:before, -.icon-eur:before { +.fa-euro:before, +.fa-eur:before { content: "\f153"; } -.icon-gbp:before { +.fa-gbp:before { content: "\f154"; } -.icon-dollar:before, -.icon-usd:before { +.fa-dollar:before, +.fa-usd:before { content: "\f155"; } -.icon-rupee:before, -.icon-inr:before { +.fa-rupee:before, +.fa-inr:before { content: "\f156"; } -.icon-yen:before, -.icon-jpy:before { +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { content: "\f157"; } -.icon-renminbi:before, -.icon-cny:before { +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { content: "\f158"; } -.icon-won:before, -.icon-krw:before { +.fa-won:before, +.fa-krw:before { content: "\f159"; } -.icon-bitcoin:before, -.icon-btc:before { +.fa-bitcoin:before, +.fa-btc:before { content: "\f15a"; } -.icon-file:before { +.fa-file:before { content: "\f15b"; } -.icon-file-text:before { +.fa-file-text:before { content: "\f15c"; } -.icon-sort-by-alphabet:before { +.fa-sort-alpha-asc:before { content: "\f15d"; } -.icon-sort-by-alphabet-alt:before { +.fa-sort-alpha-desc:before { content: "\f15e"; } -.icon-sort-by-attributes:before { +.fa-sort-amount-asc:before { content: "\f160"; } -.icon-sort-by-attributes-alt:before { +.fa-sort-amount-desc:before { content: "\f161"; } -.icon-sort-by-order:before { +.fa-sort-numeric-asc:before { content: "\f162"; } -.icon-sort-by-order-alt:before { +.fa-sort-numeric-desc:before { content: "\f163"; } -.icon-thumbs-up:before { +.fa-thumbs-up:before { content: "\f164"; } -.icon-thumbs-down:before { +.fa-thumbs-down:before { content: "\f165"; } -.icon-youtube-sign:before { +.fa-youtube-square:before { content: "\f166"; } -.icon-youtube:before { +.fa-youtube:before { content: "\f167"; } -.icon-xing:before { +.fa-xing:before { content: "\f168"; } -.icon-xing-sign:before { +.fa-xing-square:before { content: "\f169"; } -.icon-youtube-play:before { +.fa-youtube-play:before { content: "\f16a"; } -.icon-dropbox:before { +.fa-dropbox:before { content: "\f16b"; } -.icon-stackexchange:before { +.fa-stack-overflow:before { content: "\f16c"; } -.icon-instagram:before { +.fa-instagram:before { content: "\f16d"; } -.icon-flickr:before { +.fa-flickr:before { content: "\f16e"; } -.icon-adn:before { +.fa-adn:before { content: "\f170"; } -.icon-bitbucket:before { +.fa-bitbucket:before { content: "\f171"; } -.icon-bitbucket-sign:before { +.fa-bitbucket-square:before { content: "\f172"; } -.icon-tumblr:before { +.fa-tumblr:before { content: "\f173"; } -.icon-tumblr-sign:before { +.fa-tumblr-square:before { content: "\f174"; } -.icon-long-arrow-down:before { +.fa-long-arrow-down:before { content: "\f175"; } -.icon-long-arrow-up:before { +.fa-long-arrow-up:before { content: "\f176"; } -.icon-long-arrow-left:before { +.fa-long-arrow-left:before { content: "\f177"; } -.icon-long-arrow-right:before { +.fa-long-arrow-right:before { content: "\f178"; } -.icon-apple:before { +.fa-apple:before { content: "\f179"; } -.icon-windows:before { +.fa-windows:before { content: "\f17a"; } -.icon-android:before { +.fa-android:before { content: "\f17b"; } -.icon-linux:before { +.fa-linux:before { content: "\f17c"; } -.icon-dribbble:before { +.fa-dribbble:before { content: "\f17d"; } -.icon-skype:before { +.fa-skype:before { content: "\f17e"; } -.icon-foursquare:before { +.fa-foursquare:before { content: "\f180"; } -.icon-trello:before { +.fa-trello:before { content: "\f181"; } -.icon-female:before { +.fa-female:before { content: "\f182"; } -.icon-male:before { +.fa-male:before { content: "\f183"; } -.icon-gittip:before { +.fa-gittip:before { content: "\f184"; } -.icon-sun:before { +.fa-sun-o:before { content: "\f185"; } -.icon-moon:before { +.fa-moon-o:before { content: "\f186"; } -.icon-archive:before { +.fa-archive:before { content: "\f187"; } -.icon-bug:before { +.fa-bug:before { content: "\f188"; } -.icon-vk:before { +.fa-vk:before { content: "\f189"; } -.icon-weibo:before { +.fa-weibo:before { content: "\f18a"; } -.icon-renren:before { +.fa-renren:before { content: "\f18b"; } +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} diff --git a/addons/web/static/lib/fontawesome/font/FontAwesome.otf b/addons/web/static/lib/fontawesome/font/FontAwesome.otf deleted file mode 100644 index 70125459f7d593b79cabc75bd60b91943aa65e93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61896 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 diff --git a/addons/web/static/lib/fontawesome/font/fontawesome-webfont.eot b/addons/web/static/lib/fontawesome/font/fontawesome-webfont.eot deleted file mode 100755 index 0662cb96bfb78cb2603df4bc9995314bd6806312..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37405 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�-A&iY%khEsGVMaq&$BJA^QAs8x>7-g_ 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 k3oQ9m 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 diff --git a/addons/web/static/lib/fontawesome/fonts/FontAwesome.otf b/addons/web/static/lib/fontawesome/fonts/FontAwesome.otf new file mode 100644 index 0000000000000000000000000000000000000000..8b0f54e47e1d356dcf1496942a50e228e0f1ee14 GIT binary patch literal 62856 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