فهرست منبع

commit inicial

Deisy 8 سال پیش
کامیت
c262c872a9

+ 10 - 0
README.md

@@ -0,0 +1,10 @@
+# Odoo Invoice Dashboard V8
+This module allows you to display weekly charts as in the version 9 of Odoo.
+
+Depends
+===============
+This module depend the free module <a href="https://apps.odoo.com/apps/modules/8.0/web_kanban_graph/">Graph Widget for Kanban</a>
+
+Result
+===============
+<img src="static/description/example.jpg" title="example">

+ 2 - 0
__init__.py

@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import models

BIN
__init__.pyc


+ 40 - 0
__openerp__.py

@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright :
+#        (c) 2016 Jonathan Bravo, (Guayaquil, Ecuador, http://www.jonathanbravo.com)
+#                 Jonathan Bravo <jjbravo88@gmail.com.com>
+#
+##############################################################################
+
+{
+    'name' : 'Odoo Invoice Dashboard V9',
+    'description': """
+
+Este modulo permite mostrar graficos semanales como en al version 9 de odoo.
+--
+This module allows you to display weekly charts as in the version 9 of Odoo.
+
+	""",
+    'version': '8.0.1.0.0',
+    'category': 'Accounting',
+    'author' : 'Jonathan Bravo @jbravot/Deisy',
+    'complexity': 'normal',
+    'website': 'http://jbravot.github.io/portafolio/',
+    'data': [
+        'views/account_journal_dashboard_view.xml'
+    ],
+    'depends' : [
+        'account',
+        'web_kanban_graph',
+    ],
+    'js': [],
+    'css': [],
+    'qweb': [],
+    "images": [
+		"static/description/example.jpg",
+	],
+    'installable': True,
+    'auto_install': False,
+}

+ 42 - 0
__openerp__.py~

@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright :
+#        (c) 2016 Jonathan Bravo, (Guayaquil, Ecuador, http://www.jonathanbravo.com)
+#                 Jonathan Bravo <jjbravo88@gmail.com.com>
+#
+##############################################################################
+
+{
+    'name' : 'Odoo Invoice Dashboard V9',
+    'description': """
+
+Este modulo permite mostrar graficos semanales como en al version 9 de odoo.
+--
+This module allows you to display weekly charts as in the version 9 of Odoo.
+
+	""",
+    'version': '8.0.1.0.0',
+    'category': 'Accounting',
+    'author' : 'Jonathan Bravo @jbravot',
+    'complexity': 'normal',
+    'website': 'http://jbravot.github.io/portafolio/',
+    'data': [
+        'views/account_journal_dashboard_view.xml'
+    ],
+    'depends' : [
+        'account',
+        'web_kanban_graph',
+    ],
+    'js': [],
+    'css': [],
+    'qweb': [],
+    "images": [
+		"static/description/example.jpg",
+	],
+    'installable': True,
+    'auto_install': False,
+    'price': 35.00,
+    'currency': 'EUR',
+}

+ 2 - 0
models/__init__.py

@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import account_journal_dashboard

BIN
models/__init__.pyc


+ 405 - 0
models/account_journal_dashboard.py

