Skip to content
Extraits de code Groupes Projets
Valider 72ac948f rédigé par Benjamin - Le Filament's avatar Benjamin - Le Filament
Parcourir les fichiers

[IMP] detail view for customer and employee timesheet

parent f77919eb
Branches
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -59,6 +59,7 @@ repos: ...@@ -59,6 +59,7 @@ repos:
hooks: hooks:
- id: prettier - id: prettier
name: prettier (with plugin-xml) name: prettier (with plugin-xml)
exclude: ^templates/
additional_dependencies: additional_dependencies:
- "prettier@2.7.1" - "prettier@2.7.1"
- "@prettier/plugin-xml@2.2.0" - "@prettier/plugin-xml@2.2.0"
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
"security/ir.model.access.csv", "security/ir.model.access.csv",
# datas # datas
# templates # templates
"templates/dashboard_detail.xml",
"templates/lefilament_dashboard.xml", "templates/lefilament_dashboard.xml",
# views # views
"views/account_bank_statement_line.xml", "views/account_bank_statement_line.xml",
...@@ -29,6 +30,7 @@ ...@@ -29,6 +30,7 @@
"web._assets_frontend_helpers": [], "web._assets_frontend_helpers": [],
"web.assets_frontend": [], "web.assets_frontend": [],
"web.assets_backend": [ "web.assets_backend": [
"lefilament_tdb/static/src/js/dashboard_detail.js",
"lefilament_tdb/static/src/js/dashboard_overview.js", "lefilament_tdb/static/src/js/dashboard_overview.js",
"lefilament_tdb/static/src/css/lefilament_tdb.css", "lefilament_tdb/static/src/css/lefilament_tdb.css",
], ],
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import date from datetime import date
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from psycopg2.extensions import AsIs
from odoo import api, fields, models from odoo import api, fields, models
from odoo.tools.safe_eval import safe_eval from odoo.tools.safe_eval import safe_eval
...@@ -291,3 +293,186 @@ class LeFilamentTdb(models.Model): ...@@ -291,3 +293,186 @@ class LeFilamentTdb(models.Model):
res["company_id"] = self.env.company res["company_id"] = self.env.company
return res return res
@api.model
def dashboard_detail_values(self, date_start=None, date_end=None):
return {
"customer": self._customer_detail(date_start, date_end),
"employee_time": self._employee_time(date_start, date_end),
"date_start": date_start,
"date_end": date_end,
}
@api.model
def dashboard_detail_values_template(self, date_start=None, date_end=None):
return self.env["ir.ui.view"]._render_template(
"lefilament_tdb.dashboard_detail_values",
self.dashboard_detail_values(date_start, date_end),
)
def _customer_detail(self, date_start=None, date_end=None):
clause = "where 1=1 "
if date_start:
clause += f"and line_date >= '{date_start}'"
if date_end:
clause += f"and line_date <= '{date_end}'"
query = """
select
customer as "Client",
sum(prod) as "Imput.",
sum(invoiced + purchase + expense)::int as "Balance Prod",
sum(invoiced + purchase + expense)/NULLIF(sum(prod), 0) as "Taux Horaire",
sum(invoiced + invoiced_mco) as "Tot. Fact.",
sum(invoiced)::int as "Fact. Prod",
sum(invoiced_mco)::int as "Fact. Maint",
sum(purchase)::int as "Achats",
sum(expense)::int as "NdF"
from
(
-- Sélection des heures
select
aal.date as line_date,
p.name as customer,
-- contact != Filament et projet != Maintenance et pas flagué vacances
case when aal.partner_id != 1 or aal.partner_id is null and aal.holiday_id is null and project_id != 19 then unit_amount else 0 end as prod,
0 as invoiced,
0 as invoiced_mco,
0 as purchase,
0 as expense
from
account_analytic_line aal
left join
res_partner p on aal.partner_id = p.id
left join
hr_leave l on aal.holiday_id = l.id
left join
hr_leave_type lt on l.holiday_status_id = lt.id
where
aal.project_id is not null
and aal.date <= CURRENT_DATE
and (lt.active is true or lt.active is null)
and partner_id != 1
-- Sélection du facturé hors maintenance
union all
select
aml.date as line_date,
p.name as customer,
0 as prod,
(aml.credit - aml.debit) as invoiced,
0 as invoiced_mco,
0 as purchase,
0 as expense
from account_move_line aml
left join account_move i on aml.move_id = i.id
left join res_partner p on i.beneficiary_id = p.id
where
i.move_type in ('out_invoice', 'out_refund')
and i.state = 'posted'
and aml.product_id is not null
and aml.product_id not in (33, 34, 50, 51, 61, 62)
-- Sélection du facturé Maintenance
union all
select
aml.date as line_date,
p.name as customer,
0 as prod,
0 as invoiced,
(aml.credit - aml.debit) as invoiced_mco,
0 as purchase,
0 as expense
from account_move_line aml
left join account_move i on aml.move_id = i.id
left join res_partner p on i.beneficiary_id = p.id
where
i.move_type in ('out_invoice', 'out_refund')
and i.state = 'posted'
and aml.product_id is not null
and aml.product_id in (33, 34, 50, 51, 61, 62)
-- Sélection des charges
union all
select
aal.date as line_date,
p.name as customer,
0 as prod,
0 as invoiced,
0 as invoiced_mco,
amount as purchase,
0 as expense
from account_analytic_line aal
left join account_move_line aml on aal.move_line_id = aml.id
left join account_move i on aml.move_id = i.id
left join account_analytic_account a on aal.account_id = a.id
left join res_partner p on a.partner_id = p.id
where
(aal.plan_id is null or aal.plan_id = 1)
and i.state = 'posted'
and aml.journal_id = 2
-- Sélection des NDF
union all
select
aal.date as line_date,
p.name as customer,
0 as prod,
0 as invoiced,
0 as invoiced_mco,
0 as purchase,
amount as expense
from account_analytic_line aal
left join account_move_line aml on aal.move_line_id = aml.id
left join account_move i on aml.move_id = i.id
left join account_analytic_account a on aal.account_id = a.id
left join res_partner p on a.partner_id = p.id
where
(aal.plan_id is null or aal.plan_id = 1)
and i.state = 'posted'
and aml.journal_id = 9
) query
%s
group by
customer
order by
sum(invoiced) desc
"""
self.env.cr.execute(query, (AsIs(clause),))
result = self.env.cr.dictfetchall()
return result
def _employee_time(self, date_start=None, date_end=None):
clause = ""
if date_start:
clause += f"and aal.date >= '{date_start}'"
if date_end:
clause += f"and aal.date <= '{date_end}'"
query = """
select
e.name as "Personne",
sum(case when aal.partner_id != 1 or aal.partner_id is null and aal.holiday_id is null then unit_amount else 0 end) as "Prod",
sum(case when aal.partner_id = 1 and aal.holiday_id is null then unit_amount else 0 end) as "Interne"
from
account_analytic_line aal
left join
hr_employee e on aal.employee_id = e.id
left join
hr_leave l on aal.holiday_id = l.id
left join
hr_leave_type lt on l.holiday_status_id = lt.id
where
aal.project_id is not null
and (lt.active is true or lt.active is null)
%s
group by
e.name
order by
sum(case when aal.partner_id != 1 or aal.partner_id is null and aal.holiday_id is null then unit_amount else 0 end) desc
"""
self.env.cr.execute(query, (AsIs(clause),))
result = self.env.cr.dictfetchall()
return result
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
padding-bottom: 40px; padding-bottom: 40px;
background-color: #fefefe; background-color: #fefefe;
} }
.lefilament_dashboard .display-6, .lefilament_dashboard .card-title { .lefilament_dashboard .display-6,
.lefilament_dashboard .card-title {
color: #495057; color: #495057;
} }
.lefilament_dashboard a.dashboard_view { .lefilament_dashboard a.dashboard_view {
cursor: pointer; cursor: pointer;
} }
p.card-maj { p.card-maj {
font-size: 10px; font-size: 10px;
font-style: italic; font-style: italic;
...@@ -35,3 +35,9 @@ p.card-maj { ...@@ -35,3 +35,9 @@ p.card-maj {
font-weight: 700; font-weight: 700;
padding: 2px 0; padding: 2px 0;
} }
.dashboard_detail {
padding-top: 10px;
padding-bottom: 40px;
background-color: #fefefe;
}
odoo.define("lefilament_tdb.dashboard_detail", function (require) {
"use strict";
const qweb = require("web.qweb");
const viewRegistry = require("web.view_registry");
const Controller = qweb.Controller.extend({
events: _.extend({}, qweb.Controller.prototype.events, {
"click .button-period": "_onClickButtonPeriod",
"click #button-custom-date": "_onClickButtonCustomDate",
}),
async _onClickButtonPeriod(e) {
e.preventDefault();
var $target = $(e.currentTarget);
var data = $target.data();
var dashboard_detail_values = this.$el.find("#dashboard_detail_values");
await this._rpc({
model: "lefilament.dashboard",
method: "dashboard_detail_values_template",
args: [],
kwargs: {
date_start: data.dateStart || null,
date_end: data.dateEnd || null,
},
}).then(function (result) {
dashboard_detail_values.html(result);
});
},
async _onClickButtonCustomDate(e) {
e.preventDefault();
var dashboard_detail_values = this.$el.find("#dashboard_detail_values");
var date_start = this.$el.find("#custom-date-start")[0];
var date_end = this.$el.find("#custom-date-end")[0];
await this._rpc({
model: "lefilament.dashboard",
method: "dashboard_detail_values_template",
args: [],
kwargs: {
date_start: date_start.value || null,
date_end: date_end.value || null,
},
}).then(function (result) {
dashboard_detail_values.html(result);
});
},
});
const DashboardDetail = qweb.View.extend({
withSearchBar: false,
searchMenuTypes: [],
config: _.extend({}, qweb.View.prototype.config, {
Controller: Controller,
}),
});
viewRegistry.add("dashboard_detail", DashboardDetail);
});
odoo.define("lefilament_tdb.dashboard_overview", function (require) { odoo.define("lefilament_tdb.dashboard_overview", function (require) {
"use strict"; "use strict";
var core = require("web.core");
const qweb = require("web.qweb"); const qweb = require("web.qweb");
const viewRegistry = require("web.view_registry"); const viewRegistry = require("web.view_registry");
var QWeb = core.qweb;
const Controller = qweb.Controller.extend({ const Controller = qweb.Controller.extend({
events: _.extend({}, qweb.Controller.prototype.events, { events: _.extend({}, qweb.Controller.prototype.events, {
"click #update_view": "_onClickUpdateView", "click #update_view": "_onClickUpdateView",
...@@ -38,7 +35,6 @@ odoo.define("lefilament_tdb.dashboard_overview", function (require) { ...@@ -38,7 +35,6 @@ odoo.define("lefilament_tdb.dashboard_overview", function (require) {
} }
}); });
}, },
}); });
const DashboardOverview = qweb.View.extend({ const DashboardOverview = qweb.View.extend({
......
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Template layout -->
<record id="lefilament_dashboard_detail" model="ir.ui.view">
<field name="name">lefilament.dashboard.detail</field>
<field name="type">qweb</field>
<field name="model">lefilament.dashboard</field>
<field name="arch" type="xml">
<qweb js_class="dashboard_detail">
<nav class="o_qweb_cp_buttons">
<div class="btn-group" role="group" aria-label="Dates buttons">
<button
type="object"
class="btn btn-outline-primary button-period"
name="this_month"
t-att-data-date-start="datetime.date.today().replace(day=1).strftime('%Y-%m-%d')"
t-att-data-date-end="(datetime.date.today().replace(day=1) + relativedelta(months=1, days=-1)).strftime('%Y-%m-%d')"
>
Ce mois-ci
</button>
<button
type="object"
class="btn btn-outline-primary button-period"
name="last_month"
t-att-data-date-start="(datetime.date.today().replace(day=1) + relativedelta(months=-1)).strftime('%Y-%m-%d')"
t-att-data-date-end="(datetime.date.today().replace(day=1) + relativedelta(days=-1)).strftime('%Y-%m-%d')"
>
Le mois dernier
</button>
<button
type="object"
class="btn btn-outline-primary button-period"
name="this_year"
t-att-data-date-start="datetime.date.today().replace(day=1, month=1).strftime('%Y-%m-%d')"
t-att-data-date-end="datetime.date.today().replace(day=31, month=12).strftime('%Y-%m-%d')"
>
Cette année
</button>
<button
type="object"
class="btn btn-outline-primary button-period"
name="last_year"
t-att-data-date-start="(datetime.date.today().replace(day=1, month=1) + relativedelta(years=-1)).strftime('%Y-%m-%d')"
t-att-data-date-end="(datetime.date.today().replace(day=31, month=12) + relativedelta(years=-1)).strftime('%Y-%m-%d')"
>
L'année dernière
</button>
<button
type="object"
class="btn btn-outline-primary button-period"
name="all"
data-date-start=""
data-date-end=""
>
Tout
</button>
</div>
<div class="btn-group ms-5" role="group">
<input type="date" class="form-control text-center" id="custom-date-start" />
<input type="date" class="form-control text-center" id="custom-date-end" />
<button
type="object"
id="button-custom-date"
class="btn btn-outline-primary"
>
Mettre à jour
</button>
</div>
</nav>
<div id="dashboard_detail_values">
<t t-value="records.dashboard_detail_values()" t-set="init_data" />
<t t-value="init_data.get('customer')" t-set="customer" />
<t t-value="init_data.get('employee_time')" t-set="employee_time" />
<t t-call="lefilament_tdb.dashboard_detail_values" />
</div>
</qweb>
</field>
</record>
<!-- Dashboard Values -->
<template id="dashboard_detail_values" name="dashboard_detail_values">
<div class="dashboard_detail">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="mt-2 mb-4 display-6 text-center">
Période :
<t t-out="date_start" t-options="{'widget': 'date', 'format': 'dd/MM/YYYY'}" /> -
<t t-out="date_end" t-options="{'widget': 'date', 'format': 'dd/MM/YYYY'}" />
</div>
</div>
<div class="col-7">
<h3 class="text-uppercase py-2 mb-0">Rentabilité client</h3>
<hr class="mt-0" />
<t t-call="lefilament_tdb.dashboard_detail_table">
<t t-set="data" t-value="customer" />
</t>
</div>
<div class="col-5">
<h3 class="text-uppercase py-2 mb-0">Imputations</h3>
<hr class="mt-0" />
<t t-call="lefilament_tdb.dashboard_detail_table">
<t t-set="data" t-value="employee_time" />
</t>
</div>
</div>
</div>
</div>
</template>
<!-- Table Template -->
<template id="dashboard_detail_table" name="dashboard_detail_table">
<div t-if="data" class="px-4 mb-5" style="max-height: 600px; overflow: scroll; border: 1px solid #eee;">
<table class="table table-hover table-striped">
<thead>
<tr class="bg-100">
<th>#</th>
<th t-foreach="data[0].keys()" t-as="header">
<t t-out="header" />
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="data" t-as="line">
<td t-out="line_index + 1" />
<t t-foreach="line.values()" t-as="v">
<td t-att-class="'text-end' if isinstance(v, (int, float)) else ''">
<t t-if="isinstance(v, (int, float))">
<t t-out="v" t-options="{'widget': 'float', 'precision': 0}" />
</t>
<t t-else="">
<t t-out="v" />
</t>
</td>
</t>
</tr>
</tbody>
</table>
</div>
</template>
<!-- Action -->
<record id="le_filament_dashboard_detail_action" model="ir.actions.act_window">
<field name="name">Détail</field>
<field name="res_model">lefilament.dashboard</field>
<field name="view_mode">qweb</field>
<field name="view_id" ref="lefilament_dashboard_detail"/>
<field name="context">{'active_test': False}</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="lefilament_dashboard_overview_form" model="ir.ui.view"> <record id="lefilament_dashboard_overview_form" model="ir.ui.view">
<field name="name">lefilament.dashboardt.overview</field> <field name="name">lefilament.dashboard.overview</field>
<field name="type">qweb</field> <field name="type">qweb</field>
<field name="model">lefilament.dashboard</field> <field name="model">lefilament.dashboard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
...@@ -535,6 +535,7 @@ ...@@ -535,6 +535,7 @@
<field name="name">Rapport Annuel</field> <field name="name">Rapport Annuel</field>
<field name="res_model">lefilament.dashboard</field> <field name="res_model">lefilament.dashboard</field>
<field name="view_mode">qweb</field> <field name="view_mode">qweb</field>
<field name="view_id" ref="lefilament_dashboard_overview_form" />
<field name="context">{'active_test': False}</field> <field name="context">{'active_test': False}</field>
</record> </record>
</odoo> </odoo>
...@@ -24,6 +24,13 @@ ...@@ -24,6 +24,13 @@
sequence="1" sequence="1"
action="lefilament_tdb.le_filament_dashboard_overview_action" action="lefilament_tdb.le_filament_dashboard_overview_action"
/> />
<menuitem
id="lefilament_dashboard_report_detail"
parent="lefilament_dashboard_report"
name="Détail"
sequence="1"
action="lefilament_tdb.le_filament_dashboard_detail_action"
/>
<menuitem <menuitem
id="lefilament_dashboard_conf" id="lefilament_dashboard_conf"
parent="lefilament_dashboard_menu" parent="lefilament_dashboard_menu"
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Veuillez vous inscrire ou vous pour commenter