diff --git a/LICENSE b/LICENSE index 7e5dad22c284347a94943bae488585d0f873592e..33aa54e42256094f481eb4f0de5dfb73e4b50bb5 100644 --- a/LICENSE +++ b/LICENSE @@ -201,30 +201,3 @@ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY C If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - 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 <https://www.gnu.org/licenses/>. -Also add information on how to contact you by electronic and paper mail. - -If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. - -You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>. \ No newline at end of file diff --git a/__init__.py b/__init__.py index 268881c69254a9d7537e4161d9686270f3941870..b44d765940f68ce76fabf4b0f835ae473a8236d8 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- -import models +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models diff --git a/__manifest__.py b/__manifest__.py index a57535077560a5d0e209d822db4ddea79c00801c..52f5bba8b9ae0929ad2d3c9bc55495a52f374856 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { 'name': "Le Filament - Tableau de Bord", @@ -8,27 +9,26 @@ 'description': """ Tableaux de Bord Le Filament - Affichage de tableaux de bord : + Affichage de tableaux de bord : - suivi annuel Factures/Commandes/Pipe et Trésorerie - tableau des performances mensuelles - graphe de la tésorerie sur 1 an glissant - prévisionnel de la trésorerie sur 6 mois - + """, 'author': "LE FILAMENT", 'category': 'dashboard', - 'website': "http://www.le-filament.com", - 'version': '10.0.1', + 'website': "https://le-filament.com", + 'version': '10.0.1.0.0', 'license': 'AGPL-3', - 'depends': ['crm','account','hr_expense'], + 'depends': ['account', 'crm', 'hr_expense', 'sale'], 'data': [ 'security/lefilament_dashboard_security.xml', 'security/ir.model.access.csv', 'views/assets.xml', 'views/views.xml', - # 'views/account_invoice.xml', 'views/schedule.xml', - 'data/ir_module_category.xml' + 'data/ir_module_category.xml' ], 'qweb': [ 'static/src/xml/*.xml', diff --git a/data/ir_module_category.xml b/data/ir_module_category.xml index c7e4878d0f7b96a0d9ae615862d14654e01b6274..f772ab7fa05bac72fc1aef63caa0f7d07472ffb1 100644 --- a/data/ir_module_category.xml +++ b/data/ir_module_category.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <odoo> <data> <record model="ir.module.category" id="module_category_dashboard"> diff --git a/models/__init__.py b/models/__init__.py index 5cc4332439536bffab0750336a46d6725b29edc5..3c981791e5d05a2529cc5f7e5560f606a620ba69 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -import lefilament_tdb -import res_company -import hr_employee -# import account_invoice +from . import lefilament_tdb +from . import res_company +from . import hr_employee diff --git a/models/hr_employee.py b/models/hr_employee.py index a1075b5a0ce8076d5b78476d6495bdcdbbc549c5..2af3611dc337bc1b7e37606d488028519ad4076d 100644 --- a/models/hr_employee.py +++ b/models/hr_employee.py @@ -5,8 +5,9 @@ from odoo import models, fields + class hr_employee(models.Model): - _name = "hr.employee" - _inherit = "hr.employee" + _name = "hr.employee" + _inherit = "hr.employee" - capital = fields.Float( "Apport Capital Social" ) + capital = fields.Float("Apport Capital Social") diff --git a/models/lefilament_tdb.py b/models/lefilament_tdb.py index 9b6c739b59f7a95b37eb8d4efe1e48bd5864154b..b27846bec812050f63d16f2a547695f521c1ff39 100644 --- a/models/lefilament_tdb.py +++ b/models/lefilament_tdb.py @@ -3,263 +3,433 @@ # © 2017 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from datetime import datetime, timedelta +from datetime import datetime from dateutil.relativedelta import relativedelta from odoo import models, fields, api -from odoo import tools from odoo.fields import Date class LeFilamentTdb(models.Model): - _name = "lefilament.dashboard" - _description = "Le Filament Dashboard" - _order = "date_tdb desc" - - name = fields.Char( 'Mois', compute='get_month') - date_tdb = fields.Date( 'Date', required=True, default=datetime.today().strftime('%Y-%m-%d'),) - - ca_mois = fields.Float( 'Facturé', compute="dashboard_values", store=True) - - cmd_mois = fields.Float( 'Commandes', compute="dashboard_values", store=True) - - pipe_mois = fields.Float( 'Pipe', compute="dashboard_values", store=True ) - - - treso = fields.Float( 'Trésorerie', compute="dashboard_values", store=True ) - variation = fields.Float( 'Variation', compute="dashboard_values", store=True ) - charges = fields.Float( 'Décaissé', compute="dashboard_values", store=True ) - encaisse = fields.Float( 'Encaissé', compute="dashboard_values", store=True ) - charges_fixes = fields.Float( 'Charges Fixes', default=10000 ) - runway = fields.Float( 'Runway', compute="runway_value", ) - - @api.multi - @api.depends('date_tdb') - def dashboard_values(self): - for record in self: - if record.date_tdb : - date_tdb = datetime.strptime(record.date_tdb, '%Y-%m-%d') - - ############## CA ################ - # FACTURÉ - self.env.cr.execute("select sum(amount_untaxed_signed) from account_invoice where state!='draft' and (type='out_invoice' or type='out_refund') and date >= date_trunc('month', %s) and date < date_trunc('month', %s + interval '1' month);", (date_tdb, date_tdb) ) - ca_mois = self.env.cr.fetchone()[0] - - ############## COMMANDES ################ - # TOTAL - self.env.cr.execute("select sum(amount_untaxed) from sale_order where invoice_status='to invoice' and date_order >= date_trunc('month', %s) and date_order < date_trunc('month', %s + interval '1' month);", (date_tdb, date_tdb) ) - cmd_mois = self.env.cr.fetchone()[0] - - ############## TRESO ################ - # Trésorerie - self.env.cr.execute("select sum(amount) from account_bank_statement_line where date < date_trunc('month', %s + interval '1' month);", (date_tdb, ) ) - treso_total = self.env.cr.fetchone()[0] - # CHARGES - self.env.cr.execute("select sum(amount) from account_bank_statement_line where amount < 0 and date >= date_trunc('month', %s) and date < date_trunc('month', %s + interval '1' month);", (date_tdb, date_tdb) ) - charges = self.env.cr.fetchone()[0] - # ENCAISSE - self.env.cr.execute("select sum(amount) from account_bank_statement_line where amount > 0 and date >= date_trunc('month', %s) and date < date_trunc('month', %s + interval '1' month);", (date_tdb, date_tdb) ) - encaisse = self.env.cr.fetchone()[0] - - ############## CHARGES ################ - self.env.cr.execute("select charges_fixes from res_company" ) - charges_fixes = self.env.cr.fetchone()[0] - - ############## PIPE ################ - # TOTAL - self.env.cr.execute("select sum(planned_revenue*probability/100) from crm_lead where active=True;") - pipe = self.env.cr.fetchone()[0] - - if not encaisse: - encaisse = 0 - if not charges: - charges = 0 - - record.ca_mois = ca_mois - record.cmd_mois = cmd_mois - record.treso = treso_total - record.charges = charges * (-1.0) - record.encaisse = encaisse - record.variation = encaisse + charges - record.charges_fixes = charges_fixes - record.pipe_mois = pipe - - - @api.multi - @api.depends('charges_fixes','treso') - def runway_value(self): - for record in self: - if record.charges_fixes : - record.runway = record.treso / record.charges_fixes - - @api.one - def get_month(self): - months = ['Janv', 'Fév', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sept', 'Oct', 'Nov', 'Dec'] - date_tdb = self.date_tdb - month = int(date_tdb[5:7]) - year = date_tdb[2:4] - self.name = months[month-1] + " " + year - - - @api.model - def new_data(self): - self.create({ 'date_tdb': str(datetime.now()) }) - - @api.model - def retrieve_datas_dashboard(self): - ## Get fiscal years - fiscal_date = datetime(datetime.now().year, self.env.user.company_id.fiscalyear_last_month, self.env.user.company_id.fiscalyear_last_day) - if datetime.now() > fiscal_date: - fiscal_year = "'" + Date.to_string(fiscal_date) + "'" - fiscal_year_next = "'" + Date.to_string(fiscal_date+relativedelta(years=1)) + "'" - else: - fiscal_year = "'" + Date.to_string(fiscal_date-relativedelta(years=1)) + "'" - fiscal_year_next = "'" + Date.to_string(fiscal_date) + "'" - - ## Prepare values - res = { - 'facture': 0, - 'commandes': 0, - 'pipe': 0, - 'pipe_win': 0, - 'pipe_to_win': 0, - 'pipe_n1': 0, - 'tresorerie': 0, - 'entree': 0, - 'sortie': 0, - 'variation': 0, - 'target': 0, - 'cca': 0, - 'capital': 0, - 'date_maj': 0, - 'a_encaisser': 0, - 'a_payer': 0, - 'fiscal_year': fiscal_year, - 'fiscal_year_next': fiscal_year_next, - } - - self._cr.execute(""" - SELECT - (select count(*) from account_invoice) as id, - (select sum(amount_untaxed_signed) from account_invoice where state!='draft' and (type='out_invoice' or type='out_refund') and date > %s and date <= %s ) as facture, - (select sum(residual_company_signed) from account_invoice where state!='draft' and type='out_invoice' ) as a_encaisser, - (select sum(residual_company_signed) from account_invoice where state!='draft' and type='in_invoice' ) as a_payer, - (select sum(planned_revenue*probability/100) from crm_lead where active=True and (date_deadline <= %s or date_deadline is null) ) as pipe, - (select sum(planned_revenue*probability/100) from crm_lead where active=True and date_deadline > %s ) as pipe_n1, - (select sum(planned_revenue*probability/100) from crm_lead where active=True and probability=100 and (date_deadline <= %s or date_deadline is null) ) as pipe_win, - (select sum(planned_revenue*probability/100) from crm_lead where active=True and probability!=100 and (date_deadline < %s or date_deadline is null) ) as pipe_to_win, - (select date from account_bank_statement ORDER BY ID DESC LIMIT 1) as date_maj, - (select sum(amount) from account_bank_statement_line ) as tresorerie, - (select sum(amount) from account_bank_statement_line where amount > 0 and date > %s ) as entree, - (select sum(amount) from account_bank_statement_line where amount < 0 and date > %s ) as sortie, - (select sum(amount) from account_bank_statement_line where date > %s ) as variation, - (select sum(e.total_amount) from hr_expense_sheet es, hr_expense e where es.id = e.sheet_id and e.payment_mode='own_account' and es.state!='done' ) as cca, - (select sum(price_subtotal-qty_invoiced*price_unit) from sale_order_line where invoice_status='to invoice') as commandes; """ - % (fiscal_year, fiscal_year_next, fiscal_year_next, fiscal_year_next, fiscal_year_next, fiscal_year_next, fiscal_year, fiscal_year, fiscal_year) ) - datas = self._cr.dictfetchall() - - self._cr.execute("select ca_target from res_company;") - ca_target = self._cr.dictfetchall() - - self._cr.execute("select sum(capital) as capital from hr_employee;") - capital = self._cr.dictfetchall() - - if datas[0]['facture']: - res['facture'] =+ datas[0]['facture'] - if datas[0]['a_encaisser']: - res['a_encaisser'] =+ datas[0]['a_encaisser'] - if datas[0]['a_payer']: - res['a_payer'] =+ datas[0]['a_payer'] - if datas[0]['pipe']: - res['pipe'] =+ datas[0]['pipe'] - if datas[0]['pipe_win']: - res['pipe_win'] =+ datas[0]['pipe_win'] - if datas[0]['pipe_to_win']: - res['pipe_to_win'] =+ datas[0]['pipe_to_win'] - if datas[0]['pipe_n1']: - res['pipe_n1'] =+ datas[0]['pipe_n1'] - if datas[0]['tresorerie']: - res['tresorerie'] =+ datas[0]['tresorerie'] - if datas[0]['date_maj']: - res['date_maj'] = datas[0]['date_maj'] - if datas[0]['entree']: - res['entree'] =+ datas[0]['entree'] - if datas[0]['sortie']: - res['sortie'] =+ datas[0]['sortie'] - if datas[0]['variation']: - res['variation'] =+ datas[0]['variation'] - if datas[0]['commandes']: - res['commandes'] =+ datas[0]['commandes'] - if datas[0]['cca']: - res['cca'] =+ datas[0]['cca'] - if ca_target[0]['ca_target']: - res['target'] =+ ca_target[0]['ca_target'] - if capital[0]['capital']: - res['capital'] =+ capital[0]['capital'] - - return res - - @api.model - def tresorerie(self): - self._cr.execute("""SELECT to_char(date_trunc('month', date),'YYYY-MM') as mois, - sum(case when amount > 0 then amount else 0 end ) as entree, - sum(case when amount < 0 then amount else 0 end ) as sortie, - sum(amount) as variation - from account_bank_statement_line - group by date_trunc('month', date) - order by date_trunc('month', date);""") - tresorerie = self._cr.dictfetchall() - - self._cr.execute("""SELECT - (select sum(e.total_amount) as fonds_propres from hr_expense_sheet es, hr_expense e where es.id = e.sheet_id and e.payment_mode='own_account' and es.state!='done') as cca, - (select sum(capital) as capital from hr_employee) as capital;""") - fonds_propres = self._cr.dictfetchall()[0] - - if not fonds_propres['cca']: - fonds_propres['cca'] = 0 - - return { 'tresorerie': tresorerie, 'fonds_propres': fonds_propres } - - @api.model - def previ_tresorerie(self): - self._cr.execute("""SELECT to_char(date_trunc('month', date),'YYYY-MM') as mois, sum(sum(amount)) - over ( order by date_trunc('month', date) ) as treso - from account_bank_statement_line - group by date_trunc('month', date) - order by date_trunc('month', date) desc limit 6;""") - tresorerie = self._cr.dictfetchall() - - self._cr.execute("""SELECT - (select sum(e.total_amount) as fonds_propres from hr_expense_sheet es, hr_expense e where es.id = e.sheet_id and e.payment_mode='own_account' and es.state!='done') as cca, - (select sum(capital) as capital from hr_employee) as capital;""") - fonds_propres = self._cr.dictfetchall()[0] - - self._cr.execute("""SELECT to_char(date_trunc('month', date_due),'YYYY-MM') as mois, - sum(case when type='in_invoice' then residual_company_signed else 0 end ) as f_fournisseur, - sum(case when type='out_invoice' then residual_company_signed else 0 end ) as f_client - from account_invoice - where state!='draft' and state!='paid' - group by date_trunc('month', date_due) - order by date_trunc('month', date_due);""") - factures = self._cr.dictfetchall() - - self._cr.execute("""SELECT periode, - sum(montant) - from previ_treso - where periode != 1 - group by periode - order by periode;""") - charges_periode = self._cr.dictfetchall() - - self._cr.execute("""SELECT to_char(date_trunc('month', date),'YYYY-MM') as mois, - sum(montant) - from previ_treso - where periode = 1 - group by date_trunc('month', date);""") - charges_fixes = self._cr.dictfetchall() - - if not fonds_propres['cca']: - fonds_propres['cca'] = 0 - - return { 'tresorerie': tresorerie, 'fonds_propres': fonds_propres, 'factures': factures, 'charges_fixes': charges_fixes, 'charges_periode':charges_periode, } + _name = "lefilament.dashboard" + _description = "Le Filament Dashboard" + _order = "date_tdb desc" + + name = fields.Char('Mois', compute='get_month') + date_tdb = fields.Date('Date', + required=True, + default=datetime.today().strftime('%Y-%m-%d'),) + ca_mois = fields.Float('Facturé', compute="dashboard_values", store=True) + cmd_mois = fields.Float('Commandes', + compute="dashboard_values", + store=True) + pipe_mois = fields.Float('Pipe', compute="dashboard_values", store=True) + treso = fields.Float('Trésorerie', compute="dashboard_values", store=True) + variation = fields.Float('Variation', + compute="dashboard_values", + store=True) + charges = fields.Float('Décaissé', compute="dashboard_values", store=True) + encaisse = fields.Float('Encaissé', compute="dashboard_values", store=True) + charges_fixes = fields.Float('Charges Fixes', default=10000) + runway = fields.Float('Runway', compute="runway_value") + + @api.multi + @api.depends('date_tdb') + def dashboard_values(self): + for record in self: + if record.date_tdb: + date_tdb = datetime.strptime(record.date_tdb, '%Y-%m-%d') + + # FACTURÉ + self.env.cr.execute( + "SELECT SUM(amount_untaxed_signed) \ + FROM account_invoice \ + WHERE state != 'draft' \ + AND (type = 'out_invoice' OR type = 'out_refund') \ + AND date >= date_trunc('month', %s) \ + AND date < date_trunc('month', %s + interval '1' month);", + (date_tdb, date_tdb) + ) + ca_mois = self.env.cr.fetchone()[0] + + # COMMANDES TOTAL + self.env.cr.execute( + "SELECT SUM(amount_untaxed) \ + FROM sale_order \ + WHERE invoice_status = 'to invoice' \ + AND date_order >= date_trunc('month', %s) \ + AND date_order < date_trunc('month', %s \ + + interval '1' month);", + (date_tdb, date_tdb) + ) + cmd_mois = self.env.cr.fetchone()[0] + + # Trésorerie + self.env.cr.execute( + "SELECT SUM(amount) \ + FROM account_bank_statement_line \ + WHERE date < date_trunc('month', %s \ + + interval '1' month);", + (date_tdb) + ) + treso_total = self.env.cr.fetchone()[0] + # CHARGES + self.env.cr.execute( + "SELECT SUM(amount) \ + FROM account_bank_statement_line \ + WHERE amount < 0 \ + AND date >= date_trunc('month', %s) \ + AND date < date_trunc('month', %s + interval '1' month);", + (date_tdb, date_tdb) + ) + charges = self.env.cr.fetchone()[0] + # ENCAISSE + self.env.cr.execute( + "SELECT SUM(amount) \ + FROM account_bank_statement_line \ + WHERE amount > 0 \ + AND date >= date_trunc('month', %s) \ + AND date < date_trunc('month', %s + interval '1' month);", + (date_tdb, date_tdb) + ) + encaisse = self.env.cr.fetchone()[0] + + # CHARGES FIXES + self.env.cr.execute( + "SELECT charges_fixes \ + FROM res_company" + ) + charges_fixes = self.env.cr.fetchone()[0] + + # PIPE + self.env.cr.execute( + "SELECT SUM(planned_revenue * probability / 100) \ + FROM crm_lead \ + WHERE active = True" + ) + pipe = self.env.cr.fetchone()[0] + + if not encaisse: + encaisse = 0 + if not charges: + charges = 0 + + record.ca_mois = ca_mois + record.cmd_mois = cmd_mois + record.treso = treso_total + record.charges = charges * (-1.0) + record.encaisse = encaisse + record.variation = encaisse + charges + record.charges_fixes = charges_fixes + record.pipe_mois = pipe + + @api.multi + @api.depends('charges_fixes', 'treso') + def runway_value(self): + for record in self: + if record.charges_fixes: + record.runway = record.treso / record.charges_fixes + + @api.multi + @api.depends('date_tdb') + def get_month(self): + for record in self: + months = ['Janv', + 'Fév', + 'Mars', + 'Avr', + 'Mai', + 'Juin', + 'Juil', + 'Août', + 'Sept', + 'Oct', + 'Nov', + 'Dec'] + date_tdb = record.date_tdb + month = int(date_tdb[5:7]) + year = date_tdb[2:4] + record.name = months[month-1] + " " + year + + @api.model + def new_data(self): + self.create({'date_tdb': str(datetime.now())}) + + @api.model + def retrieve_datas_dashboard(self): + # Get fiscal years + fiscal_date = datetime( + datetime.now().year, + self.env.user.company_id.fiscalyear_last_month, + self.env.user.company_id.fiscalyear_last_day + ) + if datetime.now() > fiscal_date: + fiscal_year = "'" + Date.to_string(fiscal_date) + "'" + fiscal_year_next = "'" + Date.to_string( + fiscal_date + relativedelta(years=1)) + "'" + else: + fiscal_year = "'" + Date.to_string(fiscal_date - relativedelta( + years=1)) + "'" + fiscal_year_next = "'" + Date.to_string(fiscal_date) + "'" + + # Prepare values + res = { + 'facture': 0, + 'commandes': 0, + 'pipe': 0, + 'pipe_win': 0, + 'pipe_to_win': 0, + 'pipe_n1': 0, + 'tresorerie': 0, + 'entree': 0, + 'sortie': 0, + 'variation': 0, + 'target': 0, + 'cca': 0, + 'capital': 0, + 'date_maj': 0, + 'a_encaisser': 0, + 'a_payer': 0, + 'fiscal_year': fiscal_year, + 'fiscal_year_next': fiscal_year_next, + } + + self._cr.execute( + """ + SELECT + (SELECT COUNT(*) + FROM account_invoice + ) AS id, + (SELECT SUM(amount_untaxed_signed) + FROM account_invoice + WHERE state!='draft' + AND (type='out_invoice' OR type='out_refund') + AND date > %s AND date <= %s + ) AS facture, + (SELECT SUM(residual_company_signed) + FROM account_invoice + WHERE state!='draft' AND type='out_invoice' + ) AS a_encaisser, + (SELECT SUM(residual_company_signed) + FROM account_invoice + WHERE state!='draft' AND type='in_invoice' + ) AS a_payer, + (SELECT SUM(planned_revenue * probability / 100) + FROM crm_lead + WHERE active=True + AND (date_deadline <= %s OR date_deadline is NULL) + ) AS pipe, + (SELECT SUM(planned_revenue * probability / 100) + FROM crm_lead WHERE active=True AND date_deadline > %s + ) AS pipe_n1, + (SELECT SUM(planned_revenue * probability / 100) + FROM crm_lead + WHERE active=True + AND probability = 100 + AND (date_deadline <= %s OR date_deadline is NULL) + ) AS pipe_win, + (SELECT SUM(planned_revenue * probability / 100) + FROM crm_lead + WHERE active=True + AND probability != 100 + AND (date_deadline < %s OR date_deadline is NULL) + ) AS pipe_to_win, + (SELECT date + FROM account_bank_statement + ORDER BY ID DESC LIMIT 1 + ) AS date_maj, + (SELECT SUM(amount) + FROM account_bank_statement_line + ) AS tresorerie, + (SELECT SUM(amount) + FROM account_bank_statement_line + WHERE amount > 0 AND date > %s + ) AS entree, + (SELECT SUM(amount) + FROM account_bank_statement_line + WHERE amount < 0 AND date > %s + ) AS sortie, + (SELECT SUM(amount) + FROM account_bank_statement_line + WHERE date > %s + ) AS variation, + (SELECT SUM(e.total_amount) + FROM hr_expense_sheet es, hr_expense e + WHERE es.id = e.sheet_id + AND e.payment_mode = 'own_account' + AND es.state != 'done' + ) AS cca, + (SELECT SUM(price_subtotal - qty_invoiced * price_unit) + FROM sale_order_line + WHERE invoice_status = 'to invoice' + ) AS commandes; + """ + % (fiscal_year, + fiscal_year_next, + fiscal_year_next, + fiscal_year_next, + fiscal_year_next, + fiscal_year_next, + fiscal_year, + fiscal_year, + fiscal_year) + ) + datas = self._cr.dictfetchall() + + self._cr.execute("SELECT ca_target FROM res_company;") + ca_target = self._cr.dictfetchall() + + self._cr.execute("SELECT sum(capital) AS capital FROM hr_employee;") + capital = self._cr.dictfetchall() + + if datas[0]['facture']: + res['facture'] += datas[0]['facture'] + if datas[0]['a_encaisser']: + res['a_encaisser'] += datas[0]['a_encaisser'] + if datas[0]['a_payer']: + res['a_payer'] += datas[0]['a_payer'] + if datas[0]['pipe']: + res['pipe'] += datas[0]['pipe'] + if datas[0]['pipe_win']: + res['pipe_win'] += datas[0]['pipe_win'] + if datas[0]['pipe_to_win']: + res['pipe_to_win'] += datas[0]['pipe_to_win'] + if datas[0]['pipe_n1']: + res['pipe_n1'] += datas[0]['pipe_n1'] + if datas[0]['tresorerie']: + res['tresorerie'] += datas[0]['tresorerie'] + if datas[0]['date_maj']: + res['date_maj'] = datas[0]['date_maj'] + if datas[0]['entree']: + res['entree'] += datas[0]['entree'] + if datas[0]['sortie']: + res['sortie'] += datas[0]['sortie'] + if datas[0]['variation']: + res['variation'] += datas[0]['variation'] + if datas[0]['commandes']: + res['commandes'] += datas[0]['commandes'] + if datas[0]['cca']: + res['cca'] += datas[0]['cca'] + if ca_target[0]['ca_target']: + res['target'] += ca_target[0]['ca_target'] + if capital[0]['capital']: + res['capital'] += capital[0]['capital'] + + return res + + @api.model + def tresorerie(self): + self._cr.execute( + """ + SELECT to_char(date_trunc('month', date),'YYYY-MM') AS mois, + SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) AS entree, + SUM(CASE WHEN amount < 0 THEN amount ELSE 0 END) AS sortie, + SUM(amount) AS variation + FROM account_bank_statement_line + GROUP BY date_trunc('month', date) + ORDER BY date_trunc('month', date); + """ + ) + tresorerie = self._cr.dictfetchall() + + self._cr.execute( + """ + SELECT + (SELECT SUM(e.total_amount) AS fonds_propres + FROM hr_expense_sheet es, hr_expense e + WHERE es.id = e.sheet_id + AND e.payment_mode = 'own_account' + AND es.state != 'done' + ) AS cca, + (SELECT SUM(capital) AS capital FROM hr_employee) AS capital; + """ + ) + fonds_propres = self._cr.dictfetchall()[0] + + if not fonds_propres['cca']: + fonds_propres['cca'] = 0 + + return {'tresorerie': tresorerie, 'fonds_propres': fonds_propres} + + @api.model + def previ_tresorerie(self): + self._cr.execute( + """ + SELECT to_char(date_trunc('month', date),'YYYY-MM') AS mois, + SUM(SUM(amount)) OVER ( + ORDER BY date_trunc('month', date) + ) AS treso + FROM account_bank_statement_line + GROUP BY date_trunc('month', date) + ORDER BY date_trunc('month', date) DESC LIMIT 6; + """ + ) + tresorerie = self._cr.dictfetchall() + + self._cr.execute( + """ + SELECT + (SELECT SUM(e.total_amount) AS fonds_propres + FROM hr_expense_sheet es, hr_expense e + WHERE es.id = e.sheet_id + AND e.payment_mode = 'own_account' + AND es.state != 'done' + ) as cca, + (SELECT SUM(capital) AS capital FROM hr_employee) AS capital; + """ + ) + fonds_propres = self._cr.dictfetchall()[0] + + self._cr.execute( + """ + SELECT to_char(date_trunc('month', date_due),'YYYY-MM') AS mois, + SUM(CASE WHEN type = 'in_invoice' + THEN residual_company_signed + ELSE 0 END + ) AS f_fournisseur, + SUM(CASE WHEN type = 'out_invoice' + THEN residual_company_signed + ELSE 0 END + ) AS f_client + FROM account_invoice + WHERE state != 'draft' AND state != 'paid' + GROUP BY date_trunc('month', date_due) + ORDER BY date_trunc('month', date_due); + """ + ) + factures = self._cr.dictfetchall() + + self._cr.execute( + """ + SELECT periode, + SUM(montant) + FROM previ_treso + WHERE periode != 1 + GROUP BY periode + ORDER BY periode; + """ + ) + charges_periode = self._cr.dictfetchall() + + self._cr.execute( + """ + SELECT to_char(date_trunc('month', date), 'YYYY-MM') as mois, + SUM(montant) + FROM previ_treso + WHERE periode = 1 + GROUP BY date_trunc('month', date); + """ + ) + charges_fixes = self._cr.dictfetchall() + + if not fonds_propres['cca']: + fonds_propres['cca'] = 0 + + return { + 'tresorerie': tresorerie, + 'fonds_propres': fonds_propres, + 'factures': factures, + 'charges_fixes': charges_fixes, + 'charges_periode': charges_periode, + } diff --git a/models/res_company.py b/models/res_company.py index 5ae5e39362ad54516a723a313a1e6e9c08150ef2..74128d93d74ebc546963d23f7aef33bc42b843d1 100644 --- a/models/res_company.py +++ b/models/res_company.py @@ -1,27 +1,33 @@ # -*- coding: utf-8 -*- - # © 2017 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api +from odoo import models, fields -class res_company(models.Model): - _name = "res.company" - _inherit = "res.company" - ca_target = fields.Integer( "Objectif Chiffre d'Affaire" ) - charges_fixes = fields.Integer( 'Charges Fixes' ) - previ_treso_ids = fields.One2many('previ.treso', 'company_id', 'Prévisionnel') +class res_company(models.Model): + _inherit = "res.company" + ca_target = fields.Integer("Objectif Chiffre d'Affaire") + charges_fixes = fields.Integer('Charges Fixes') + previ_treso_ids = fields.One2many('previ.treso', + 'company_id', + 'Prévisionnel') class previ_treso(models.Model): - _name = "previ.treso" - _description = "Previsionnel de tresorerie" - _order = 'name' - - company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.user.company_id.id ) - name = fields.Char('Nom') - periode = fields.Selection([(12,'Mensuel'),(3,'Trimestriel'),(1,'Annuel')], srting='Période') - date = fields.Date('Date') - montant = fields.Float('Montant') \ No newline at end of file + _name = "previ.treso" + _description = "Previsionnel de tresorerie" + _order = 'name' + + company_id = fields.Many2one( + 'res.company', + 'Company', + default=lambda self: self.env.user.company_id.id) + name = fields.Char('Nom') + periode = fields.Selection( + [(12, 'Mensuel'), (3, 'Trimestriel'), (1, 'Annuel')], + srting='Période' + ) + date = fields.Date('Date') + montant = fields.Float('Montant') diff --git a/security/lefilament_dashboard_security.xml b/security/lefilament_dashboard_security.xml index de335fffcfe9ede3995424a208c1f82a0eadc3f7..5aeff45c677a4d77e9e7984ec1e4b48ab1ae3a0d 100644 --- a/security/lefilament_dashboard_security.xml +++ b/security/lefilament_dashboard_security.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <odoo> <data noupdate="0"> diff --git a/static/description/icon_menu.png b/static/description/icon_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..6591b91a04ae5274382d55080290ba0d41372825 Binary files /dev/null and b/static/description/icon_menu.png differ diff --git a/static/description/icon_menu.svg b/static/description/icon_menu.svg new file mode 100644 index 0000000000000000000000000000000000000000..10e163bafb581011598e647f3abc1512898bebe4 --- /dev/null +++ b/static/description/icon_menu.svg @@ -0,0 +1,23 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"> + <defs> + <path id="icon-a" d="M4,5.35309892e-14 C36.4160122,9.87060235e-15 58.0836068,-3.97961823e-14 65,5.07020818e-14 C69,6.733808e-14 70,1 70,5 C70,43.0488877 70,62.4235458 70,65 C70,69 69,70 65,70 C61,70 9,70 4,70 C1,70 7.10542736e-15,69 7.10542736e-15,65 C7.25721566e-15,62.4676575 3.83358709e-14,41.8005206 3.60818146e-14,5 C-1.13686838e-13,1 1,5.75716207e-14 4,5.35309892e-14 Z"/> + <linearGradient id="icon-c" x1="100%" x2="0%" y1="0%" y2="100%"> + <stop offset="0%" stop-color="#36DABE"/> + <stop offset="100%" stop-color="#00B495"/> + </linearGradient> + <path id="icon-d" d="M18.0450069,57.225 C16.6239398,57.2249541 15.319401,56.4292666 14.6550625,55.1573449 C12.9601701,51.9125391 12,48.2137078 12,44.2875 C12,31.4261695 22.2974514,21 35,21 C47.7025486,21 58,31.4261695 58,44.2875 C58,48.2137078 57.0398299,51.9125391 55.3449375,55.1573449 C54.6806259,56.4292924 53.3760701,57.2249902 51.9549931,57.225 L18.0450069,57.225 Z M52.8888889,41.7 C51.4775035,41.7 50.3333333,42.8584723 50.3333333,44.2875 C50.3333333,45.7165277 51.4775035,46.875 52.8888889,46.875 C54.3002743,46.875 55.4444444,45.7165277 55.4444444,44.2875 C55.4444444,42.8584723 54.3002743,41.7 52.8888889,41.7 Z M35,28.7625 C36.4113854,28.7625 37.5555556,27.6040277 37.5555556,26.175 C37.5555556,24.7459723 36.4113854,23.5875 35,23.5875 C33.5886146,23.5875 32.4444444,24.7459723 32.4444444,26.175 C32.4444444,27.6040277 33.5886146,28.7625 35,28.7625 Z M17.1111111,41.7 C15.6997257,41.7 14.5555556,42.8584723 14.5555556,44.2875 C14.5555556,45.7165277 15.6997257,46.875 17.1111111,46.875 C18.5224965,46.875 19.6666667,45.7165277 19.6666667,44.2875 C19.6666667,42.8584723 18.5224965,41.7 17.1111111,41.7 Z M22.3506389,28.8925219 C20.9392535,28.8925219 19.7950833,30.0509941 19.7950833,31.4800219 C19.7950833,32.9090496 20.9392535,34.0675219 22.3506389,34.0675219 C23.7620243,34.0675219 24.9061944,32.9090496 24.9061944,31.4800219 C24.9061944,30.0509941 23.7620243,28.8925219 22.3506389,28.8925219 Z M47.6493611,28.8925219 C46.2379757,28.8925219 45.0938056,30.0509941 45.0938056,31.4800219 C45.0938056,32.9090496 46.2379757,34.0675219 47.6493611,34.0675219 C49.0607465,34.0675219 50.2049167,32.9090496 50.2049167,31.4800219 C50.2049167,30.0509941 49.0607465,28.8925219 47.6493611,28.8925219 Z M40.6952153,31.4423414 C39.686809,31.1156695 38.6082049,31.6784508 38.285566,32.6992195 L34.6181042,44.3034293 C31.9739028,44.501373 29.8888889,46.7346281 29.8888889,49.4625 C29.8888889,52.3205555 32.1772292,54.6375 35,54.6375 C37.8227708,54.6375 40.1111111,52.3205555 40.1111111,49.4625 C40.1111111,47.8636676 39.3946771,46.434559 38.269434,45.4852699 L41.9365764,33.8821113 C42.2591354,32.8612617 41.7033819,31.7690133 40.6952153,31.4423414 Z"/> + </defs> + <g fill="none" fill-rule="evenodd"> + <mask id="icon-b" fill="#fff"> + <use xlink:href="#icon-a"/> + </mask> + <g mask="url(#icon-b)"> + <rect width="70" height="70" fill="url(#icon-c)"/> + <path fill="#FFF" fill-opacity=".383" d="M4,1.8 L65,1.8 C67.6666667,1.8 69.3333333,1.13333333 70,-0.2 C70,2.46666667 70,3.46666667 70,2.8 L1.10547097e-14,2.8 C-1.65952376e-14,3.46666667 -2.9161925e-14,2.46666667 -2.66453526e-14,-0.2 C0.666666667,1.13333333 2,1.8 4,1.8 Z" transform="matrix(1 0 0 -1 0 2.8)"/> + <path fill="#393939" d="M4,50 C2,50 -7.10542736e-15,49.851312 0,45.8367347 L0,26.3942795 L16.3536575,8.86200565 C29.4512192,-0.488174988 39.6666667,-2.3877551 47,3.16326531 C54.3333333,8.71428571 58,14.9591837 58,21.8979592 C55.8677728,29.7827578 54.7719047,33.7755585 54.7123959,33.8763613 C54.6528871,33.9771642 49.9857922,39.3517104 40.7111111,50 L4,50 Z" opacity=".324" transform="translate(0 20)"/> + <path fill="#000" fill-opacity=".383" d="M4,4 L65,4 C67.6666667,4 69.3333333,3 70,1 C70,3.66666667 70,5 70,5 L1.77635684e-15,5 C1.77635684e-15,5 1.77635684e-15,3.66666667 1.77635684e-15,1 C0.666666667,3 2,4 4,4 Z" transform="translate(0 65)"/> + <use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#icon-d"/> + <path fill="#FFF" fill-rule="nonzero" d="M18.0450069,55.225 C16.6239398,55.2249541 15.319401,54.4292666 14.6550625,53.1573449 C12.9601701,49.9125391 12,46.2137078 12,42.2875 C12,29.4261695 22.2974514,19 35,19 C47.7025486,19 58,29.4261695 58,42.2875 C58,46.2137078 57.0398299,49.9125391 55.3449375,53.1573449 C54.6806259,54.4292924 53.3760701,55.2249902 51.9549931,55.225 L18.0450069,55.225 Z M52.8888889,39.7 C51.4775035,39.7 50.3333333,40.8584723 50.3333333,42.2875 C50.3333333,43.7165277 51.4775035,44.875 52.8888889,44.875 C54.3002743,44.875 55.4444444,43.7165277 55.4444444,42.2875 C55.4444444,40.8584723 54.3002743,39.7 52.8888889,39.7 Z M35,26.7625 C36.4113854,26.7625 37.5555556,25.6040277 37.5555556,24.175 C37.5555556,22.7459723 36.4113854,21.5875 35,21.5875 C33.5886146,21.5875 32.4444444,22.7459723 32.4444444,24.175 C32.4444444,25.6040277 33.5886146,26.7625 35,26.7625 Z M17.1111111,39.7 C15.6997257,39.7 14.5555556,40.8584723 14.5555556,42.2875 C14.5555556,43.7165277 15.6997257,44.875 17.1111111,44.875 C18.5224965,44.875 19.6666667,43.7165277 19.6666667,42.2875 C19.6666667,40.8584723 18.5224965,39.7 17.1111111,39.7 Z M22.3506389,26.8925219 C20.9392535,26.8925219 19.7950833,28.0509941 19.7950833,29.4800219 C19.7950833,30.9090496 20.9392535,32.0675219 22.3506389,32.0675219 C23.7620243,32.0675219 24.9061944,30.9090496 24.9061944,29.4800219 C24.9061944,28.0509941 23.7620243,26.8925219 22.3506389,26.8925219 Z M47.6493611,26.8925219 C46.2379757,26.8925219 45.0938056,28.0509941 45.0938056,29.4800219 C45.0938056,30.9090496 46.2379757,32.0675219 47.6493611,32.0675219 C49.0607465,32.0675219 50.2049167,30.9090496 50.2049167,29.4800219 C50.2049167,28.0509941 49.0607465,26.8925219 47.6493611,26.8925219 Z M40.6952153,29.4423414 C39.686809,29.1156695 38.6082049,29.6784508 38.285566,30.6992195 L34.6181042,42.3034293 C31.9739028,42.501373 29.8888889,44.7346281 29.8888889,47.4625 C29.8888889,50.3205555 32.1772292,52.6375 35,52.6375 C37.8227708,52.6375 40.1111111,50.3205555 40.1111111,47.4625 C40.1111111,45.8636676 39.3946771,44.434559 38.269434,43.4852699 L41.9365764,31.8821113 C42.2591354,30.8612617 41.7033819,29.7690133 40.6952153,29.4423414 Z"/> + </g> + </g> +</svg> diff --git a/static/src/js/dashboard_year.js b/static/src/js/dashboard_year.js index af8c52aa9b7138a8efb8782cdd52242a4c6579c0..e1b1c7ad10ee17cf154548fa852911ccda21b33c 100644 --- a/static/src/js/dashboard_year.js +++ b/static/src/js/dashboard_year.js @@ -1,4 +1,3 @@ - // © 2017 Le Filament (<http://www.le-filament.com>) // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/static/src/js/tresorerie.js b/static/src/js/tresorerie.js index 7b563c5d3f75189b3f243a43c6900544418875b0..eddcb39db43fce811a9042141beea1fa59d3944e 100644 --- a/static/src/js/tresorerie.js +++ b/static/src/js/tresorerie.js @@ -1,4 +1,3 @@ - // © 2017 Le Filament (<http://www.le-filament.com>) // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/static/src/xml/lefilament_tdb.xml b/static/src/xml/lefilament_tdb.xml index 1a32d32faa4deb1e5af6356f7f8a293381ea40a7..c4e9486b284887f028dfdbbdae39103ed147ef56 100644 --- a/static/src/xml/lefilament_tdb.xml +++ b/static/src/xml/lefilament_tdb.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <templates xml:space="preserve"> <t t-name="Dashboard"> diff --git a/static/src/xml/lefilament_treso.xml b/static/src/xml/lefilament_treso.xml index 936d14ca5e7342831b2976168735a2be4f21d817..3f16f1aceecf0789fc2aa047f821a05c14750e68 100644 --- a/static/src/xml/lefilament_treso.xml +++ b/static/src/xml/lefilament_treso.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <templates xml:space="preserve"> diff --git a/views/assets.xml b/views/assets.xml index 6355aa047fb6e7b1b819a901a1be8dad2c53a67a..d4363a14a2955aef4b4480af3f6bcfedf1df9caf 100644 --- a/views/assets.xml +++ b/views/assets.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <odoo> <data> diff --git a/views/schedule.xml b/views/schedule.xml index 748bcb5db9521e7139bd2cf444e5b887a7a267da..6afdedd58674616711870ac73862069030a6036e 100644 --- a/views/schedule.xml +++ b/views/schedule.xml @@ -1,6 +1,8 @@ <odoo> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <data noupdate="1"> - <record id="ir_cron_sapova_tdb" model="ir.cron"> + <record id="ir_cron_lefilament_tdb" model="ir.cron"> <field name="name">Tableau de Bord - Le Filament</field> <field name="active" eval="True" /> <field name="user_id" ref="base.user_root" /> diff --git a/views/views.xml b/views/views.xml index 7b3ee335dad9b31c569e164ba2b8d72db6eb88ca..00250c64d2ef769d7984923d239d57c721e57d9a 100644 --- a/views/views.xml +++ b/views/views.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 Le Filament (<https://www.le-filament.com>) + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <odoo> <data>