@@ -0,0 +1,405 @@
+import json
+import locale
+from datetime import datetime, timedelta
+
+from babel.dates import format_datetime, format_date
+
+from openerp import models, api, _, fields
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+
+class AccountMoveLine(models.Model):
+    _inherit = "account.move.line"
+
+    @api.depends('debit', 'credit')
+    def _store_balance(self):
+        for line in self:
+            line.balance = line.debit - line.credit
+
+    balance = fields.Float(compute='_store_balance', store=True, currency_field='company_currency_id', default=0.0, help="Technical field holding the debit - credit in order to open meaningful graph views from reports")
+
+
+class account_journal(models.Model):
+    _inherit = "account.journal"
+
+    @api.one
+    def _kanban_dashboard(self):
+        self.kanban_dashboard = json.dumps(self.get_journal_dashboard_datas())
+
+    @api.one
+    def _kanban_dashboard_graph(self):
+        if (self.type in ['sale', 'purchase']):
+            self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
+        elif (self.type in ['cash', 'bank']):
+            self.kanban_dashboard_graph = json.dumps(self.get_line_graph_datas())
+
+    kanban_dashboard = fields.Text(compute='_kanban_dashboard')
+    kanban_dashboard_graph = fields.Text(compute='_kanban_dashboard_graph')
+    show_on_dashboard = fields.Boolean(string='Show journal on dashboard', help="Whether this journal should be displayed on the dashboard or not", default=True)
+
+    @api.multi
+    def toggle_favorite(self):
+        self.write({'show_on_dashboard': False if self.show_on_dashboard else True})
+        return False
+
+    @api.multi
+    def get_line_graph_datas(self):
+        data = []
+        today = datetime.today()
+        last_month = today + timedelta(days=-30)
+        bank_stmt = []
+        # Query to optimize loading of data for bank statement graphs
+        # Return a list containing the latest bank statement balance per day for the
+        # last 30 days for current journal
+        query = """SELECT a.date, a.balance_end
+                        FROM account_bank_statement AS a,
+                            (SELECT c.date, max(c.id) AS stmt_id
+                                FROM account_bank_statement AS c
+                                WHERE c.journal_id = %s
+                                    AND c.date > %s
+                                    AND c.date <= %s
+                                    GROUP BY date, id
+                                    ORDER BY date, id) AS b
+                        WHERE a.id = b.stmt_id;"""
+
+        self.env.cr.execute(query, (self.id, last_month, today))
+        bank_stmt = self.env.cr.dictfetchall()
+
+        last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids),('date', '<=', last_month.strftime(DF))], order="date desc, id desc", limit=1)
+        start_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0
+
+        locale = self._context.get('lang', 'en_US')
+        show_date = last_month
+        #get date in locale format
+        name = format_date(show_date, 'd LLLL Y', locale=locale)
+        short_name = format_date(show_date, 'd MMM', locale=locale)
+        data.append({'x':short_name,'y':start_balance, 'name':name})
+
+        for stmt in bank_stmt:
+            #fill the gap between last data and the new one
+            number_day_to_add = (datetime.strptime(stmt.get('date'), DF) - show_date).days
+            last_balance = data[len(data) - 1]['y']
+            for day in range(0,number_day_to_add + 1):
+                show_date = show_date + timedelta(days=1)
+                #get date in locale format
+                name = format_date(show_date, 'd LLLL Y', locale=locale)
+                short_name = format_date(show_date, 'd MMM', locale=locale)
+                data.append({'x': short_name, 'y':last_balance, 'name': name})
+            #add new stmt value
+            data[len(data) - 1]['y'] = stmt.get('balance_end')
+
+        #continue the graph if the last statement isn't today
+        if show_date != today:
+            number_day_to_add = (today - show_date).days
+            last_balance = data[len(data) - 1]['y']
+            for day in range(0,number_day_to_add):
+                show_date = show_date + timedelta(days=1)
+                #get date in locale format
+                name = format_date(show_date, 'd LLLL Y', locale=locale)
+                short_name = format_date(show_date, 'd MMM', locale=locale)
+                data.append({'x': short_name, 'y':last_balance, 'name': name})
+
+        return [{'values': data, 'area': True, 'id': self.id}]
+
+    @api.multi
+    def get_bar_graph_datas(self):
+        data = []
+        today = datetime.strptime(fields.Date.context_today(self), DF)
+        data.append({'label': _('Anteriores'), 'value':0.0, 'type': 'past'})
+        day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang', 'en_US')))
+        first_day_of_week = today + timedelta(days=-day_of_week+1)
+        for i in range(-1,4):
+            if i==0:
+                label = _('Esta semana')
+            elif i==3:
+                label = _('Futuras')
+            else:
+                start_week = first_day_of_week + timedelta(days=i*7)
+                end_week = start_week + timedelta(days=6)
+                if start_week.month == end_week.month:
+                    label = str(start_week.day) + '-' +str(end_week.day)+ ' ' + format_date(end_week, 'MMM', locale=self._context.get('lang', 'en_US'))
+                else:
+                    label = format_date(start_week, 'd MMM', locale=self._context.get('lang', 'en_US'))+'-'+format_date(end_week, 'd MMM', locale=self._context.get('lang', 'en_US'))
+            data.append({'label':label,'value':0.0, 'type': 'past' if i<0 else 'future'})
+
+        # Build SQL query to find amount aggregated by week
+        select_sql_clause = """SELECT sum(amount_total) as total, min(date_invoice) as aggr_date from account_invoice where journal_id = %(journal_id)s and state IN ('open','paid')"""
+        query = ''
+        start_date = (first_day_of_week + timedelta(days=-7))
+        for i in range(0,6):
+            if i == 0:
+                query += "("+select_sql_clause+" and date_invoice < '"+start_date.strftime(DF)+"')"
+            elif i == 6:
+                query += " UNION ALL ("+select_sql_clause+" and date_invoice >= '"+start_date.strftime(DF)+"')"
+            else:
+                next_date = start_date + timedelta(days=7)
+                query += " UNION ALL ("+select_sql_clause+" and date_invoice >= '"+start_date.strftime(DF)+"' and date_invoice < '"+next_date.strftime(DF)+"')"
+                start_date = next_date
+
+        self.env.cr.execute(query, {'journal_id':self.id})
+        query_results = self.env.cr.dictfetchall()
+        for index in range(0, len(query_results)):
+            if query_results[index].get('aggr_date') != None:
+                data[index]['value'] = query_results[index].get('total')
+
+        return [{'values': data, 'id': self.id}]
+
+    @api.multi
+    def get_journal_dashboard_datas(self):
+        currency = self.currency.id or self.company_id.currency_id
+        number_to_reconcile = last_balance = account_sum = 0
+        ac_bnk_stmt = []
+        title = ''
+        number_draft = number_waiting = number_late = sum_draft = sum_waiting = sum_late = 0
+        if self.type in ['bank', 'cash']:
+            last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1)
+            last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0
+            ac_bnk_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids),('state', '=', 'open')])
+            for ac_bnk in ac_bnk_stmt:
+                for line in ac_bnk.line_ids:
+                    if not line.journal_entry_ids:
+                        number_to_reconcile += 1
+            # optimization to read sum of balance from account_move_line
+            account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id]))
+            if account_ids:
+                query = """SELECT sum(balance) FROM account_move_line WHERE account_id in %s;"""
+                self.env.cr.execute(query, (account_ids,))
+                query_results = self.env.cr.dictfetchall()
+                if query_results and query_results[0].get('sum') != None:
+                    account_sum = query_results[0].get('sum')
+        #TODO need to check if all invoices are in the same currency than the journal!!!!
+        elif self.type in ['sale', 'purchase']:
+            title = _('Bills to pay') if self.type == 'purchase' else _('Invoices owed to you')
+            # optimization to find total and sum of invoice that are in draft, open state
+            query = """SELECT state, amount_total, currency_id AS currency FROM account_invoice WHERE journal_id = %s AND state NOT IN ('paid', 'cancel');"""
+            self.env.cr.execute(query, (self.id,))
+            query_results = self.env.cr.dictfetchall()
+            today = datetime.today()
+            query = """SELECT amount_total, currency_id AS currency FROM account_invoice WHERE journal_id = %s AND date_invoice < %s AND state = 'open';"""
+            self.env.cr.execute(query, (self.id, today))
+            late_query_results = self.env.cr.dictfetchall()
+            sum_draft = 0.0
+            number_draft = 0
+            number_waiting = 0
+
+            for result in query_results:
+                cur = self.env['res.currency'].browse(result.get('currency'))
+                if result.get('state') in ['draft', 'proforma', 'proforma2']:
+                    number_draft += 1
+                    sum_draft += cur.compute(result.get('amount_total'), currency)
+                elif result.get('state') == 'open':
+                    number_waiting += 1
+                    sum_waiting += cur.compute(result.get('amount_total'), currency)
+            sum_late = 0.0
+            number_late = 0
+            for result in late_query_results:
+                cur = self.env['res.currency'].browse(result.get('currency'))
+                number_late += 1
+                sum_late += cur.compute(result.get('amount_total'), currency)
+
+        return {
+            'number_to_reconcile': number_to_reconcile,
+            'account_balance': self.formatLang(self.env, account_sum, currency_obj=self.currency.id or self.company_id.currency_id),
+            'last_balance': self.formatLang(self.env, last_balance, currency_obj=self.currency.id or self.company_id.currency_id),
+            'number_draft': number_draft,
+            'number_waiting': number_waiting,
+            'number_late': number_late,
+            'currency_id':currency.symbol,
+            'sum_draft': locale.format("%.2f",sum_draft , grouping=True),
+            'sum_waiting':locale.format("%.2f",sum_waiting , grouping=True),
+            'sum_late': self.formatLang(self.env, sum_late or 0.0, currency_obj=self.currency.id or self.company_id.currency_id),
+            'bank_statements_source': False,
+            'title': title,
+        }
+
+    @api.multi
+    def action_create_new(self):
+        ctx = self._context.copy()
+        model = 'account.invoice'
+        if self.type == 'sale':
+            ctx.update({'journal_type': self.type, 'default_type': 'out_invoice', 'type': 'out_invoice', 'default_journal_id': self.id})
+            if ctx.get('refund'):
+                ctx.update({'default_type':'out_refund', 'type':'out_refund'})
+            view_id = self.env.ref('account.invoice_form').id
+        elif self.type == 'purchase':
+            ctx.update({'journal_type': self.type, 'default_type': 'in_invoice', 'type': 'in_invoice', 'default_journal_id': self.id})
+            if ctx.get('refund'):
+                ctx.update({'default_type': 'in_refund', 'type': 'in_refund'})
+            view_id = self.env.ref('account.invoice_supplier_form').id
+        else:
+            ctx.update({'default_journal_id': self.id})
+            view_id = self.env.ref('account.view_move_form').id
+            model = 'account.move'
+        return {
+            'name': _('Create invoice/bill'),
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': model,
+            'view_id': view_id,
+            'context': ctx,
+        }
+
+    @api.multi
+    def create_cash_statement(self):
+        ctx = self._context.copy()
+        ctx.update({'journal_id': self.id, 'default_journal_id': self.id, 'default_journal_type': 'cash'})
+        return {
+            'name': _('Create cash statement'),
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'account.bank.statement',
+            'context': ctx,
+        }
+
+    @api.multi
+    def action_open_reconcile(self):
+        if self.type in ['bank', 'cash']:
+            # Open reconciliation view for bank statements belonging to this journal
+            bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)])
+            return {
+                'type': 'ir.actions.client',
+                'tag': 'bank_statement_reconciliation_view',
+                'context': {'statement_ids': bank_stmt.ids},
+            }
+        else:
+            # Open reconciliation view for customers/suppliers
+            action_context = {'show_mode_selector': False}
+            if self.type == 'sale':
+                action_context.update({'mode': 'customer'})
+            elif self.type == 'purchase':
+                action_context.update({'mode': 'supplier'})
+            return {
+                'type': 'ir.actions.client',
+                'tag': 'manual_reconciliation_view',
+                'context': action_context,
+            }
+
+    @api.multi
+    def open_action(self):
+        """return action based on type for related journals"""
+        action_name = self._context.get('action_name', False)
+        if not action_name:
+            if self.type == 'bank':
+                action_name = 'action_bank_statement_tree'
+            elif self.type == 'cash':
+                action_name = 'action_view_bank_statement_tree'
+            elif self.type == 'sale':
+                action_name = 'action_invoice_tree1'
+            elif self.type == 'purchase':
+                action_name = 'action_invoice_tree2'
+            else:
+                action_name = 'action_move_journal_line'
+
+        _journal_invoice_type_map = {
+            'sale': 'out_invoice',
+            'purchase': 'in_invoice',
+            'bank': 'bank',
+            'cash': 'cash',
+            'general': 'general',
+        }
+        invoice_type = _journal_invoice_type_map[self.type]
+
+        ctx = self._context.copy()
+        ctx.update({
+            'journal_type': self.type,
+            'default_journal_id': self.id,
+            'search_default_journal_id': self.id,
+            'default_type': invoice_type,
+            'type': invoice_type
+        })
+        ir_model_obj = self.pool['ir.model.data']
+        model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account', action_name)
+        action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
+        action['context'] = ctx
+        action['domain'] = self._context.get('use_domain', [])
+        return action
+
+    @api.multi
+    def open_spend_money(self):
+        return self.open_payments_action('outbound')
+
+    @api.multi
+    def open_collect_money(self):
+        return self.open_payments_action('inbound')
+
+    @api.multi
+    def open_transfer_money(self):
+        return self.open_payments_action('transfer')
+
+    @api.multi
+    def open_payments_action(self, payment_type):
+        ctx = self._context.copy()
+        ctx.update({
+            'default_payment_type': payment_type,
+            'default_journal_id': self.id
+        })
+        action_rec = self.env['ir.model.data'].xmlid_to_object('account.action_account_payments')
+        if action_rec:
+            action = action_rec.read([])[0]
+            action['context'] = ctx
+            action['domain'] = [('journal_id','=',self.id),('payment_type','=',payment_type)]
+            return action
+
+    @api.multi
+    def open_action_with_context(self):
+        action_name = self.env.context.get('action_name', False)
+        if not action_name:
+            return False
+        ctx = dict(self.env.context, default_journal_id=self.id)
+        if ctx.get('search_default_journal', False):
+            ctx.update(search_default_journal_id=self.id)
+        ir_model_obj = self.pool['ir.model.data']
+        model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account', action_name)
+        action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
+        action['context'] = ctx
+        if ctx.get('use_domain', False):
+            action['domain'] = ['|', ('journal_id', '=', self.id), ('journal_id', '=', False)]
+            action['name'] += ' for journal '+self.name
+        return action
+
+    @api.multi
+    def create_bank_statement(self):
+        """return action to create a bank statements. This button should be called only on journals with type =='bank'"""
+        self.bank_statements_source = 'manual'
+        action = self.env.ref('account.action_bank_statement_tree').read()[0]
+        action.update({
+            'views': [[False, 'form']],
+            'context': "{'default_journal_id': " + str(self.id) + "}",
+        })
+        return action
+
+
+    def formatLang(self, env, value, digits=None, grouping=True, monetary=False, dp=False, currency_obj=False):
+        """
+            Assuming 'Account' decimal.precision=3:
+                formatLang(value) -> digits=2 (default)
+                formatLang(value, digits=4) -> digits=4
+                formatLang(value, dp='Account') -> digits=3
+                formatLang(value, digits=5, dp='Account') -> digits=5
+        """
+
+        if digits is None:
+            digits = DEFAULT_DIGITS = 2
+            if dp:
+                decimal_precision_obj = env['decimal.precision']
+                digits = decimal_precision_obj.precision_get(dp)
+            elif (hasattr(value, '_field') and isinstance(value._field, (float_field, function_field)) and value._field.digits):
+                    digits = value._field.digits[1]
+                    if not digits and digits is not 0:
+                        digits = DEFAULT_DIGITS
+
+        if isinstance(value, (str, unicode)) and not value:
+            return ''
+
+        lang = env.user.company_id.partner_id.lang or 'en_US'
+        lang_objs = env['res.lang'].search([('code', '=', lang)])
+        if not lang_objs:
+            lang_objs = env['res.lang'].search([('code', '=', 'en_US')])
+        lang_obj = lang_objs[0]
+
+        res = lang_obj.format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
+
+        #if currency_obj:
+        #    res = '%s %s' % (currency_obj.symbol, res)
+        return res

