[ADD] planner, planner_crm : add the planner engine, and the planner for CRM module. A planner is a tool helping people to set up quickly. For CRM, it helps to define sales strategy, objectives, leads, KPIs and much more !

This commit is contained in:
dka-odoo
2015-01-09 16:40:38 +01:00
committed by Jérome Maes
parent 96ca872ba3
commit 4009a74764
49 changed files with 11493 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Management Solution
# Copyright (C) 2014-Today Odoo SA (<http://www.odoo.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import models
+18
View File
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
{
'name': 'Planner',
'category': 'Planner',
'summary': 'Help to configure application',
'version': '1.0',
'description': """Application Planner""",
'author': 'Odoo SA',
'depends': ['web'],
'data': [
'security/ir.model.access.csv',
'views/planner.xml',
],
'qweb': ['static/src/xml/planner.xml'],
'installable': True,
'auto_install': True,
}
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import planner
+52
View File
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from openerp import api, models, fields
class Planner(models.Model):
"""Planner Model.
Each Planner has link to an ir.ui.view record that is a template used
to display the planner pages.
Each Planner has link to ir.ui.menu record that is a top menu used to display the
planner launcher(progressbar)
Method _prepare_<planner_application>_data(self, cr, uid, context) that
generate the values used to display in specific planner pages
"""
_name = 'planner.planner'
_description = 'Planner'
@api.model
def _get_planner_application(self):
return []
name = fields.Char(string='Name', required=True)
menu_id = fields.Many2one('ir.ui.menu', string='Menu', required=True)
view_id = fields.Many2one('ir.ui.view', string='Template', required=True)
progress = fields.Integer(string="Progress Percentage")
# used to store the data filled by user in planner(JSON Data)
data = fields.Text(string='Data')
tooltip_planner = fields.Html(string='Planner Tooltips', translate=True)
planner_application = fields.Selection('_get_planner_application', string='Planner Application', required=True)
@api.model
def render(self, template_id, planner_app):
# prepare the planner data as per the planner application
values = {
'prepare_backend_url': self.prepare_backend_url,
}
planner_find_method_name = '_prepare_%s_data' % planner_app
if hasattr(self, planner_find_method_name):
values.update(getattr(self, planner_find_method_name)()) # update the default value
return self.env['ir.ui.view'].browse(template_id).render(values=values)
@api.model
def prepare_backend_url(self, action_xml_id, view_type='list', module_name=None):
""" prepare the backend url to the given action, or to the given module view. """
action = self.env.ref(action_xml_id, False)
url = "/web#view_type=%s&action=%s" % (view_type, action and action.id or '')
if module_name:
module = self.env['ir.module.module'].search([('name', '=', module_name)], limit=1)
if module:
url = "&id=%s" % (module.id,)
return url
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_planner_emp,planner.planner.emp,model_planner_planner,base.group_user,1,1,0,0
access_planner_manager,planner.planner.manager,model_planner_planner,base.group_erp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_planner_emp planner.planner.emp model_planner_planner base.group_user 1 1 0 0
3 access_planner_manager planner.planner.manager model_planner_planner base.group_erp_manager 1 1 1 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 154 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 155 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 156 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 147 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 178 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

