diff --git a/__manifest__.py b/__manifest__.py index 36a31fee484f9f88e18642f1d10bcfee157a3ee1..84c541455f296f3b95311ece9f25a912a573a76d 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -17,6 +17,10 @@ ], 'website': 'http://www.le-filament.com', 'data': [ + 'views/assets.xml', 'views/lefilament_projets_view.xml', ], + 'qweb': [ + 'static/src/xml/*.xml', + ], } diff --git a/models/lefilament_projets.py b/models/lefilament_projets.py index 9572e0fe984862c53518cffea10cc9907b06c158..97f5c9cee3147a55fd697adf34d0eb83c193d793 100644 --- a/models/lefilament_projets.py +++ b/models/lefilament_projets.py @@ -20,6 +20,14 @@ class FilamentProjet(models.Model): lf_couts_annexes = fields.Float('Coûts Annexes', compute='_couts_annexes') lf_couts_estimes = fields.Float('Coûts Estimés') lf_commentaire = fields.Text('Commentaires') + lf_delay = fields.Integer('Délai facture', compute='_delay') + lf_total_days = fields.Integer('Durée projet', compute='_total_days') + lf_advance = fields.Float('Cash avancé', compute='_advance') + lf_balance = fields.Float('Balance Actuelle', compute='_balance') + + #################################################### + ### Fields Function ### + #################################################### @api.one def _taux_horaire(self): @@ -30,7 +38,7 @@ class FilamentProjet(models.Model): def _total_heures_passees(self): res = 0.0 project = self.id - ############## Calcul heures ################ + ## Calcul heures self.env.cr.execute("select sum(unit_amount) from account_analytic_line where project_id=%s;", (project, ) ) heures_passees = self.env.cr.fetchone()[0] if heures_passees: @@ -74,6 +82,63 @@ class FilamentProjet(models.Model): def _total_heures_restantes(self): self.lf_heures_restantes = self.lf_heures_projet - self.lf_heures_passees + ## Time to first invoice + @api.one + def _delay(self): + account_id = self.analytic_account_id.id + self._cr.execute(""" + SELECT + date(d_invoice) - date(d_cost) as datediff + from + (select date FROM account_analytic_line where account_id=%s and amount < 0 order by date limit 1) as d_cost, + (select date FROM account_analytic_line where account_id=%s and amount > 0 order by date limit 1) as d_invoice + ;""", (account_id, account_id ) ) + self.lf_delay = self._cr.fetchone()[0] + + ## Total Days + @api.one + def _total_days(self): + account_id = self.analytic_account_id.id + self.env.cr.execute(""" + SELECT + date(d_last) - date(d_first) as datediff + from + (select date FROM account_analytic_line where account_id=%s order by date limit 1) as d_first, + (select date FROM account_analytic_line where account_id=%s order by date desc limit 1) as d_last + ;""", (account_id, account_id, ) ) + self.lf_total_days = self._cr.fetchone()[0] + + ## Balance + @api.one + def _balance(self): + account_id = self.analytic_account_id.id + self.env.cr.execute(""" + SELECT sum(amount) as Total + FROM account_analytic_line + WHERE account_id=%s + ;""", (account_id, ) ) + self.lf_balance = self._cr.fetchone()[0] + + + ## Advance cash + @api.one + def _advance(self): + account_id = self.analytic_account_id.id + self.env.cr.execute(""" + SELECT sum(amount) as Total + FROM account_analytic_line + WHERE account_id=31 + and date < (case when + (select date FROM account_analytic_line where account_id=31 and amount > 0 order by date limit 1) IS NOT NULL then + (select date FROM account_analytic_line where account_id=31 and amount > 0 order by date limit 1) else current_date end + );""", (account_id, account_id, account_id, ) ) + self.lf_advance = self._cr.fetchone()[0] + + + #################################################### + ### Actions ### + #################################################### + def open_project(self): return { 'type': 'ir.actions.act_window', @@ -83,3 +148,61 @@ class FilamentProjet(models.Model): 'res_id': self.id, 'views': [(False, 'kanban')], } + + def open_cash_flow(self): + return { + 'type': 'ir.actions.client', + 'name': 'Cash Flow - ' + self.analytic_account_id.name, + 'tag': 'lefilament_projets.cashFlow', + 'target': 'new', + 'params': { + 'account_id': self.analytic_account_id.id, + 'project_id': self.id, + }, + } + + #################################################### + ### Widget Function ### + #################################################### + + @api.model + def cash_flow(self, account_id, project_id): + account = self.env['account.analytic.account'].search([('id', '=', account_id)]) + project = self.env['project.project'].search([('id', '=', project_id)]) + + ## Analytic account name + nom = account.name + + ## Cash Flow Request + self.env.cr.execute(""" + SELECT date, sum(sum(amount)) over (order by date) as Total, + sum(sum(case when is_timesheet then amount else 0 end)) over (order by date) as Timesheet, + sum(sum(case when is_timesheet = False and amount < 0 then amount else 0 end)) over (order by date) as Fournisseurs, + sum(sum(case when is_timesheet = False and amount > 0 then amount else 0 end)) over (order by date) as Clients + FROM account_analytic_line + WHERE account_id=%s + group by date; + """, (account_id, ) ) + cash_flow = self._cr.dictfetchall() + + ## Time to first invoice + delay = project.lf_delay + + ## Total Days + total_days = project.lf_total_days + + ## Balance + balance = project.lf_balance + + ## Advance cash + advance = project.lf_advance + + return { + 'nom' : nom, + 'cash_flow': cash_flow, + 'delay': delay, + 'balance': balance, + 'advance': advance, + 'total_days': total_days, + } + diff --git a/models/res_config.py b/models/res_config.py index 9e25d0ed17910318f16d52b903f4360edb4c842f..f17f990974cdc58a6080df98cdf6d02c865280b0 100644 --- a/models/res_config.py +++ b/models/res_config.py @@ -10,10 +10,10 @@ class ProjectLFConfiguration(models.TransientModel): @api.multi def set_default_lf_heures_jour(self): - check = self.env.user.has_group('base.group_system') - Values = check and self.env['ir.values'].sudo() or self.env['ir.values'] - for config in self: - Values.set_default('project.config.settings', 'lf_heures_jour', config.lf_heures_jour) + # if self.env.user.has_group('project.group_erp_manager'): + Values = self.env['ir.values'].sudo() + + Values.set_default('project.config.settings', 'lf_heures_jour', self.lf_heures_jour) @api.multi def get_default_lf_heures_jour(self, field): diff --git a/static/src/js/cash_flow.js b/static/src/js/cash_flow.js new file mode 100644 index 0000000000000000000000000000000000000000..c91e5505d5681924efaae0729d5b58fc72353bfb --- /dev/null +++ b/static/src/js/cash_flow.js @@ -0,0 +1,135 @@ +odoo.define('lefilament_projets.cashFlow', function(require) { + 'use strict'; + + var core = require('web.core'); + var data = require('web.data'); + var Widget = require('web.Widget'); + var Model = require('web.Model'); + var form_common = require('web.form_common'); + + var _t = core._t; + var QWeb = core.qweb; + + var CashFlow = Widget.extend({ + template: "CashFlow", + + init: function(parent, action) { + this._super(parent, action); + this.account_id = action.params.account_id; + this.project_id = action.params.project_id; + }, + + willStart: function() { + var deferred = new jQuery.Deferred(); + var self = this; + self.cash_datas = {}; + + var dash_model = new Model('project.project'); + dash_model.call('cash_flow', [self.account_id, self.project_id]) + .then(function(results) { + self.cash_datas = results.cash_flow; + self.project = results.nom; + self.delay = results.delay; + self.total_days = results.total_days; + self.balance = results.balance; + self.advance = results.advance; + deferred.resolve(); + }); + + return jQuery.when(this._super.apply(this, arguments),deferred); + }, + + start: function() { + var sup = this._super(); + + this.display_chart(); + return sup; + }, + + display_chart: function() { + var self = this; + + this.ctx = this.$el.find('#cash_flow')[0].getContext('2d'); + + var labels = []; + + var total = []; + var heures = []; + var fournisseurs = []; + var clients = []; + + _.each( self.cash_datas, function(value, key) { + labels.push(moment(value.date)); + total.push(value.total); + heures.push(value.timesheet); + fournisseurs.push(value.fournisseurs); + clients.push(value.clients); + }); + + + var datasets = [ + { label: 'Total', data: total, backgroundColor: 'transparent',borderColor: '#5E8ED5', }, + { label: 'Timesheet', data: heures, backgroundColor: 'transparent',borderColor: '#FFDFA9', borderWidth: 1.5, radius: 0, }, + { label: 'Client', data: clients, backgroundColor: 'transparent',borderColor: '#51d2b7', borderWidth: 1.5, radius: 0, }, + { label: 'Fournisseur', data: fournisseurs, backgroundColor: 'transparent',borderColor: '#FCA7B3', borderWidth: 1.5, radius: 0, }, + ]; + + var options = { + scales: { + yAxes: [{ + ticks: { + beginAtZero:true, + } + }], + xAxes: [{ + type: 'time', + time: { + displayFormats: { + 'millisecond': 'DD MMM', + 'second': 'DD MMM', + 'minute': 'DD MMM', + 'hour': 'DD MMM', + 'day': 'DD MMM', + 'week': 'DD MMM', + 'month': 'DD MMM', + 'quarter': 'DD MMM', + 'year': 'DD MMM', + }, + }, + }], + }, + tooltips: { + backgroundColor: 'rgba(255,255,255,0.8)', + titleFontStyle: 'normal', + titleFontColor: '#999', + bodyFontColor: '#777', + callbacks: { + label: function(tooltipItems, data) { + return ' ' + (tooltipItems.yLabel).toLocaleString('fr', { maximumFractionDigits: 0 }) + ' €'; + }, + title: function(tooltipItems, data) { + return ' ' + (tooltipItems[0].xLabel._d).toLocaleString('fr', {day: 'numeric', month:'short', year:'2-digit'}); + } + } + }, + responsive: true, + } + + this.targetData = { + labels : labels, + datasets : datasets + }; + + var myChart = new Chart(this.ctx, { type: 'line', data: this.targetData, options: options } ); + }, + + render_monetary: function(value) { + value = value.toLocaleString('fr', { maximumFractionDigits: 0 }) + ' €'; + return value; + }, + + }); + + core.action_registry.add('lefilament_projets.cashFlow', CashFlow); + +}); diff --git a/static/src/xml/cash_flow.xml b/static/src/xml/cash_flow.xml new file mode 100644 index 0000000000000000000000000000000000000000..c07fbe08ed81862d91f81af75da8ca5bbdf4baeb --- /dev/null +++ b/static/src/xml/cash_flow.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-name="CashFlow"> + <div class="card"> + <table class="table"> + <thead> + <tr> + <th>Balance actuelle</th> + <th>Durée Projet</th> + <th>Délai 1ère facture</th> + <th>Cash avancé</th> + </tr> + </thead> + <tbody> + <tr> + <td><t t-esc="widget.render_monetary(widget.balance)"></t></td> + <td><t t-esc="widget.total_days"></t> jours</td> + <td><t t-esc="widget.delay"></t> jours</td> + <td><t t-esc="widget.render_monetary(widget.advance)"></t></td> + </tr> + </tbody> + </table> + <canvas id="cash_flow" width="auto" height="100"></canvas> + </div> + </t> + + +</templates> \ No newline at end of file diff --git a/views/assets.xml b/views/assets.xml new file mode 100644 index 0000000000000000000000000000000000000000..1725223cc6431b05788d99d5aa9022d65a7e0b7b --- /dev/null +++ b/views/assets.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + + <template id="lefilament_tdb_assets_backend" inherit_id="web.assets_backend"> + <xpath expr="." position="inside"> + + <link rel="stylesheet" href="/lefilament_projets/static/src/less/style.less"/> + + <script type="text/javascript" src="/lefilament_projets/static/src/js/widget.js"></script> + <script type="text/javascript" src="/lefilament_projets/static/src/js/cash_flow.js"></script> + + </xpath> + </template> + + </data> +</odoo> diff --git a/views/lefilament_projets_view.xml b/views/lefilament_projets_view.xml index 23b47d4a6e9abc64b5b015868e5f16484909a037..cf33f7cbf3ffa8c8b0a755e65600bf3ea7050958 100644 --- a/views/lefilament_projets_view.xml +++ b/views/lefilament_projets_view.xml @@ -3,21 +3,17 @@ <odoo> <data> - <!-- Assets --> - <template id="assets_backend" name="project assets le filament" inherit_id="web.assets_backend"> - <xpath expr="." position="inside"> - <link rel="stylesheet" href="/lefilament_projets/static/src/less/style.less"/> - - <script type="text/javascript" src="/lefilament_projets/static/src/js/widget.js"></script> - </xpath> - </template> - <!-- Project Form --> <record id="view_form_lefilament_project_form_inherited" model="ir.ui.view"> <field name="name">Le Filament Projets</field> <field name="model">project.project</field> <field name="inherit_id" ref="project.edit_project"/> <field name="arch" type="xml"> + <xpath expr="//sheet" position="before"> + <header> + <button string="Voir le Cash Flow" type="object" name="open_cash_flow" class="oe_highlight"/> + </header> + </xpath> <xpath expr="//notebook" position="before"> <group> <group> @@ -30,15 +26,26 @@ <field name="lf_heures_projet" /> </group> </group> + <group> + <group> + <field name="lf_total_days" /> + <field name="lf_balance" widget="monetary" options="{'currency_field': 'company_currency'}" /> + </group> + <group> + <field name="lf_delay" /> + <field name="lf_advance" widget="monetary" options="{'currency_field': 'company_currency'}" /> + </group> + </group> + <div class="clearfix"></div> </xpath> <xpath expr="//page" position="before"> <page string="Commentaires" name="commentaires"> <field name="lf_commentaire" /> </page> </xpath> - <xpath expr="//field[@widget='mail_followers']" position="after"> - <field name="message_ids" widget="mail_thread"/> - </xpath> + <xpath expr="//field[@widget='mail_followers']" position="after"> + <field name="message_ids" widget="mail_thread"/> + </xpath> </field> </record> @@ -107,8 +114,8 @@ </tr> </tbody></table> <table class="budget_table"><tbody><tr> - <td><a t-if="record.use_tasks.raw_value" name="%(project.act_project_project_2_project_task_all)d" type="action">Voir les tâches</a></td> - <td><a type="edit">Editer le projet</a></td> + <td><a t-if="record.use_tasks.raw_value" name="%(project.act_project_project_2_project_task_all)d" type="action">Les tâches</a></td> + <td><a type="edit">Le projet</a></td> </tr></tbody></table> </xpath> <!-- HIDE TASK AND DOCS NUMBER -->