diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..caf89a56789588a76e645558f4516fc491c0e7d4 --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2022 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import main diff --git a/controllers/main.py b/controllers/main.py new file mode 100644 index 0000000000000000000000000000000000000000..48012ea1d48985108b1db3d3bcf7692eba86dab6 --- /dev/null +++ b/controllers/main.py @@ -0,0 +1,37 @@ +# Copyright 2022 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from odoo import http +from odoo.http import request + + +class AccountInvoiceAllocationController(http.Controller): + + @http.route('/account/allocation', type='json', auth="user") + def plan(self): + """ Get the HTML of the project plan for projects matching the given domain + :param domain: a domain for project.project + """ + invoice_ids = request.env["account.invoice"].search( + [ + ("type", "in", ["out_invoice", "out_refund"]), + ("state", "in", ["open", "in_payment", "paid"]), + "|", + ("is_allocated", "=", False), + ("is_allocation_error", "=", True), + ], + order="date_invoice" + ) + partner_ids = ( + request.env.ref("base.main_partner") + + request.env["hr.employee"].search([]).mapped("user_id").mapped("partner_id") + ) + view = request.env.ref("legicoop_account.invoice_allocation") + return { + 'html_content': view.render({ + "invoice_ids": invoice_ids, + "partner_ids": partner_ids, + }), + "invoice_ids": invoice_ids.ids, + } diff --git a/static/src/css/invoice_allocation.css b/static/src/css/invoice_allocation.css new file mode 100755 index 0000000000000000000000000000000000000000..b0bb4d8e005adadb53f0bd1bba77078fa877f67f --- /dev/null +++ b/static/src/css/invoice_allocation.css @@ -0,0 +1,39 @@ +.allocation-title { + padding: 10px; + background-color: #fff; + width: 100%; + position: fixed; + top: 46px; +} +.allocation-title > div { + padding: 5px 10px; + display:inline-block; +} +.allocation-line { + width: 100%; + overflow-x: scroll; + white-space: nowrap; + border-bottom: 1px solid #aaa; + scrollbar-width: none; +} +.allocation-line::-webkit-scrollbar { + height: 4px; +} + +.allocation-line::-webkit-scrollbar-track { + background: #eee; +} + +.allocation-line::-webkit-scrollbar-thumb { + background-color: #7C7BAD; + border-radius: 0px; +} + +.allocation-col { + padding: 10px; + display: inline-block; + width: 120px; + max-height: 120px; + overflow: hidden; + white-space: initial; +} diff --git a/static/src/js/invoice_allocation.js b/static/src/js/invoice_allocation.js new file mode 100755 index 0000000000000000000000000000000000000000..8bfda0b253477b398a63d532ee62455e0667a891 --- /dev/null +++ b/static/src/js/invoice_allocation.js @@ -0,0 +1,127 @@ +// © 2019 Le Filament (<http://www.le-filament.com>) +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +odoo.define('legicoop_account.invoice_allocation', function (require) { + "use strict"; + + var AbstractAction = require('web.AbstractAction'); + var core = require('web.core'); + var data = require('web.data'); + + var _t = core._t; + var QWeb = core.qweb; + + var AccountInvoiceAllocation = AbstractAction.extend({ + events: { + "submit form[name='allocation_form']": "_onSubmit", + }, + /** + * @override + */ + init: function (parent, action) { + this._super.apply(this, arguments); + this.action = action; + this.action_manager = parent; + this.set('title', action.name || _t('Factures à répartir')); + this.invoice_ids = []; + }, + + /** + * @override + */ + start: function () { + this._fetchAllocation() + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Refresh the DOM html + * + * @private + * @param {string|html} dom + */ + _refreshAllocation: function (dom) { + var $dom = $(dom); + this.$el.html($dom); + }, + + /** + * Call controller to get the html content + * + * @private + * @param {string[]} + * @returns {Deferred} + */ + _fetchAllocation: function () { + var self = this; + return this._rpc({ + route:"/account/allocation", + }).then(function(result) { + self._refreshAllocation(result.html_content); + self.invoice_ids = result.invoice_ids; + }); + }, + + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * + * @private + * @param {MouseEvent} event + */ + _onSubmit: function (ev) { + ev.preventDefault(); + var self = this; + let invoice_id = parseInt(ev.target.id) + let elements = Array.from(ev.target.elements) + // Get employees with percent > 0 & total percent + let employees = elements.filter(function (el) { + return el.dataset.inputName === "employee" && el.valueAsNumber > 0; + }); + let employeeTotal = employees.reduce(function (accumulator, employee) { + return accumulator + employee.valueAsNumber; + }, 0); + // Get Subcontracting amount + let subcontracting = elements.find(el => el.name === "subcontracting_amount"); + // Get Subcontracting amount + let expense = elements.find(el => el.name === "expense_amount"); + + if (employeeTotal != 100) { + var msg = $(ev.target).find(".error-message") + msg.html("<div class='alert alert-danger mt16' role='alert'>Mise à jour impossible : le total des pourcentage n'est pas égal à 100.</div>") + return + } else { + var new_allocation = [] + _.each(employees, function (emp) { + new_allocation.push([0, 0, { + "partner_id": parseInt(emp.dataset.partnerId), + "percentage": emp.valueAsNumber, + }]) + }); + var new_values = { + "subcontracting_amount": subcontracting.valueAsNumber, + "expense_amount": expense.valueAsNumber, + "employee_allocation_ids": new_allocation, + } + console.log(new_values) + this._rpc({ + model: "account.invoice", + method: "create_employee_lines", + args: [[invoice_id], new_values], + }).then(function (result) { + console.log(result) + self._fetchAllocation() + }); + } + }, + }); + + core.action_registry.add('legicoop_account.invoice_allocation', AccountInvoiceAllocation); +}); + diff --git a/static/src/xml/employee_assign_form.xml b/static/src/xml/employee_assign_form.xml new file mode 100755 index 0000000000000000000000000000000000000000..f3bf780a7bcec1af0b44fdf51f5e27d9b3c46833 --- /dev/null +++ b/static/src/xml/employee_assign_form.xml @@ -0,0 +1,227 @@ +<?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="EmployeeAssignForm"> + <div class="yeardashboard"> + <div class="row"> + <!-- COLONNE 1 --> + <div class="col-xs-12"> + <t t-foreach="widget.invoices" + <h3>En Cours</h3> + <!-- Facturé --> + <div class="col-xs-12"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Facturé</h5> + <p class="card-number"> + <a id="facture" t-attf-data-fiscalyear="#{widget.values.fiscal_year}" > + <t t-esc="widget.render_monetary(widget.values.facture)"></t> + </a> + </p> + </div> + </div> + </div> + <!-- Commandes --> + <div class="col-xs-12"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Commandes en cours</h5> + <p class="card-number"> + <a id="commandes"> + <t t-esc="widget.render_monetary(widget.values.commandes)"></t> + </a> + </p> + </div> + </div> + </div> + <!-- Pipe --> + <div class="col-xs-12 dashboard-tab"> + <ul class="nav nav-tabs" id="myTab1"> + <li class="nav-item"> + <a class="nav-link active" data-toggle="tab" href="#pipe">Pipe</a> + </li> + <li class="nav-item"> + <a class="nav-link" data-toggle="tab" href="#pipe_n1">Pipe N+1</a> + </li> + </ul> + <div class="tab-content card-tab" id="myTab1Content"> + <div id="pipe" class="tab-pane fade show active in"> + <p class="card-number"> + <a id="pipe_link" t-attf-data-fiscalyearnext="#{widget.values.fiscal_year_next}" > + <t t-esc="widget.render_monetary(widget.values.pipe)"></t> + </a> + </p> + </div> + <div id="pipe_n1" class="tab-pane fade"> + <p class="card-number"> + <a id="pipe_n1_link" t-attf-data-fiscalyearnext="#{widget.values.fiscal_year_next}" > + <t t-esc="widget.render_monetary(widget.values.pipe_n1)"></t> + </a> + </p> + </div> + </div> + </div> + </div> + <!-- COLONNE 2 --> + <div class="col-xs-12 col-sm-6 col-md-4"> + <h3>Objectif</h3> + <div class="col-xs-12"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Target</h5> + <p class="card-number"> + <t t-esc="widget.render_monetary(widget.values.target)"></t> + </p> + <canvas id="target" width="auto" height="125"></canvas> + <!-- <div id="hchart" ></div> --> + <table class="table-legend"> + <tr> + <td style="background-color: #8ED8A2; width: 20px;"></td> + <td>Facturé</td> + <td class="nb"><t t-esc="widget.render_percent(widget.pfact)"></t></td> + <td class="nb"><t t-esc="widget.render_keur(widget.pfact2)"></t></td> + </tr> + <tr> + <td style="background-color: #F6DCA2; width: 20px;"></td> + <td>Commandes</td> + <td class="nb"><t t-esc="widget.render_percent(widget.pcomm)"></t></td> + <td class="nb"><t t-esc="widget.render_keur(widget.pcomm2)"></t></td> + </tr> + <tr> + <td style="background-color: #F6CCA2; width: 20px;"></td> + <td>Pipe Gagné</td> + <td class="nb"><t t-esc="widget.render_percent(widget.ppipe_win)"></t></td> + <td class="nb"><t t-esc="widget.render_keur(widget.ppipe2_win)"></t></td> + </tr> + <tr class="table-legend-total"> + <td style="width: 20px;"></td> + <td>Total</td> + <td class="nb"><t t-esc="widget.render_percent(widget.total)"></t></td> + <td class="nb"><t t-esc="widget.render_keur(widget.total2)"></t></td> + </tr> + <tr> + <td style="width: 20px;"></td> + <td></td> + <td class="nb"></td> + <td class="nb"></td> + </tr> + <tr> + <td style="background-color: #F6ACA2; width: 20px;"></td> + <td>Pipe</td> + <td class="nb"><t t-esc="widget.render_percent(widget.ppipe_to_win)"></t></td> + <td class="nb"><t t-esc="widget.render_keur(widget.ppipe2_to_win)"></t></td> + </tr> + <tr> + <td style="background-color: #eee; width: 20px;"></td> + <td>To Do</td> + <td class="nb"><t t-esc="widget.render_percent(widget.ptarg)"></t></td> + <td class="nb"><t t-esc="widget.render_keur(widget.ptarg2)"></t></td> + </tr> + </table> + </div> + </div> + </div> + </div> + <!-- COLONNE 3 --> + <div class="col-xs-12 col-sm-6 col-md-4 tresorerie"> + <h3>Trésorerie</h3> + <!-- Tréso --> + <div class="col-xs-12"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Trésorerie</h5> + <p class="card-maj">Denière mise à jour le <t t-esc="widget.render_date(widget.values.date_maj)"></t></p> + <p class="card-number"> + <a id="releve"> + <strong><t t-esc="widget.render_monetary(widget.values.tresorerie)"></t></strong> + </a> + </p> + </div> + </div> + </div> + <!-- Non encaissé / Du --> + <div class="col-xs-12 dashboard-tab"> + <ul class="nav nav-tabs" id="tab3"> + <li class="nav-item"> + <a class="nav-link active" data-toggle="tab" href="#non_encaisse">Facturé non encaissé</a> + </li> + <li class="nav-item"> + <a class="nav-link" data-toggle="tab" href="#fournisseur">Fournisseurs</a> + </li> + </ul> + <div class="tab-content card-tab" id="myTab3Content"> + <div id="non_encaisse" class="tab-pane fade show active in"> + <p class="card-number"> + <a id="facture_non_encaisse"> + <t t-esc="widget.render_monetary(widget.values.a_encaisser)"></t> + </a> + </p> + </div> + <div id="fournisseur" class="tab-pane fade"> + <p class="card-number"> + <a id="fournisseur_link"> + <t t-esc="widget.render_monetary(widget.values.a_payer)"></t> + </a> + </p> + </div> + </div> + </div> + <!-- Variation --> + <div class="col-xs-12 dashboard-tab"> + <ul class="nav nav-tabs" id="myTab2"> + <li class="nav-item"> + <a class="nav-link active" data-toggle="tab" href="#variation">Variation</a> + </li> + <li class="nav-item"> + <a class="nav-link" data-toggle="tab" href="#e_s">Entrées/Sorties</a> + </li> + </ul> + <div class="tab-content card-tab" id="myTab2Content"> + <div id="variation" class="tab-pane fade show active in"> + <p class="card-number"> + <t t-raw="widget.render_monetary_color(widget.values.variation)"></t> + </p> + </div> + <div id="e_s" class="tab-pane fade"> + <div class="card-half"> + <h5 class="card-title">Encaissé</h5> + <p class="card-number"> + <t t-esc="widget.render_monetary(widget.values.entree)"></t> + </p> + </div> + <div class="card-half"> + <h5 class="card-title">Sorties</h5> + <p class="card-number"> + <t t-esc="widget.render_monetary(widget.values.sortie)"></t> + </p> + </div> + <div class="clearfix"></div> + </div> + </div> + </div> + <!-- <div class="col-xs-12"> + <div class="card card-cca"> + <div class="card-body"> + <div class="card-cca-half"> + <h5 class="card-title">CCA en cours</h5> + <p class="card-number"> + <t t-raw="widget.render_monetary(widget.values.cca)"></t> + </p> + </div> + <div class="card-cca-half"> + <h5 class="card-title">Capital Social</h5> + <p class="card-number"> + <t t-raw="widget.render_monetary(widget.values.capital)"></t> + </p> + </div> + </div> + </div> + </div> --> + </div> + </div> + </div> + </t> + +</templates> diff --git a/templates/invoice_allocation_templates.xml b/templates/invoice_allocation_templates.xml new file mode 100644 index 0000000000000000000000000000000000000000..e28e07362b651c6a580ab672377dde63217e0c2f --- /dev/null +++ b/templates/invoice_allocation_templates.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <template id="invoice_allocation" name="Invoice Allocation"> + <div class="o_form_view o_form_readonly"> + <div class="allocation-main"> + <div class="allocation-title"> + <h2 class="mt32">Liste des factures à répartir</h2> + <hr/> + <div style="width:120px;"> + <strong>Facture</strong> + </div> + <div style="width:120px;"> + <strong>CA facturé</strong> + </div> + <div style="width:240px;"> + <strong>Montant sous-traitance/frais</strong> + </div> + <div> + <strong>Répartition employés</strong> + </div> + </div> + + <hr style="margin-top: 130px;" /> + <t t-foreach="invoice_ids" t-as="i"> + <div class="allocation-block"> + <form name="allocation_form" method="post" t-att-id="i.id"> + <div class="error-message" /> + <div class="allocation-line"> + <!-- Invoice name --> + <div class="allocation-col"> + <strong><span t-field="i.number" /></strong> + <br /> + <span t-field="i.partner_id.name" /> + </div> + <!-- Invoice amount --> + <div class="allocation-col"> + <strong> + <span t-field="i.amount_untaxed_signed" t-options="{'widget': 'monetary'}" /> + </strong> + <br /> + <t t-if="i.state == 'paid'"> + <span class="badge badge-pill badge-success">Payée</span> + </t> + <t t-else=""> + <span class="badge badge-pill badge-warning">Ouverte</span> + </t> + </div> + <!-- Subcontracting amount --> + <div class="allocation-col" style="background-color: #eee; border-left: 1px solid #aaa;"> + <div style="min-height: 50px;"> + Sous-traitance (HT) + </div> + <input + type="number" + t-att-value="i.subcontracting_amount" + name="subcontracting_amount" + /> + </div> + <!-- Expense amount --> + <div class="allocation-col" style="background-color: #eee; border-right: 1px solid #aaa;"> + <div style="min-height: 50px;"> + Frais (HT) + </div> + <input + type="number" + t-att-value="i.expense_amount" + name="expense_amount" + /> + </div> + <!-- Employee allocation --> + <t t-foreach="partner_ids" t-as="p"> + <div class="allocation-col"> + <div style="min-height: 50px;"> + <span t-field="p.name" /> + </div> + <input + type="number" + t-att-data-partner-id="p.id" + data-input-name="employee" + min="0" + max="100" + t-att-value="20 if p.id == 1 else i.get_employee_allocation(p)" + /> + </div> + </t> + <!-- Button --> + <div class="allocation-col"> + <div style="height: 45px;"> + </div> + <button t-att-id="i.id" class="btn btn-info" type="submit">Affecter</button> + </div> + </div> + </form> + </div> + </t> + </div> + </div> + </template> + + <record id="account_invoice_invoice_allocation" model="ir.actions.client"> + <field name="name">Allocation facture</field> + <field name="tag">legicoop_account.invoice_allocation</field> + <field name="res_model">account.invoice</field> + </record> + +</odoo> diff --git a/views/assets.xml b/views/assets.xml new file mode 100755 index 0000000000000000000000000000000000000000..f6076b15e9f5808fdb7b1c130b487356b9753ae8 --- /dev/null +++ b/views/assets.xml @@ -0,0 +1,15 @@ +<?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="legicoop_account_assets_backend" inherit_id="web.assets_backend"> + <xpath expr="." position="inside"> + <link href="/legicoop_account/static/src/css/invoice_allocation.css" rel="stylesheet" /> + <script type="text/javascript" src="/legicoop_account/static/src/js/invoice_allocation.js"></script> + </xpath> + </template> + + </data> +</odoo> diff --git a/views/menus.xml b/views/menus.xml new file mode 100644 index 0000000000000000000000000000000000000000..47d9062a8e598ddea4f27d80cf318c22b64ff617 --- /dev/null +++ b/views/menus.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + + <!-- This Menu Item must have a parent and an action --> + <menuitem + id="account_invoice_allocation_menu" + name="Affecter la répartition des factures" + parent="account.menu_finance_entries_actions" + action="account_invoice_invoice_allocation" + sequence="80" + /> + + </data> +</odoo>