BIN
models/account_journal_dashboard.pyc


BIN
static/description/example.jpg


BIN
static/description/icon.png


+ 18 - 0
static/description/index.html

@@ -0,0 +1,18 @@
+<section class="oe_container">
+    <div class="oe_row oe_spaced">
+        <div class="oe_span12">
+            <h2 class="oe_slogan">Odoo Invoice Dashboard V8</h2>
+            <h3 class="oe_slogan">This module allows you to display weekly charts as in the version 9 of Odoo.</h3>
+        </div>
+        <div class="oe_span12">
+            <p class="oe_mt32">
+This module depend the free module <a href="https://apps.odoo.com/apps/modules/8.0/web_kanban_graph/">Graph Widget for Kanban</a>
+            </p>
+        </div>
+        <div class="oe_span12">
+            <div class="oe_demo oe_picture oe_screenshot">
+                    <img src="example.jpg">
+            </div>
+        </div>
+    </div>
+</section>

+ 95 - 0
static/src/js/account_journal_dashboard_widget.js

@@ -0,0 +1,95 @@
+openerp.journal_dashboard = function (instance) {
+
+instance.web_kanban.JournalDashboardGraph = instance.web_kanban.AbstractField.extend({
+    start: function() {
+        this.graph_type = this.$node.attr('graph_type');
+        this.data = JSON.parse(this.field.raw_value);
+        this.display_graph();
+        return this._super();
+    },
+
+    display_graph : function() {
+        var self = this;
+        nv.addGraph(function () {
+            self.$svg = self.$el.append('<svg>');
+
+            switch(self.graph_type) {
+
+                case "line":
+                    self.$svg.addClass('o_graph_linechart');
+
+                    self.chart = nv.models.lineChart();
+                    self.chart.options({
+                        x: function(d, u) { return u },
+                        margin: {'left': 0, 'right': 0, 'top': 0, 'bottom': 0},
+                        showYAxis: false,
+                        showLegend: false,
+                    });
+                    self.chart.xAxis
+                        .tickFormat(function(d) {
+                            var label = '';
+                            _.each(self.data, function(v, k){
+                                if (v.values[d] && v.values[d].x){
+                                    label = v.values[d].x;
+                                }
+                            });
+                            return label;
+                        });
+                    self.chart.yAxis
+                        .tickFormat(d3.format(',.2f'));
+
+                    break;
+
+                case "bar":
+                    self.$svg.addClass('o_graph_barchart');
+
+                    self.chart = nv.models.discreteBarChart()
+                        .x(function(d) { return d.label })
+                        .y(function(d) { return d.value })
+                        .showValues(false)
+                        .showYAxis(false)
+                        .margin({'left': 20, 'right': 0, 'top': 0, 'bottom': 40});
+
+                    self.chart.xAxis.axisLabel(self.data[0].title);
+                    self.chart.yAxis.tickFormat(d3.format(',.2f'));
+
+                    break;
+            }
+            d3.select(self.$el.find('svg')[0])
+                .datum(self.data)
+                .transition().duration(1200)
+                .call(self.chart);
+
+            self.customize_chart();
+
+            nv.utils.windowResize(self.on_resize);
+        });
+    },
+
+    on_resize: function(){
+        this.chart.update();
+        this.customize_chart();
+    },
+
+    customize_chart: function(){
+        if (this.graph_type === 'bar') {
+            // Add classes related to time on each bar of the bar chart
+            var bar_classes = _.map(this.data[0].values, function (v, k) {return v.type});
+
+            _.each(this.$('.nv-bar'), function(v, k){
+                // classList doesn't work with phantomJS & addClass doesn't work with a SVG element
+                $(v).attr('class', $(v).attr('class') + ' ' + bar_classes[k]);
+            });
+        }
+    },
+
+    destroy: function(){
+        nv.utils.offWindowResize(this.on_resize);
+        this._super();
+    },
+
+});
+
+instance.web_kanban.fields_registry.add('dashboard_graph',  "instance.web_kanban.JournalDashboardGraph");
+
+}

