diff --git a/LICENSE b/LICENSE index 7e5dad22c284347a94943bae488585d0f873592e..fb0e255fe2e1ed8df758e690072ccc04477c7d89 100644 --- a/LICENSE +++ b/LICENSE @@ -200,31 +200,4 @@ 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 +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/__init__.py b/__init__.py index a5e7e1d4f02807b492d1626a2f84c5ba769474b3..b44d765940f68ce76fabf4b0f835ae473a8236d8 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -import models \ No newline at end of file +from . import models diff --git a/__manifest__.py b/__manifest__.py index 7abf96fbf3474c99a857a310cc9a7a48d798f287..c623a785621708d5ef76be2644b004340f158335 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -1,35 +1,37 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'Le Filament - Cash Flow project', + 'name': 'Le Filament - Cash Flow project', - 'summary': """ + 'summary': """ Cash Flow project Le Filament""", - 'version': '10.0.1.0', - 'license': 'AGPL-3', - 'description': """ - - Module Cash Flow Project Le Filament + 'version': '10.0.1.0.0', + 'license': 'AGPL-3', + 'description': """ - This module depends upon *lefilament_projets* module. + Module Cash Flow Project Le Filament - This module provides: - - the project cash flow graph - """, - - 'author': 'LE FILAMENT', - 'category': 'Project', - 'depends': ['lefilament_projets'], - 'contributors': [ + This module depends upon *lefilament_projets* module. + + This module provides: + - the project cash flow graph + """, + + 'author': 'LE FILAMENT', + 'category': 'Project', + 'depends': ['lefilament_projets'], + 'contributors': [ 'Benjamin Rivier <benjamin@le-filament.com>', - 'Rémi Cazenave <remi@le-filament.com>', - 'Juliana Poudou <juliana@le-filament.com>', + 'Rémi Cazenave <remi@le-filament.com>', + 'Juliana Poudou <juliana@le-filament.com>', + ], + 'website': 'https://le-filament.com', + 'data': [ + 'views/assets.xml', + 'views/lefilament_projets_view.xml', ], - 'website': 'https://le-filament.com', - 'data': [ - 'views/assets.xml', - 'views/lefilament_projets_view.xml', - ], - 'qweb': [ - 'static/src/xml/*.xml', + 'qweb': [ + 'static/src/xml/*.xml', ], } diff --git a/models/__init__.py b/models/__init__.py index ba241314a7658d90c91090ae2d2297abc4be1aca..c75f263bf4d7dc60ebe0e337bb5864beb92d5fe9 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -import project \ No newline at end of file +from . import project diff --git a/models/project.py b/models/project.py index 472f86daeee280f097977b1fadb6172fdbc75993..20b2c65361f6e998ebfc14233d707ebb721973bc 100644 --- a/models/project.py +++ b/models/project.py @@ -3,10 +3,9 @@ # © 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, date -import time -from odoo import tools -from odoo import models, fields, api, osv +from datetime import datetime +from odoo import models, fields, api + class FilamentProjetCashFlow(models.Model): _inherit = 'project.project' @@ -16,63 +15,94 @@ class FilamentProjetCashFlow(models.Model): lf_advance = fields.Float('Advanced cash', compute='_advance') lf_balance = fields.Float('Current balance', compute='_balance') - ## Time to first invoice + # Time to first invoice @api.one def _delay(self): account_id = self.analytic_account_id.id - prospection_tasks = self.env['project.task'].search([('name','=ilike','Prospection'+'%'),('project_id','=',self.id)]).ids + prospection_tasks = self.env['project.task'].search( + [('name', '=ilike', 'Prospection'+'%'), + ('project_id', '=', self.id)]).ids if not prospection_tasks: self.env.cr.execute(""" - SELECT date - FROM account_analytic_line - where account_id=%s and project_id is not null order by date - ;""", (account_id, ) ) + SELECT date + FROM account_analytic_line + WHERE account_id=%s AND project_id IS NOT NULL + ORDER BY date + ;""", (account_id, )) else: self.env.cr.execute(""" - SELECT date - FROM account_analytic_line - where account_id=%s and (task_id not in %s or task_id is null) and project_id is not null order by date - ;""", (account_id, tuple(prospection_tasks), ) ) - + SELECT date + FROM account_analytic_line + WHERE account_id=%s AND (task_id not IN %s OR task_id IS NULL) + AND project_id IS NOT NULL + ORDER BY date + ;""", (account_id, tuple(prospection_tasks), )) + d_cost = self.env.cr.fetchall() self.env.cr.execute(""" - SELECT date - FROM account_analytic_line - where account_id=%s and project_id is null and ref is null order by date limit 1 - ;""", (account_id, ) ) + SELECT date + FROM account_analytic_line + WHERE account_id=%s AND project_id IS NULL AND ref IS NULL + ORDER BY date LIMIT 1 + ;""", (account_id, )) d_invoice = self.env.cr.fetchone() if d_cost: if d_invoice: - self.lf_delay = (datetime.strptime(d_invoice[0], "%Y-%m-%d").date() - datetime.strptime(d_cost[0][0], "%Y-%m-%d").date()).days + self.lf_delay = ( + datetime.strptime(d_invoice[0], "%Y-%m-%d").date() + - datetime.strptime(d_cost[0][0], "%Y-%m-%d").date() + ).days else: - self.lf_delay = (datetime.strptime(d_cost[-1][0], "%Y-%m-%d").date() - datetime.strptime(d_cost[0][0], "%Y-%m-%d").date()).days + self.lf_delay = ( + datetime.strptime(d_cost[-1][0], "%Y-%m-%d").date() + - datetime.strptime(d_cost[0][0], "%Y-%m-%d").date() + ).days else: self.lf_delay = 0 - ## Total Days + # Total Days @api.one def _total_days(self): account_id = self.analytic_account_id.id - prospection_tasks = self.env['project.task'].search([('name','=ilike','Prospection'+'%'),('project_id','=',self.id)]).ids + prospection_tasks = self.env['project.task'].search( + [('name', '=ilike', 'Prospection'+'%'), + ('project_id', '=', self.id)] + ).ids if not prospection_tasks: self.env.cr.execute(""" - SELECT - date(d_last) - date(d_first) as datediff - from - (select date FROM account_analytic_line where account_id=%s and project_id is not null order by date limit 1) as d_first, - (select date FROM account_analytic_line where account_id=%s and project_id is not null order by date desc limit 1) as d_last - ;""", (account_id, account_id, ) ) + SELECT date(d_last) - date(d_first) AS datediff + FROM + (SELECT date + FROM account_analytic_line + WHERE account_id=%s AND project_id IS NOT NULL + ORDER BY date LIMIT 1) AS d_first, + (SELECT date + FROM account_analytic_line + WHERE account_id=%s AND project_id IS NOT NULL + ORDER BY date DESC LIMIT 1) AS d_last + ;""", (account_id, account_id, )) else: - self.env.cr.execute(""" - SELECT - date(d_last) - date(d_first) as datediff - from - (select date FROM account_analytic_line where account_id=%s and (task_id not in %s or task_id is null) and project_id is not null order by date limit 1) as d_first, - (select date FROM account_analytic_line where account_id=%s and (task_id not in %s or task_id is null) and project_id is not null order by date desc limit 1) as d_last - ;""", (account_id, tuple(prospection_tasks), account_id, tuple(prospection_tasks), ) ) - + self.env.cr.execute( + """ + SELECT date(d_last) - date(d_first) AS datediff + FROM + (SELECT date FROM account_analytic_line + WHERE account_id=%s + AND (task_id not IN %s OR task_id IS NULL) + AND project_id IS NOT NULL + ORDER BY date LIMIT 1) AS d_first, + (SELECT date FROM account_analytic_line + WHERE account_id=%s + AND (task_id not IN %s OR task_id IS NULL) + AND project_id IS NOT NULL + ORDER BY date DESC LIMIT 1) AS d_last + ;""", + (account_id, tuple(prospection_tasks), + account_id, tuple(prospection_tasks), ) + ) + lf_total_days = self._cr.fetchone() if lf_total_days: @@ -80,57 +110,87 @@ class FilamentProjetCashFlow(models.Model): else: self.lf_total_days = 0 - ## Balance + # Balance @api.one def _balance(self): account_id = self.analytic_account_id.id - prospection_tasks = self.env['project.task'].search([('name','=ilike','Prospection'+'%'),('project_id','=',self.id)]).ids + prospection_tasks = self.env['project.task'].search( + [('name', '=ilike', 'Prospection'+'%'), + ('project_id', '=', self.id)]).ids if not prospection_tasks: self.env.cr.execute(""" - SELECT sum(amount) as Total + SELECT SUM(amount) AS Total FROM account_analytic_line WHERE account_id=%s - ;""", (account_id, ) ) + ;""", (account_id, )) self.lf_balance = self._cr.fetchone()[0] else: self.env.cr.execute(""" - SELECT sum(amount) as Total + SELECT SUM(amount) AS Total FROM account_analytic_line - WHERE account_id=%s and (task_id not in %s or task_id is null) - ;""", (account_id, tuple(prospection_tasks), ) ) + WHERE account_id=%s AND (task_id not IN %s OR task_id IS NULL) + ;""", (account_id, tuple(prospection_tasks), )) self.lf_balance = self._cr.fetchone()[0] - ## Advance cash + # Advance cash @api.one def _advance(self): account_id = self.analytic_account_id.id - prospection_tasks = self.env['project.task'].search([('name','=ilike','Prospection'+'%'),('project_id','=',self.id)]).ids + prospection_tasks = self.env['project.task'].search( + [('name', '=ilike', 'Prospection'+'%'), + ('project_id', '=', self.id)]).ids if not prospection_tasks: - self.env.cr.execute(""" - SELECT sum(amount) as Total + self.env.cr.execute( + """ + SELECT SUM(amount) AS Total FROM account_analytic_line - WHERE account_id=%s - and date < (case when - (select date FROM account_analytic_line where account_id=%s and project_id is null and ref is null order by date limit 1) IS NOT NULL then - (select date FROM account_analytic_line where account_id=%s and project_id is null and ref is null order by date limit 1) else current_date end - );""", (account_id, account_id, account_id, ) ) + WHERE account_id=%s AND date < + (CASE WHEN + (SELECT date FROM account_analytic_line + WHERE account_id=%s + AND project_id IS NULL + AND ref IS NULL + ORDER BY date LIMIT 1) IS NOT NULL + THEN + (SELECT date FROM account_analytic_line + WHERE account_id=%s + AND project_id IS NULL + AND ref IS NULL + ORDER BY date LIMIT 1) + ELSE current_date + END); + """, + (account_id, account_id, account_id, )) else: - self.env.cr.execute(""" - SELECT sum(amount) as Total + self.env.cr.execute( + """ + SELECT SUM(amount) AS Total FROM account_analytic_line - WHERE account_id=%s - and date < (case when - (select date FROM account_analytic_line where account_id=%s and project_id is null and ref is null order by date limit 1) IS NOT NULL then - (select date FROM account_analytic_line where account_id=%s and project_id is null and ref is null order by date limit 1) else current_date end - ) and (task_id not in %s or task_id is null);""", (account_id, account_id, account_id, tuple(prospection_tasks), ) ) + WHERE account_id=%s AND date < + (CASE WHEN + (SELECT date FROM account_analytic_line + WHERE account_id=%s + AND project_id IS NULL + AND ref is NULL + ORDER BY date LIMIT 1) IS NOT NULL + THEN + (SELECT date FROM account_analytic_line + WHERE account_id=%s + AND project_id IS NULL + AND ref IS NULL + ORDER BY date LIMIT 1) + ELSE current_date + END) + AND (task_id NOT IN %s OR task_id IS NULL); + """, + (account_id, account_id, account_id, + tuple(prospection_tasks), )) self.lf_advance = self._cr.fetchone()[0] - #################################################### - ### Actions ### + # Actions # #################################################### - def open_cash_flow(self): return { 'type': 'ir.actions.client', @@ -144,60 +204,82 @@ class FilamentProjetCashFlow(models.Model): } #################################################### - ### Widget Function ### + # Widget Function # #################################################### @api.model def cash_flow(self, account_id, project_id): - account = self.env['account.analytic.account'].search([('id', '=', account_id)]) + account = self.env['account.analytic.account'].search( + [('id', '=', account_id)]) project = self.env['project.project'].search([('id', '=', project_id)]) - - ## Analytic account name + + # Analytic account name nom = account.name - ## Cash Flow Request - prospection_tasks = self.env['project.task'].search([('name','=ilike','Prospection'+'%'),('project_id','=',project_id)]).ids + # Cash Flow Request + prospection_tasks = self.env['project.task'].search( + [('name', '=ilike', 'Prospection'+'%'), + ('project_id', '=', project_id)]).ids if not prospection_tasks: - self.env.cr.execute(""" - SELECT date, sum(sum(amount)) over (order by date) as Total, - sum(sum(case when project_id is not null then amount else 0 end)) over (order by date) as Timesheet, - sum(sum(case when project_id is null and ref is not null then amount else 0 end)) over (order by date) as Fournisseurs, - sum(sum(case when project_id is null and ref is null then amount else 0 end)) over (order by date) as Clients + self.env.cr.execute( + """ + SELECT date, + SUM(SUM(amount)) OVER (ORDER BY date) AS Total, + SUM(SUM(CASE WHEN project_id IS NOT NULL + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Timesheet, + SUM(SUM(CASE WHEN project_id IS NULL AND ref IS NOT NULL + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Fournisseurs, + SUM(SUM(CASE WHEN project_id IS NULL AND ref IS NULL + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Clients FROM account_analytic_line WHERE account_id=%s - group by date; - """, (account_id, ) ) + GROUP BY date; + """, (account_id, )) else: - self.env.cr.execute(""" - SELECT date, - sum(sum(case when (task_id not in %s or task_id is null) then amount else 0 end)) over (order by date) as Total, - sum(sum(case when project_id is not null and (task_id not in %s or task_id is null) then amount else 0 end)) over (order by date) as Timesheet, - sum(sum(case when project_id is null and ref is not null then amount else 0 end)) over (order by date) as Fournisseurs, - sum(sum(case when project_id is null and ref is null then amount else 0 end)) over (order by date) as Clients + self.env.cr.execute( + """ + SELECT date, + SUM(SUM(CASE WHEN (task_id NOT IN %s OR task_id IS NULL) + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Total, + SUM(SUM(CASE WHEN project_id IS NOT NULL + AND (task_id not in %s or task_id IS NULL) + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Timesheet, + SUM(SUM(CASE WHEN project_id IS NULL AND ref IS NOT NULL + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Fournisseurs, + SUM(SUM(CASE WHEN project_id IS NULL AND ref IS NULL + THEN amount ELSE 0 END)) + OVER (ORDER BY date) AS Clients FROM account_analytic_line WHERE account_id=%s - group by date; - """, (tuple(prospection_tasks), tuple(prospection_tasks), account_id, ) ) + GROUP BY date; + """, + (tuple(prospection_tasks), tuple(prospection_tasks), + account_id, )) cash_flow = self._cr.dictfetchall() - ## Time to first invoice + # Time to first invoice delay = project.lf_delay - ## Total Days + # Total Days total_days = project.lf_total_days - ## Balance + # Balance balance = project.lf_balance - ## Advance cash - advance = project.lf_advance + # Advance cash + advance = project.lf_advance return { - 'nom' : nom, + 'nom': nom, 'cash_flow': cash_flow, 'delay': delay, 'balance': balance, 'advance': advance, 'total_days': total_days, } - diff --git a/static/src/js/cash_flow.js b/static/src/js/cash_flow.js index 55535d1a5ad3377cf6503878b919a8e8bc1d51ee..3f88f53cc190f693b0d836983876f7e9a406dcd6 100644 --- a/static/src/js/cash_flow.js +++ b/static/src/js/cash_flow.js @@ -1,5 +1,5 @@ -// # © 2017 Le Filament (<http://www.le-filament.com>) -// # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +// © 2017 Le Filament (<http://www.le-filament.com>) +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). odoo.define('lefilament_cash_flow.cashFlow', function(require) { 'use strict'; diff --git a/static/src/xml/cash_flow.xml b/static/src/xml/cash_flow.xml index 2a7a3ef911bcc87fc353639770491dd569cda3b9..3cbb0b9fe634fec07db0ba8b2b9f74b6b8d71ed2 100644 --- a/static/src/xml/cash_flow.xml +++ b/static/src/xml/cash_flow.xml @@ -1,29 +1,26 @@ <?xml version="1.0" encoding="UTF-8"?> <templates xml:space="preserve"> - - <t t-name="CashFlow"> - <div class="card"> - <table class="table"> - <thead> - <tr> - <th>Current balance</th> - <th>Project duration</th> - <th>Delay 1st facture</th> - <th>Advanced cash</th> - </tr> - </thead> - <tbody> - <tr> - <td><t t-esc="widget.render_monetary(widget.balance)"></t></td> - <td><t t-esc="widget.total_days"></t> days</td> - <td><t t-esc="widget.delay"></t> days</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> - - + <t t-name="CashFlow"> + <div class="card"> + <table class="table"> + <thead> + <tr> + <th>Current balance</th> + <th>Project duration</th> + <th>Delay 1st facture</th> + <th>Advanced cash</th> + </tr> + </thead> + <tbody> + <tr> + <td><t t-esc="widget.render_monetary(widget.balance)"></t></td> + <td><t t-esc="widget.total_days"></t> days</td> + <td><t t-esc="widget.delay"></t> days</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 index d0943f1d5dad6df1ccded09e40bf3ddf4a506dab..d2fb65a89e60aa113de43469808c08a31b735934 100644 --- a/views/assets.xml +++ b/views/assets.xml @@ -1,14 +1,14 @@ <?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> - <template id="lefilament_tdb_assets_backend" inherit_id="web.assets_backend"> <xpath expr="." position="inside"> - <script type="text/javascript" src="/lefilament_cash_flow/static/src/lib/chart.js"></script> + <script type="text/javascript" src="/lefilament_cash_flow/static/src/lib/chart.js"></script> <script type="text/javascript" src="/lefilament_cash_flow/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 584f3584dcc5905a30c8e0430da8a38f146c3918..8f3f066debeb9be06e7c6ed28a65269d3a966c7d 100644 --- a/views/lefilament_projets_view.xml +++ b/views/lefilament_projets_view.xml @@ -1,34 +1,34 @@ <?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> - - <!-- Project Form --> - <record id="view_form_lefilament_cashflow_form_inherited" model="ir.ui.view"> - <field name="name">Le Filament Projets Cash Flow</field> - <field name="model">project.project</field> - <field name="inherit_id" ref="lefilament_projets.view_form_lefilament_project_form_inherited"/> - <field name="arch" type="xml"> - <xpath expr="//sheet" position="before"> - <header> - <button string="See the Cash Flow" type="object" name="open_cash_flow" class="oe_highlight"/> - </header> - </xpath> - <xpath expr="//notebook" position="before"> - <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> - </field> - </record> - - </data> + <data> + <!-- Project Form --> + <record id="view_form_lefilament_cashflow_form_inherited" model="ir.ui.view"> + <field name="name">Le Filament Projets Cash Flow</field> + <field name="model">project.project</field> + <field name="inherit_id" ref="lefilament_projets.view_form_lefilament_project_form_inherited"/> + <field name="arch" type="xml"> + <xpath expr="//sheet" position="before"> + <header> + <button string="See the Cash Flow" type="object" name="open_cash_flow" class="oe_highlight"/> + </header> + </xpath> + <xpath expr="//notebook" position="before"> + <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> + </field> + </record> + </data> </odoo>