+306
View File
@@ -0,0 +1,306 @@
(function() {
"use strict";
var instance = openerp;
var QWeb = instance.web.qweb;
instance.planner = {};
instance.web.WebClient.include({
start: function() {
this.planner_manager = new instance.planner.PlannerManager(this);
return this._super.apply(this, arguments);
},
show_application: function() {
var self = this;
this._super.apply(this, arguments);
self.menu.on("top_menu_change", self.planner_manager, self.planner_manager.on_menu_clicked);
}
});
instance.web.Menu.include({
open_menu: function (id) {
this._super.apply(this, arguments);
// find the active menu with class '.active', instead of with click event to display planner in case of refresh
var active_top_menu = this.$el.find('.active a').data('menu');
if((active_top_menu != this.active_top_menu)){
this.active_top_menu = active_top_menu;
this.trigger('top_menu_change', active_top_menu);
this.reflow(); // re-arrange the top menu (hide overflow in 'more' menu)
}
},
});
instance.planner.PlannerManager = instance.web.Widget.extend({
init: function(parent) {
var self = this;
this._super(parent);
this.planner_by_menu = {};
this.fetch_application_planner().done(function(apps) {
self.planner_apps = apps;
});
},
on_menu_clicked: function(menu_id) {
var self = this;
self.planner_launcher && self.planner_launcher.destroy();
if (_.contains(_.keys(self.planner_apps), menu_id.toString())) {
self.planner_launcher = new instance.planner.PlannerLauncher(self, self.planner_apps[menu_id]);
self.planner_launcher.prependTo(window.$('.oe_systray'));
}
},
fetch_application_planner: function() {
var self = this;
var def = $.Deferred();
if (!_.isEmpty(this.planner_by_menu)) {
def.resolve(self.planner_by_menu);
}else{
(new instance.web.Model('planner.planner')).query().all().then(function(res) {
_.each(res, function(planner){
self.planner_by_menu[planner.menu_id[0]] = planner;
self.planner_by_menu[planner.menu_id[0]].data = jQuery.parseJSON(self.planner_by_menu[planner.menu_id[0]].data) || {};
});
def.resolve(self.planner_by_menu);
}).fail(function() {def.reject();});
}
return def;
}
});
instance.planner.PlannerLauncher = instance.web.Widget.extend({
template: "PlannerLauncher",
events: {
'click .oe_planner_progress': 'toggle_dialog'
},
init: function(parent, planner) {
this._super(parent);
this.planner = planner;
this.dialog = new instance.planner.PlannerDialog(this, planner);
},
start: function() {
var self = this;
this._super.apply(this, arguments);
this.$(".oe_planner_progress").tooltip({html: true, title: this.planner.tooltip_planner, placement: 'bottom', delay: {'show': 500}});
this.dialog.on("planner_progress_changed", this, function(percent){
self.update_parent_progress_bar(percent);
});
this.dialog.appendTo(document.body);
},
update_parent_progress_bar: function(percent) {
this.$(".progress-bar").css('width', percent+"%");
},
toggle_dialog: function() {
this.dialog.$('#PlannerModal').modal('toggle');
}
});
/*
Widget implementing the Modal of the planner (with all form, menu items, mark buttons, pages, ...). The content (the view_id) is fetched by
calling the 'render' method (server side), then footer are appended to every pages (client side). Data are saved when a 'mark button' is clicked.
Some element of the template MUST respect naming convention :
* Input elements (select, textarea, radio, ...) MUST HAVE an 'id' as 'input_element_#####', where ##### is a string.
* Page div MUST HAVE the class "panel-collapse collapse" and an 'id' as 'planner_page#', where # is ideally a
number. !!! The order of the page are important. The 'id' number is NOT the sequence order.
* Menu item MUST HAVE an href attribute containing the id of the page they are referencing.
*/
instance.planner.PlannerDialog = instance.web.Widget.extend({
template: "PlannerDialog",
events: {
'click .oe_planner div[id^="planner_page"] a[href^="#planner_page"]': 'next_page',
'click .oe_planner li a[href^="#planner_page"]': 'onclick_menu',
'click .oe_planner div[id^="planner_page"] button[data-pageid^="planner_page"]': 'mark_as_done',
},
init: function(parent, planner) {
this._super(parent);
this.planner_launcher = parent;
this.planner = planner;
this.cookie_name = this.planner['planner_application'] + '_last_page';
this.set('progress', 0);
},
start: function() {
var self = this;
this._super.apply(this, arguments);
this._setup_view().then(function(){
self.prepare_planner_event();
});
},
// This method should be overridden in other planners to bind their custom events, once the view is loaded.
prepare_planner_event: function() {
var self = this;
self.on('change:progress', self, function(){
self.trigger('planner_progress_changed', self.get('progress'));
});
this.on('planner_progress_changed', this, this.update_ui_progress_bar);
this.set('progress', this.planner.progress); // init progress to trigger ui update
$(window).on('resize', function() {
self.resize_dialog();
});
},
// ui
_setup_view: function(){
var self = this;
return (new instance.web.Model('planner.planner')).call('render', [self.planner.view_id[0], self.planner.planner_application]).then(function(res) {
self.$('.content_page').html(res);
// add footer to each page
self.add_pages_footer();
// update the planner_data with the new inputs of the view
var actual_vals = self._get_values();
self.planner.data = _.defaults(self.planner.data, actual_vals);
// set the default value
self._set_values(self.planner.data);
// show last opened page
var last_open_page = (openerp.session.get_cookie(self.cookie_name)) ? openerp.session.get_cookie(self.cookie_name) : self.planner.data['last_open_page'] || false;
if (last_open_page) {
$(".oe_planner li a[href='#"+last_open_page+"']").trigger('click');
}
// Call resize function at the beginning
self.resize_dialog();
self.$el.on('keyup', "textarea", function() {
if (this.scrollHeight != this.clientHeight) {
this.style.height = this.scrollHeight + "px";
}
});
});
},
update_ui_progress_bar: function(percent) {
this.$(".progress-bar").css('width', percent+"%");
this.$(".progress_col").find('span.counter').text(percent+"%");
},
add_pages_footer: function() {
var self = this;
//find all the pages and append footer to each pages
_.each(self.$('.oe_planner div[id^="planner_page"]'), function(element) {
var $el = $(element);
var next_page_name = self.$(".oe_planner .side li a[href='#"+$el.next().attr('id')+"']").text() || ' Finished!';
var footer_template = QWeb.render("PlannerFooter", {
'next_page_name': next_page_name,
'next_page_id': $el.next().attr('id'),
'current_page_id': $el.attr('id'),
'start': $el.prev().length ? false: true,
'end': $el.next().length ? false: true
});
$el.append(footer_template);
});
},
resize_dialog: function() {
var winH = $(window).height();
var $modal = this.$('.planner-dialog');
$modal.height(winH/1.1);
this.$('.pages').height($modal.height() - 60);
this.$('.side').height($modal.height() - 75);
},
// page switching
onclick_menu: function(ev) {
ev.stopPropagation();
var page_id = $(ev.currentTarget).attr('href').replace('#', '');
this._switch_page(page_id);
},
next_page: function(ev) {
ev.stopPropagation();
var next_page_id = $(ev.currentTarget).attr('href').replace('#', '');
this._switch_page(next_page_id);
},
_switch_page: function(page_id) {
this.$(".oe_planner li a[href^='#planner_page']").parent().removeClass('active');
this.$(".oe_planner li a[href=#"+page_id+"]").parent().addClass('active');
this.$(".oe_planner div[id^='planner_page']").removeClass('in');
this.$(".oe_planner div[id="+page_id+"]").addClass('in');
this.planner.data['last_open_page'] = page_id;
openerp.session.set_cookie(this.cookie_name, page_id, 8*60*60); // create cookie for 8h
},
// planner data functions
_get_values: function(page_id){
// if no page_id, take the complete planner
var base_elem = page_id ? this.$el : this.$(".oe_planner div[id='planner_page"+page_id+"']");
var values = {};
// get the selector for all the input and mark_button
// only INPUT (select, textearea, input, checkbox and radio), and BUTTON (.mark_button#) are observed
var inputs = base_elem.find("textarea[id^='input_element'], input[id^='input_element'], select[id^='input_element'], button[id^='mark_button']");
_.each(inputs, function(elem){
elem = $(elem);
var tid = elem.attr('id');
if (elem.prop("tagName") == 'BUTTON'){
if(elem.hasClass('fa-check-square-o')){
values[tid] = 'marked';
}else{
values[tid] = '';
}
}
if (elem.prop("tagName") == 'INPUT' || elem.prop("tagName") == 'TEXTAREA'){
var ttype = elem.attr('type');
if (ttype == 'checkbox' || ttype == 'radio'){
values[tid] = '';
if (elem.is(':checked')){
values[tid] = 'checked';
}
}else{
values[tid] = elem.val();
}
}
});
return values;
},
_set_values: function(values){
var self = this;
_.each(values, function(val, id){
var elem = self.$('#'+id);
if (elem.prop("tagName") == 'BUTTON'){
if(val == 'marked'){
elem.addClass('fa-check-square-o btn-default').removeClass('fa-square-o btn-primary');
self.$(".oe_planner li a[href=#"+elem.data('pageid')+"] span").addClass('fa-check');
}
}
if (elem.prop("tagName") == 'INPUT' || elem.prop("tagName") == 'TEXTAREA'){
var ttype = elem.attr("type");
if (ttype == 'checkbox' || ttype == 'radio'){
if (val == 'checked') {
elem.attr('checked', 'checked');
}
}else{
elem.val(val);
}
}
});
},
update_planner: function(page_id){
// update the planner.data with the inputs
var vals = this._get_values(page_id);
this.planner.data = _.extend(this.planner.data, vals);
// re compute the progress percentage
var mark_btn = this.$(".oe_planner button[id^='mark_button']");
var marked_btn = this.$(".oe_planner button[id^='mark_button'].fa-check-square-o");
var percent = parseInt((marked_btn.length / mark_btn.length * 100));
this.set('progress', percent);
this.planner.progress = percent;
// save data and progress in database
this._save_planner_data();
},
_save_planner_data: function() {
return (new instance.web.Model('planner.planner')).call('write', [this.planner.id, {'data': JSON.stringify(this.planner.data), 'progress': this.planner.progress}]);
},
// user actions
mark_as_done: function(ev) {
var self = this;
var btn = $(ev.currentTarget);
var page_id = btn.attr('data-pageid');
var active_menu = self.$(".oe_planner li a[href=#"+page_id+"] span");
var active_page = self.$(".oe_planner div[id^='planner_page'].panel-collapse.collapse.in");
var next_button = self.$(".oe_planner a[data-parent="+page_id+"]");
if (!btn.hasClass('fa-check-square-o')) {
active_menu.addClass('fa-check');
btn.addClass('fa-check-square-o btn-default').removeClass('fa-square-o btn-primary');
next_button.addClass('btn-primary').removeClass('btn-default');
// page checked animation
active_page.addClass('marked');
setTimeout(function() { active_page.removeClass('marked'); }, 1000);
} else {
btn.removeClass('fa-check-square-o btn-default').addClass('fa-square-o btn-primary');
next_button.addClass('btn-default').removeClass('btn-primary');
active_menu.removeClass('fa-check');
}
self.update_planner(page_id);
},
});
})();
+590
View File
@@ -0,0 +1,590 @@
// FONTS
@font-face {
font-family: 'LatoLatin';
src: url('/planner/static/src/fonts/LatoLatin/lato-thin-webfont.eot');
src: url('/planner/static/src/fonts/LatoLatin/lato-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('/planner/static/src/fonts/LatoLatin/lato-thin-webfont.woff2') format('woff2'),
url('/planner/static/src/fonts/LatoLatin/lato-thin-webfont.woff') format('woff'),
url('/planner/static/src/fonts/LatoLatin/lato-thin-webfont.ttf') format('truetype'),
url('/planner/static/src/fonts/LatoLatin/lato-thin-webfont.svg#latolight') format('svg');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'LatoLatin';
src: url('/planner/static/src/fonts/LatoLatin/lato-light-webfont.eot');
src: url('/planner/static/src/fonts/LatoLatin/lato-light-webfont.eot?#iefix') format('embedded-opentype'),
url('/planner/static/src/fonts/LatoLatin/lato-light-webfont.woff2') format('woff2'),
url('/planner/static/src/fonts/LatoLatin/lato-light-webfont.woff') format('woff'),
url('/planner/static/src/fonts/LatoLatin/lato-light-webfont.ttf') format('truetype'),
url('/planner/static/src/fonts/LatoLatin/lato-light-webfont.svg#latolight') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'LatoLatin';
src: url('/planner/static/src/fonts/LatoLatin/lato-semibold-webfont.eot');
src: url('/planner/static/src/fonts/LatoLatin/lato-semibold-webfont.eot?#iefix') format('embedded-opentype'),
url('/planner/static/src/fonts/LatoLatin/lato-semibold-webfont.woff2') format('woff2'),
url('/planner/static/src/fonts/LatoLatin/lato-semibold-webfont.woff') format('woff'),
url('/planner/static/src/fonts/LatoLatin/lato-semibold-webfont.ttf') format('truetype'),
url('/planner/static/src/fonts/LatoLatin/lato-semibold-webfont.svg#latosemibold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'LatoLatin';
src: url('/planner/static/src/fonts/LatoLatin/lato-regular-webfont.eot');
src: url('/planner/static/src/fonts/LatoLatin/lato-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('/planner/static/src/fonts/LatoLatin/lato-regular-webfont.woff2') format('woff2'),
url('/planner/static/src/fonts/LatoLatin/lato-regular-webfont.woff') format('woff'),
url('/planner/static/src/fonts/LatoLatin/lato-regular-webfont.ttf') format('truetype'),
url('/planner/static/src/fonts/LatoLatin/lato-regular-webfont.svg#latoregular') format('svg');
font-weight: medium;
font-style: normal;
}
@font-face {
font-family: 'Simple-Line-Icons';
src:url('/planner/static/src/fonts/Simple-Line-Icons.eot');
src:url('/planner/static/src/fonts/Simple-Line-Icons.eot?#iefix') format('embedded-opentype'),
url('/planner/static/src/fonts/Simple-Line-Icons.woff') format('woff'),
url('/planner/static/src/fonts/Simple-Line-Icons.ttf') format('truetype'),
url('/planner/static/src/fonts/Simple-Line-Icons.svg#Simple-Line-Icons') format('svg');
font-weight: normal;
font-style: normal;
}
// use data attributes for inserting icons
// http://www.issme.net/india/assets/plugins/line-icons/
[data-icon]:before {
font-family: 'Simple-Line-Icons';
content: attr(data-icon);
speak: none;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
//Animations
@keyframes markAnim {
0% {
opacity: 0;
transform: scaleX(0.50) scaleY(0.50);
}
30% {
opacity: 1;
transform: scaleX(1.00) scaleY(1.00);
}
100% {
opacity: 0;
transform: scaleX(1.00) scaleY(1.00);
}
}
@-moz-keyframes markAnim {
0% {
opacity: 0;
-moz-transform: scaleX(0.50) scaleY(0.50);
}
30% {
opacity: 1;
-moz-transform: scaleX(1.00) scaleY(1.00);
}
100% {
opacity: 0;
-moz-transform: scaleX(1.00) scaleY(1.00);
}
}
@-webkit-keyframes markAnim {
0% {
opacity: 0;
-webkit-transform: scaleX(0.50) scaleY(0.50);
}
30% {
opacity: 1;
-webkit-transform: scaleX(1.00) scaleY(1.00);
}
100% {
opacity: 0;
-webkit-transform: scaleX(1.00) scaleY(1.00);
}
}
@-o-keyframes markAnim {
0% {
opacity: 0;
-o-transform: scaleX(0.50) scaleY(0.50);
}
30% {
opacity: 1;
-o-transform: scaleX(1.00) scaleY(1.00);
}
100% {
opacity: 0;
-o-transform: scaleX(1.00) scaleY(1.00);
}
}
@-ms-keyframes markAnim {
0% {
opacity: 0;
-ms-transform: scaleX(0.50) scaleY(0.50);
}
30% {
opacity: 1;
-ms-transform: scaleX(1.00) scaleY(1.00);
}
100% {
opacity: 0;
-ms-transform: scaleX(1.00) scaleY(1.00);
}
}
//bounce effect
@-webkit-keyframes bounceIn {
0%, 20%, 40%, 60%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
0% {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
-webkit-transform: scale3d(.9, .9, .9);
transform: scale3d(.9, .9, .9);
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
-webkit-transform: scale3d(.97, .97, .97);
transform: scale3d(.97, .97, .97);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes bounceIn {
0%, 20%, 40%, 60%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
0% {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
-webkit-transform: scale3d(.9, .9, .9);
transform: scale3d(.9, .9, .9);
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
-webkit-transform: scale3d(.97, .97, .97);
transform: scale3d(.97, .97, .97);
}
100% {
opacity: 1;
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
#oe_main_menu_navbar.navbar {
.oe_planner_systray {
width: 100px;
height: 13px;
margin: 10px 20px 0 0;
div.oe_planner_progress {
cursor: pointer;
}
.oe_planner_progress {
div.progress {
height: 13px;
}
}
}
}
#PlannerModal {
.planner-dialog {
width: 85%;
font-family: "LatoLatin", serif;
max-width: 1024px;
overflow: hidden;
margin-left: auto;
margin-right: auto;
//button close
button.close {
width: 13px;
height: 30px;
opacity: 1;
line-height: 30px;
font-size: 16px;
color: rgba(51, 51, 51, 0.54);
position: absolute;
right: 15px;
z-index: 9999;
}
button.close:hover {color:#222;}
.modal-content {
border-radius: 2px;
background: #f3f3f3;
}
.modal-body {
padding: 0;
overflow: hidden;
max-height: inherit;
}
.row.oe_planner {padding: 0 15px;}
.mb1{
margin-bottom: 1em!important;
}
.mb2{
margin-bottom: 2em!important;
}
.mb3{
margin-bottom: 3em!important;
}
.mb5{
margin-bottom: 5em!important;
}
.odoo_purple, a.btn.odoo_purple {
background-color: #A24689;
border:none;
text-shadow:none;
color: white;
}
.odoo_purple.btn:hover{
background: #a24689;
color: #fff;
}
.planner_footer {
padding: 0;
height: auto;
overflow-y: visible;
overflow-x: initial;
display: block;
background: transparent;
position: fixed;
z-index: 99;
right: 0;
bottom: 0;
.shadow {
background: url("/planner/static/src/img/shadow.png") center center repeat-x;
height: 12px;
width: 100%;
display: block;
border-bottom: 1px solid #E5E5E5;
opacity: 0.3;
}
> div {
padding: 1em 15px;
background: #f7f7f7;
min-height: 60px;
}
button.fa-check-square-o {
background-color: transparent;
line-height: 0.8em;
font-size: 2em;
border: none;
&:focus {outline:none}
&:hover:before{
content: "\f096";
color: #888;
position: absolute;
margin-left: 2px;
}
&:hover i:before{
content: "\f00c";
color: #CACACA;
}
span {display: none;}
}
button.fa-check-square-o:after {
color: #7C7C7C;
content: "Unckeck";
position: absolute;
left: 5.5em;
font-size: .5em;
opacity: 0;
-webkit-transition: all 0.3s ease-in-out;
-moz-transition: all 0.3s ease-in-out;
-ms-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
}
button.fa-check-square-o:hover:after {
left: 5em;
opacity: 1;
-webkit-transition: all 0.3s ease-in-out;
-moz-transition: all 0.3s ease-in-out;
-ms-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
}
.btn-next i {
transform: translateY(1px);
position: relative;
display: inline-block;
font-size: 1.3em;
margin-left: 10px;
}
.span-next {
position: absolute;
right: 9em;
top: 0.8em;
opacity: 0;
line-height: 2.2em;
height: 1.3em;
font-size: 1em;
display: block;
-webkit-transition: all 0.3s ease-in-out;
-moz-transition: all 0.3s ease-in-out;
-ms-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
}
.next-block:hover .span-next {
right: 9.6em;
opacity: 1;
}
.mark_as_done span {
font-family: "LatoLatin", serif;
}
}
// MARKED ANIMATION
.panel-collapse.marked:after {
position: absolute;
left: 0;
top: 0;
font-size: 20em;
font-family: "FontAwesome";
content: "\f00c";
width: 100%;
height: 100%;
z-index: 999;
color: #21b799;
vertical-align: middle;
padding: 0.3em 0.7em;
animation: markAnim ease-in-out 1s;
animation-iteration-count: 1;
transform-origin: 50% 50%;
animation-fill-mode:forwards; /*when the spec is finished*/
-webkit-animation: markAnim ease-in-out 1s;
-webkit-animation-iteration-count: 1;
-webkit-transform-origin: 50% 50%;
-webkit-animation-fill-mode:forwards; /*Chrome 16+, Safari 4+*/
-moz-animation: markAnim ease-in-out 1s;
-moz-animation-iteration-count: 1;
-moz-transform-origin: 50% 50%;
-moz-animation-fill-mode:forwards; /*FF 5+*/
-o-animation: markAnim ease-in-out 1s;
-o-animation-iteration-count: 1;
-o-transform-origin: 50% 50%;
-o-animation-fill-mode:forwards; /*Not implemented yet*/
-ms-animation: markAnim ease-in-out 1s;
-ms-animation-iteration-count: 1;
-ms-transform-origin: 50% 50%;
-ms-animation-fill-mode:forwards; /*IE 10+*/
}
// PROGRESS
.progress_container {
padding: 0;
.progress_col {
border-right: 1px solid #E5E5E5;
background: #f7f7f7;
padding: 10px 12px 0 12px;
i.fa{
font-size: 1.7em;
padding-left: 0;
margin-right: 0.2em;
margin-top: 0.1em;
color: #929292;
}
.counter {
padding: 6px 0 0 8px;
font-weight: bold;
color: #727272;
font-size: 13px;
}
.progress {
background-color: #C4C4C4;
height: 30px;
padding: 3px;
border-radius: 4px;
}
.progress-bar {
background: #A24689;
background: -moz-linear-gradient(top, #cc59ab 0%, #a24689 8%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cc59ab), color-stop(8%,#a24689));
background: -webkit-linear-gradient(top, #cc59ab 0%,#a24689 8%);
background: -o-linear-gradient(top, #cc59ab 0%,#a24689 8%);
background: -ms-linear-gradient(top, #cc59ab 0%,#a24689 8%);
background: linear-gradient(to bottom, #cc59ab 0%,#a24689 8%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc59ab', endColorstr='#a24689',GradientType=0 );
border-radius: 4px;
font-weight: bold;
text-shadow: 0 1px 0 #4E2443;
line-height: 25px;
height: 95%;
box-shadow: 0 1px 0 #7A3067;
}
}
}
.dropdown.mobile {
padding: 10px 12px 0 0px;
.dropdown-menu{
width: 100%;
height: 250px;
overflow: auto;
}
.btn {
height: 30px;
line-height: 15px;
text-align: justify;
}
.dropdown-menu a {
color: #333;
padding: 3px;
}
.dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus {
background-color: #F3F3F3;
}
.dropdown-menu h4 {
padding: 10px 15px 0;
border-top: 1px solid #EAEAEA;
}
.fa {
color: #21b799;
margin-right: 5px;
}
}
.side {
padding: 5px 5px 20px 10px;
border-right: 1px solid #E5E5E5;
background: #f7f7f7;
overflow-x: hidden;
overflow-y: auto;
.nav-stacked h4 {
font-size: 15px;
color: #444;
text-transform: uppercase;
font-weight: bold;
margin-top: 20px;
}
.nav-stacked h4 i {
margin-right: 8px;
color: #A24689;
}
.nav-stacked li a {
padding: 3px 0 3px 38px;
margin: auto -10px;
color: #444;
font-size: 13px;
border-radius: 0;
}
.nav-stacked li a:hover, .nav-stacked li a:focus {
text-decoration: none;
background-color: transparent;
color: black;
}
.nav-stacked li.active a{
background-color: #EAEAEA;
}
.fa.fa-fw.fa-check {
color: #21b799;
margin-left: -5px;
margin-right: 5px;
}
}
}
.pages {
color: #444;
margin-top:-60px;
overflow-x: hidden;
overflow-y: auto;
> div {
padding-top: 25px;
position: relative;
height: 100%;
> h1, > h2, > h3, > h4, > h5, > h6, > p, > strong, > i {
text-shadow:none;
}
h1 {
margin-top: 0;
font-size: 3.5em;
font-weight: 100;
margin-bottom: 0.5em;
}
}
}
&.odoo_colors {
a {color: #2E917D}
a:hover, a:focus {color: #21b799; text-decoration: none;}
textarea:focus, input:focus, select:focus, option:focus, .btn:focus, a:focus {outline-color: #26A38A;}
.btn-default {
background-color: transparent;
color: #7C7C7C;
line-height: 19px;
}
.btn-default:hover {
color: #333;
}
.btn-primary {
background: #21b799;
border-color: #26A38A;
text-shadow: 0 1px 0 #2E917D;
font-weight: normal;
line-height: 19px;
color: white;
}
.btn-primary:hover {
background-color: #2E917D;
color: white;
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template">
<t t-name="PlannerLauncher">
<li class="oe_topbar_item oe_planner_systray">
<div class="oe_planner_progress">
<span class="fa fa-question-circle pull-left" style="color:white;"/>
<div class="progress">
<div class="progress-bar" t-attf-style="width:0%;">
</div>
</div>
</div>
</li>
</t>
<t t-name="PlannerFooter">
<div class="planner_footer col-sm-8 col-md-9 col-xs-12 hidden-print">
<span class="shadow" />
<div class="col-xs-6 text-left mark-block">
<t t-if="!start and !end">
<button t-attf-id='mark_button#{current_page_id}'
t-attf-data-pageid='#{current_page_id}'
class='fa fa-square-o btn btn-primary mark_as_done'><i /> <span>Mark As Done</span>
</button>
</t>
</div>
<div class="col-xs-6 next-block">
<t t-if="!end">
<a data-toggle='collapse'
t-attf-class="pull-right btn btn-#{start and 'primary' or 'default'} btn-next"
t-attf-data-parent='#{current_page_id}'
t-attf-href='##{next_page_id}'>Next step<i class="fa fa-angle-right"></i>
</a>
<span class="help-block span-next"> <t t-esc="next_page_name"/></span>
</t>
</div>
</div>
</t>
<t t-name="PlannerDialog">
<div>
<div class="modal fade odoo_colors" id="PlannerModal" tabindex="-1" role="dialog" aria-labelledby="PlannerModalLabel" aria-hidden="true">
<div class="modal-dialog planner-dialog">
<div class="modal-content">
<div class="modal-body">
<button type="button" class="close" data-dismiss="modal"><i class="fa fa-times"></i></button>
<div>
<div class="col-sm-12 col-xs-7 progress_container">
<div class="col-sm-4 col-md-3 col-xs-12 progress_col">
<i class="fa fa-question-circle col-xs-1"></i>
<div class="progress col-xs-9">
<div class="progress-bar" t-attf-style="width:0%;"></div>
</div>
<span class="counter col-xs-1"></span>
</div>
</div>
<div class="content_page"/>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</templates>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="planner assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/planner/static/src/less/planner.less"/>
<script type="text/javascript" src="/planner/static/src/js/planner.js"></script>
</xpath>
</template>
</odoo>
+22
View File
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Management Solution
# Copyright (C) 2014-Today Odoo SA (<http://www.odoo.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import models
+16
View File
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
{
'name': 'CRM Planner',
'summary': 'Help to configure application',
'version': '1.0',
'category': 'Planner',
'description': """Plan your sales strategy: objectives, leads, KPIs, and much more!""",
'author': 'Odoo SA',
'depends': ['planner', 'crm'],
'data': [
'data/planner_data.xml',
'views/planner_crm.xml'
],
'installable': True,
'auto_install': True,
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import planner_crm
+25
View File
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from openerp import api, models
class PlannerCrm(models.Model):
_inherit = 'planner.planner'
@api.model
def _get_planner_application(self):
planner = super(PlannerCrm, self)._get_planner_application()
planner.append(['planner_crm', 'CRM Planner'])
return planner
@api.model
def _prepare_planner_crm_data(self):
sales_team = self.env.ref('sales_team.team_sales_department')
company_data = self.env['res.users'].browse(self._uid).company_id
values = {
'prepare_backend_url': self.prepare_backend_url,
'alias_domain': sales_team.alias_domain,
'alias_name': sales_team.alias_name,
'company_data': company_data,
}
return values
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,45 @@
(function(){
"use strict";
openerp.planner.PlannerDialog.include({
prepare_planner_event: function() {
var self = this;
this._super.apply(this, arguments);
if(self.planner['planner_application'] == 'planner_crm') {
var stages = {
'solution_selling': [
'Territory', 'Qualified', 'Qualified Sponsor',
'Proposal', 'Negotiation', 'Won', '',
'New propspect assigned to the right salesperson',
'Set fields: Expected Revenue, Expected Closing Date, Next Action',
'You are in discussion with the decision maker and HE agreed on his pain points',
'Quotation sent to customer', 'The customer came back to you to discuss your quotation',
'Quotation signed by the customer', ''],
'b2c': [
'New', 'Initial Contact', 'Product Demonstration', 'Proposal', 'Won', '', '',
'', 'Phone call with following questions: ...',
'Meeting with a demo. Set Fields: expected revenue, closing date',
'Quotation sent', '', '', ''],
'b2b': [
'New', 'Qualified', 'Needs assessment', 'POC Sold', 'Demonstration', 'Proposal', 'Won',
'Set fields: Expected Revenue, Expected Closing Date, Next Action',
'Close opportunity if: "pre-sales days * $500" < "expected revenue" * probability',
'GAP analysis with customer', 'Create a Proof of Concept with consultants',
'POC demonstration to the customer', 'Final Proposal sent', ''],
'odoo_default': [
'New', 'Qualified', 'Proposition', 'Negotiation', 'Won', 'Lost', '',
'', '', '', '', '', '', '']
}
self.$el.on('change', '#input_element_pipeline', function(ev) {
var option = $(ev.target).find(":selected").val();
if (_.has(stages, option)) {
var values = stages[option];
for(var i=0; i<values.length; i++) {
$('#input_element_stage_'+i).val(values[i]);
}
}
});
}
}
});
})();
@@ -0,0 +1,320 @@
#PlannerModal {
font-family: "LatoLatin", serif;
.flip {
-moz-transform: scaleX(-1);
-o-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: FlipH;
-ms-filter: "FlipH";
}
.relative {
position: relative;
}
// PARAGRAPHS
.paragraphs .icon {
text-align: center;
font-size: 2em;
margin: 1em auto .5em;
color: #A24689;
}
//NOTEBOOK
.notebook-container {
margin: 20px 0;
}
.notebook {
color: #555;
font-size: 22px;
padding: 0 !important;
width: 100%;
font-family: courier, monospace;
border: 1px solid #faebcc;
margin: 0;
color: #8a6d3b;
position: relative;
z-index: 1;
background-color: #fcf8e3;
textarea,input {
background: #fcf8e3;
border: none;
margin-bottom: 1em;
/* min-width: 90%;
max-width: 90%; */
font-size: 15px;
line-height: 17px;
}
> li {
list-style: none;
border-bottom: 1px dashed #C1D6DA;
height: auto;
padding: 10px 10px 5px 55px;
text-transform: capitalize;
margin: 0;
span {
position: absolute;
left: 9px;
color: rgb(199, 180, 146);
font-size: 20px;
font-weight: bold;
}
}
> li:last-child { border-bottom: 0;}
li.header {
h5{padding-left: 0px}
div.dropdown { display: inline;float:right; }
span {position: relative; left: 0;}
a {
color: #C4A87A;
position: absolute;
top: 13px;
right: 13px;
&:hover {
text-decoration: none;
color: #9C855D;
}
}
select {
margin-top: 10px;
margin-bottom: 10px;
width: 100%;
max-width: 80%;
font-family: sans-serif;
font-size: 17px;
color: #8A6D3B;
border: none;
background: #F5E7CF;
}
.help{
font-size: 14px;
color: rgb(126, 95, 41);
background: #fcf8e3;
background: -moz-linear-gradient(top, #fcf8e3 0%, #FFF8D7 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fcf8e3), color-stop(100%,#FFF8D7));
background: -webkit-linear-gradient(top, #fcf8e3 0%,#FFF8D7 100%);
background: -o-linear-gradient(top, #fcf8e3 0%,#FFF8D7 100%);
background: -ms-linear-gradient(top, #fcf8e3 0%,#FFF8D7 100%);
background: linear-gradient(to bottom, #fcf8e3 0%,#FFF8D7 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcf8e3', endColorstr='#FFF8D7',GradientType=0 );
padding: 0 1em;
margin: 0;
border: none;
z-index: 10;
position: relative;
box-shadow: inset 0 10px 10px -9px #A89E64;
margin: 0px -10px 0px -55px;
h5 {padding: 14px 0 0; text-indent:0; display: inline-block;}
ul { padding: 0 20px 20px 50px;}
li {text-indent: 0; font-style: italic;}
}
}
> li:not(.header):hover {
background-color: #F5EFD0;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
}
.lines {
border-left: 1px solid #ffaa9f;
border-right: 1px solid #ffaa9f;
background: transparent;
position: absolute;
width: 3px;
height: 100%;
top: 0;
left: 42px;
border-bottom: 0;
border-top: 0;
}
}
.notebook.columns {
li.header{
padding: 10px 10px 5px 55px;
h5{padding: 0}
}
textarea,input {
vertical-align: top;
margin-bottom: 1em;
}
}
.notebook-container:after {
content: "";
display: block;
width: 94%;
left: 20px;
background: transparent;
height: 100%;
position: absolute;
bottom: -5px;
z-index: 0;
background-color: #fcf8e3;
border: 1px solid #faebcc;
}
.info_icon{
position: relative;
padding-left: 5em;
overflow: hidden;
min-height: 6em;
span.fa{
position: absolute;
font-size: 5em;
top: 0.2em;
left: 0.2em;
opacity: 0.6;
color: white;
}
}
.alert-info.info_icon span.fa { color: #8ABCD4; }
.arrow-down {
width: 0;
height: 0;
border-left: 30px solid transparent;
border-right: 30px solid transparent;
border-top: 15px solid #D6D6D6;
position: absolute;
margin-left: -30px;
bottom: -15px;
}
#planner_page1 {
h1 {margin-bottom: 20px;}
p.last{
margin-bottom: 50px;
}
.fabien {
margin-bottom: 30px;
pointer-events: none;
.item{
float: left;
display: inline;
&:last-child{
margin-left:10px;
}
}
}
img.signature{
margin-top: 3em;
}
address {
margin-bottom: 0px;
font-style: normal;
line-height: 1.42857143;
font-size: 12px;
text-align: left;
}
}
#planner_page2,#planner_page3 {
textarea{
width: 100%;
max-width: 100%;
}
}
#planner_page4 {
textarea {
width: 58%;
max-width: 58%;
}
}
#planner_page5, #planner_page7, #planner_page8, #planner_page10, #planner_page11 {
.container-fluid {
.col-xs-12 {
padding: 5px;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
}
}
#planner_page11 {
.container-fluid {
.col-xs-12 .panel-heading {
min-height: 200px;
padding: 0;
font-size: 1.1em;
color: #407241;
a {
display: block;
padding: 20px;
height: 100%;
width: 100%;
position: absolute;
padding-top: 40px;
padding-left: 10px;
}
}
}
}
#planner_page16 {
.trophy{
font-size: 11em;
color: #A24689;
margin-top: .5em;
-webkit-animation-name: bounceIn;
animation-name: bounceIn;
-webkit-animation-duration: 1.75s;
animation-duration: 1.75s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
}
}
// XS size
@media screen and (max-width: 768px) {
#PlannerModal .pages {
margin-top: 0px;
}
#PlannerModal .counter {
padding: 6px 0 0 5px;
}
}
// SM size
@media screen and (min-width: 769px) {
#PlannerModal .paragraphs {
div.col-md-10 {
margin-top:1em;
}
.icon {
text-align: center;
font-size: 2em;
margin: 1em auto .5em;
}
span {
transform: translateX(-8px);
position: relative;
display: block;
}
}
}
// MD size
@media screen and (min-width: 992px) {
#PlannerModal .paragraphs .icon {
text-align: center;
font-size: 4em;
margin: 0.3em auto .5em;
span{
transform: translateX(0px);
position: relative;
display: block;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="planner crm assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link href="/planner_crm/static/src/less/planner_crm.less" rel="stylesheet" type="text/less"/>
<script type="text/javascript" src="/planner_crm/static/src/js/planner_crm.js"></script>
</xpath>
</template>
</odoo>