+ 121 - 0
static/src/less/account_journal_dashboard.less

@@ -0,0 +1,121 @@
+.o_kanban_view.o_kanban_dashboard.o_account_kanban {
+    .o_kanban_record {
+        @media (min-width: @screen-sm-min) {
+            min-width: 450px;
+        }
+        .oe_kanban_action_button {
+            margin-bottom: 5px;
+        }
+
+        .o_kanban_card_settings {
+            padding-top: @odoo-horizontal-padding/2;
+            padding-bottom: @odoo-horizontal-padding/2;
+
+            border-top: 1px solid;
+            border-color: @odoo-brand-lightsecondary;
+        }
+        .o_dashboard_star {
+            font-size: 12px;
+
+
+            &.fa-star-o {
+                color: @odoo-main-color-muted;
+
+                &:hover {
+                    color: gold;
+                }
+            }
+            &.fa-star {
+                color: gold;
+            }
+        }
+
+        .o_kanban_card_content {
+            // give space for graph section at the bottom
+            margin-bottom: 75px;
+            min-height: 120px;
+
+
+        }
+
+        .o_kanban_graph_section {
+
+            .o-position-absolute(auto, 0, 0, 0);
+            .o_graph_linechart {
+                > svg {
+                    height: 75px;
+
+                    // X axis
+                    g.nv-x.nv-axis {
+                        transform: translateY(50px);
+
+                        // Remove max and min labels
+                        g.nv-axisMaxMin {
+                            display: none;
+                        }
+                        g.tick.major {
+                            // Don't know why, but leads to strange bold text
+                            // stroke: @odoo-brand-primary;
+
+                            line {
+                                display: none;
+                            }
+                        }
+                    }
+
+                    // !important are needed because nvd3 uses inline style on elements
+
+                    // Remove strokes
+                    g.nv-linesWrap {
+                        g.nv-group.nv-series-0 {
+                            fill-opacity: 0.3 !important;
+                            fill: @odoo-brand-primary !important;
+                            stroke: @odoo-brand-primary !important;
+
+                            .nv-point {
+                                visibility: hidden;
+                                r: 3;
+                                stroke: @odoo-brand-primary !important;
+                                fill: white;
+                                stroke-width: 2;
+                            }
+                            .nv-point:nth-child(5n+1) {
+                                visibility: visible;
+                                fill-opacity: .95 !important;
+                                stroke-opacity: .95 !important;
+                            }
+                        }
+                    }
+                }
+            }
+            .o_graph_barchart {
+                > svg {
+                    height: 75px;
+                    overflow: visible;
+                    g.nv-barsWrap {
+                        g.nv-group.nv-series-0 {
+                            g.past {
+                                opacity: 0.5;
+                                fill: @odoo-brand-primary !important;
+                            }
+                            g.future {
+                                opacity: 0.5;
+                                fill: @odoo-brand-optional !important;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        .o_invisible .o_kanban_graph_section {
+            display: none; // This fix a bug on firefox
+        }
+    }
+
+    &.o_kanban_grouped .o_kanban_record {
+        min-width: inherit;
+    }
+
+
+}

+ 189 - 0
views/account_journal_dashboard_view.xml

@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+  <data>
+<record id="view_filter_inherited" model="ir.ui.view">
+    <field name="name">view_filter_inherited</field>
+    <field name="model">account.invoice</field>
+    <field name="inherit_id" ref="account.view_account_invoice_filter"/>
+    <field name="arch" type="xml">
+        <field name="number" position="after">
+            <filter name="sale_draft" string="Sale Draft" domain="['&amp;',('state','=','draft'),('type','=','out_invoice')]" invisible="1"/>
+            <filter name="purchase_draft" string="Purchase Draft" domain="['&amp;',('state','=','draft'),('type','=','in_invoice')]" invisible="1" />
+            <filter name="sale_open" string="Sale Open" domain="['&amp;',('state','=','open'),('type','=','out_invoice')]" invisible="1"/>
+            <filter name="purchase_open" string="Purchase Open" domain="['&amp;',('state','=','open'),('type','=','in_invoice')]" invisible="1"/>
+        </field>
+        <filter name="draft" position="attributes">
+        <attribute name="invisible">True</attribute>
+        </filter>
+        <filter name="proforma" position="attributes">
+        <attribute name="invisible">True</attribute>
+        </filter>
+        <filter name="invoices" position="attributes">
+        <attribute name="invisible">True</attribute>
+        </filter>
+        <filter name="unpaid" position="attributes">
+        <attribute name="invisible">True</attribute>
+        </filter>
+    </field>
+</record>
+
+    <!--  -->
+    <record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
+        <field name="name">account.journal.dashboard.kanban</field>
+        <field name="model">account.journal</field>
+        <field name="arch" type="xml">
+            <kanban class="oe_background_grey" create="0">
+                <field name="id"/>
+                <field name="name"/>
+                <field name="type"/>
+                <field name="show_on_dashboard"/>
+                <field name="kanban_dashboard"/>
+                <templates>
+                    <t t-name="kanban-box">
+                        <div t-attf-class="oe_kanban_color_#{kanban_getcolor(3)} oe_kanban_card oe_kanban_invoice">
+                            <t t-value="JSON.parse(record.kanban_dashboard.raw_value)" t-set="dashboard"/>
+                            <t t-value="record.type.raw_value" t-set="journal_type"/>
+                            <t t-value="record.id" t-set="id_graph"/>
+
+                            <t t-call="JournalTop"/>
+                            <div class="o_kanban_card_content o_visible">
+                                <div class="row">
+                                    <t t-if="(journal_type == 'bank' || journal_type == 'cash')" t-call="JournalBodyBankCash"/>
+                                    <t t-if="journal_type == 'sale' || journal_type == 'purchase'" t-call="JournalBodySalePurchase"/>
+                                    <div t-if="journal_type == 'general' || journal_type == 'situation'" class="row">
+                                    </div>
+                                </div>
+                                <t t-if="journal_type == 'bank' || journal_type == 'cash' || journal_type == 'sale' || journal_type == 'purchase'" t-call="JournalBodyGraph"/>
+                            </div>
+                        </div>
+                    </t>
+
+                    <t t-name="JournalTop">
+                        <div class="o_kanban_card_header">
+                            <div class="o_kanban_card_header_title">
+                                <h4 class="text-center">
+                                    <strong><field name="name"/></strong>
+                                </h4>
+                            </div>
+                        </div>
+                    </t>
+
+                    <t t-name="JournalBodyBankCash">
+                        <!-- On the left, display :
+                            - A button corresponding to the bank_statements_source, if it wasn't configured, a button for each of them
+                            - If there are statements to reconcile, a link to reconcile them -->
+                        <div class="col-xs-6 o_kanban_primary_left">
+                            <t t-if="dashboard.number_to_reconcile > 0">
+                                <button type="object" name="action_open_reconcile" class="btn btn-primary btn-sm"> Reconcile <t t-esc="dashboard.number_to_reconcile"/> Items</button>
+                            </t>
+                            <t t-if="journal_type == 'bank'">
+                                <div name="bank_journal_default_cta" t-if="! dashboard.bank_statements_source">
+                                    <button t-if="dashboard.number_to_reconcile == 0" type="object" name="create_bank_statement" class="btn btn-primary btn-sm">New Statement</button>
+                                    <a t-if="dashboard.number_to_reconcile > 0" type="object" name="create_bank_statement" class="oe_inline">New Statement</a>
+                                </div>
+                                <div name="bank_journal_cta" t-if="dashboard.bank_statements_source">
+                                    <button t-if="dashboard.bank_statements_source == 'manual' &amp;&amp; dashboard.number_to_reconcile == 0" type="object" name="create_bank_statement" class="btn btn-primary btn-sm">New Statement</button>
+                                    <a t-if="dashboard.bank_statements_source == 'manual' &amp;&amp; dashboard.number_to_reconcile > 0" type="object" name="create_bank_statement" class="oe_inline">New Statement</a>
+                                </div>
+                            </t>
+                            <t t-if="dashboard.number_to_reconcile > 0">
+                                <a t-if="journal_type == 'cash'" type="object" name="create_cash_statement" class="oe_inline">New Transactions</a>
+                            </t>
+                            <t t-if="dashboard.number_to_reconcile == 0">
+                                <button t-if="journal_type == 'cash'" type="object" name="create_cash_statement" class="btn btn-primary btn-sm">New Transactions</button>
+                            </t>
+                        </div>
+                        <!-- On the right, show other common informations/actions -->
+                        <div class="col-xs-6 o_kanban_primary_right">
+                            <div class="row">
+                                <div class="col-xs-6">
+                                    <span title="Balance in Odoo">Balance in Odoo</span>
+                                </div>
+                                <div class="col-xs-6 text-right">
+                                    <span><t t-esc="dashboard.account_balance"/></span>
+                                </div>
+                            </div>
+                            <div class="row" name="latest_statement" t-if="dashboard.last_balance != dashboard.account_balance">
+                                <div class="col-xs-6">
+                                    <span title="Latest Statement">Latest Statement</span>
+                                </div>
+                                <div class="col-xs-6 text-right">
+                                    <span><t t-esc="dashboard.last_balance"/></span>
+                                </div>
+                            </div>
+                        </div>
+                    </t>
+                    <t t-name="JournalBodySalePurchase">
+                        <div class="col-xs-5 o_kanban_primary_left">
+                            <button type="object" name="action_create_new" class="btn btn-sm">
+                                <t t-if="journal_type == 'sale'"><span>Nueva Venta</span></t>
+                                <t t-if="journal_type == 'purchase'"><span>Nueva Compra</span></t>
+                            </button>
+                        </div>
+                        <div class="col-xs-7 o_kanban_primary_right">
+                            <div class="row">
+                                <div class="col-xs-6">
+                                    <a type="object" name="open_action" context="{'search_default_sale_draft': '1'}">
+                                        <span  t-if="journal_type == 'sale'" title="Facturas por validar"><t t-esc="dashboard.number_draft"/> Facturas por validar</span>
+                                    </a>
+                                    <a type="object" name="open_action" context="{'search_default_purchase_draft': '1'}">
+                                        <span t-if="journal_type == 'purchase'" title="Facturas en borrador"><t t-esc="dashboard.number_draft"/> Facturas en borrador</span>
+                                    </a>
+                                </div>
+                                <div class="col-xs-6 text-right">
+                                    <span><t t-esc="dashboard.currency_id"/></span>
+                                    <span><t t-esc="dashboard.sum_draft"/></span>
+
+
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="col-xs-6">
+                                    <a type="object" name="open_action" context="{'search_default_sale_open':'1'}">
+                                        <span t-if="journal_type == 'sale'" title="Esperando pagos"><t t-esc="dashboard.number_waiting"/> Esperando pagos</span>
+                                    </a>
+                                    <a type="object" name="open_action" context="{'search_default_purchase_open': '1'}">
+                                        <span t-if="journal_type == 'purchase'" title="Pagos a realizar"><t t-esc="dashboard.number_waiting"/> Pagos a realizar</span>
+                                    </a>
+                                </div>
+                                <div class="col-xs-6 text-right">
+                                  <span><t t-esc="dashboard.currency_id"/></span>
+                                    <span> <t t-esc="dashboard.sum_waiting"/></span>
+                                </div>
+                            </div>
+                        </div>
+                    </t>
+                    <t t-name="JournalBodyGraph">
+                        <div class="o_kanban_graph_section">
+                            <field name="kanban_dashboard_graph" t-att-graph_type="_.contains(['cash','bank'],journal_type) ? 'line' : 'bar'" widget="kanban_graph"/>
+                        </div>
+                    </t>
+            </templates>
+            </kanban>
+        </field>
+    </record>
+
+    <record id="open_account_journal_dashboard_kanban" model="ir.actions.act_window">
+        <field name="name">Control de Mando</field>
+        <field name="res_model">account.journal</field>
+        <field name="view_type">form</field>
+        <field name="view_mode">kanban,form</field>
+        <field name="usage">menu</field>
+        <field name="context">{}</field>
+        <field name="domain">['|',('type', '=', 'sale'),('type', '=', 'purchase')]</field>
+
+    </record>
+
+    <menuitem id="menu_board_journal_1" name="Control de Mando" parent="account.menu_finance" sequence="1"/>
+    <menuitem id="submenu_board_journal_1" name="Resumen" action="open_account_journal_dashboard_kanban"
+              parent="menu_board_journal_1" sequence="1"/>
+
+
+
+    <!--  -->
+
+
+
+
+  </data>
+</openerp>