gn_discount and gn_donations in v16

pull/2/head
Florian Roger 10 months ago
parent be4786c994
commit 38c9006e82

@ -0,0 +1,2 @@
__pycache__
models/__pycache__

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

@ -0,0 +1,24 @@
{
'name': "Gn Discount",
'version': '14.0.0.1.3',
'author': 'Garage Numérique',
'category': 'Sales',
'description': """
This module adds the ability to display the total before discount and the discount amount on sales orders and invoices.
This version only works when no taxes are applied
""",
'depends': ['sale', 'sale_discount_total'],
'data': [
'views/sale_view.xml',
'views/sale_report.xml',
'views/sale_portal.xml',
'views/invoice_view.xml',
'views/invoice_report.xml'
],
'translate': True,
'translations': [
('fr_FR', 'i18n/gn_discount.fr_FR.po'),
],
'installable': True,
}

@ -0,0 +1,14 @@
msgid "Subtotal"
msgstr "Sous-total"
msgid "Discount"
msgtr "Remise"
msgid "Subtotal with discount"
msgtr "Sous-total après remise"
msgid "Total without discount"
msgtr "Total avant remise"
msgid "Total discounted"
msgtr "Total après remise"

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import sale, invoice

@ -0,0 +1,19 @@
from odoo import api, fields, models
class AccountMove(models.Model):
_inherit = 'account.move'
amount_undiscounted = fields.Monetary(compute='_compute_amount_undiscounted', store=True)
@api.depends('amount_untaxed', 'amount_discount')
def _compute_amount_undiscounted(self):
for record in self:
record.amount_undiscounted = record.amount_untaxed + record.amount_discount
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
price_subtotal_before_discount = fields.Monetary(compute='_compute_amount_undiscounted', store=True, string="Subtotal", translate="True")
@api.depends('price_unit', 'quantity')
def _compute_amount_undiscounted(self):
for record in self:
record.price_subtotal_before_discount = record.price_unit * record.quantity

@ -0,0 +1,11 @@
from odoo import api, fields, models
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
price_subtotal_before_discount = fields.Monetary(readonly=True, string="Subtotal", translate=True, compute='_compute_price_subtotal_before_discount')
@api.depends('price_unit', 'discount', 'product_uom_qty')
def _compute_price_subtotal_before_discount(self):
for line in self:
line.price_subtotal_before_discount = line.price_unit * line.product_uom_qty

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="gn_discount_report_invoice" inherit_id="account.report_invoice_document">
<!--<xpath expr="//span[@t-field='o.amount_untaxed']" position="replace">
<span t-field="o.amount_undiscounted"/>
</xpath>-->
<!--<xpath expr="//tr[td[strong[text()='Subtotal']]]" position="replace">-->
<xpath expr="//t[@t-set='display_discount']" position="replace">
<t t-set="display_discount" t-value="false"/>
</xpath>
<xpath expr="//tr[@class='border-black o_subtotal']" position="replace">
<t t-if="o.amount_discount &gt; 0">
<tr class="border-black">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span
t-if="o.amount_discount &gt;= 0"
t-att-style="'text-decoration: line-through' or None"
t-att-class="'text-danger' or ''"
data-id="total_before_discount"
t-field="o.amount_undiscounted"
/>
</td>
</tr>
<tr>
<td>Discount**</td>
<td class="text-right">
<div>
<span>-</span>
<span t-if="o.amount_discount" t-field="o.amount_discount"/>
</div>
</td>
</tr>
<tr>
<td><strong>Subtotal with discount</strong></td>
<td class="text-right">
<div>
<div t-if="o.amount_discount" t-field="o.amount_untaxed">
</div>
</div>
</td>
</tr>
</t>
<t-else>
<tr class="border-black">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span t-field="o.amount_untaxed"/>
</td>
</tr>
</t-else>
</xpath>
<xpath expr="//td[.//span[@t-field='line.price_subtotal']]" position="replace">
<td class="text-right o_price_total">
<span t-field="line.price_subtotal_before_discount"/>
</td>
</xpath>
</template>
</odoo>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_discount_invoice_view_form" model="ir.ui.view">
<field name="name">gn_discount.account.move.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount_untaxed']" position="replace">
<field name="amount_undiscounted" widget="monetary"/>
</xpath>
<xpath expr="//field[@name='amount_by_group']" position="before">
<field name="amount_untaxed"/>
</xpath>
<xpath expr="//field[@name='price_subtotal']" position="replace">
<field name="price_subtotal_before_discount" readonly="1"/>
</xpath>
</field>
</record>
</data>
</odoo>

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="gn_discount.portal_order_view_template" inherit_id="sale.sale_order_portal_content">
<xpath expr="//td[.//span[@class='oe_order_line_price_subtotal']]" position="replace">
<td class="text-right">
<span class="oe_order_line_price_subtotal" t-field="line.price_subtotal_before_discount"/>
</td>
</xpath>
<xpath expr="//td[div[@t-field='line.price_unit']]" position="replace">
<td t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
<div t-field="line.price_unit" t-attf-class="text-right"/>
</td>
</xpath>
<xpath expr="//t[@t-set='display_discount']" position="replace">
<t t-set="display_discount" t-value="false"/>
</xpath>
<!--<xpath expr="//strong[@class='text-info']" position="replace">
<strong t-if="line.discount &gt; 0" class="text-info">
<t t-esc="'{:.2f}'.format(line.discount)"/>%
</strong>
</xpath>-->
</template>
<template id="gn_discount.portal_order_view_totals" inherit_id="sale.sale_order_portal_content_totals_table">
<xpath expr="//tr[@class='border-black'][1]" position="replace">
<t t-if="sale_order.amount_discount &gt; 0">
<tr class="border-black">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<div>
<span
t-att-style="'text-decoration: line-through' or None"
t-att-class="'text-danger' or ''"
data-id="total_before_discount"
t-field="sale_order.amount_undiscounted"
t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
/>
</div>
</td>
</tr>
<tr>
<td>Discount</td>
<td class="text-right">
<div>
<div t-if="sale_order.amount_discount">
<span>-</span><t t-esc="sale_order.amount_discount" t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'/>
</div>
</div>
</td>
</tr>
<tr>
<td><strong>Subtotal with discount</strong></td>
<td class="text-right">
<div>
<div t-if="sale_order.amount_discount">
<t t-esc="sale_order.amount_untaxed" t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'/>
</div>
</div>
</td>
</tr>
</t>
<t-else>
<tr class="border-black">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<div>
<span t-field="sale_order.amount_untaxed"/>
</div>
</td>
</tr>
</t-else>
</xpath>
</template>
</odoo>

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="gn_discount_report_saleorder_document" inherit_id="sale.report_saleorder_document">
<xpath expr="//tr[@class='border-black o_subtotal']" position="replace">
<t t-if="doc.amount_discount &gt; 0">
<tr class="border-black">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span
t-att-style="'text-decoration: line-through' or None"
t-att-class="'text-danger' or ''"
data-id="total_before_discount"
t-field="doc.amount_undiscounted"
t-options='{"widget": "monetary","display_currency": doc.currency_id}'
/>
</td>
</tr>
<tr>
<td>Discount**</td>
<td class="text-right">
<div>
<span>-</span>
<span t-if="doc.amount_discount" t-field="doc.amount_discount"/>
</div>
</td>
</tr>
<tr>
<td><strong>Subtotal with discount</strong></td>
<td class="text-right">
<div>
<div t-if="doc.amount_discount" t-field="doc.amount_untaxed">
</div>
</div>
</td>
</tr>
</t>
<t-else>
<tr class="border-black">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span t-field="doc.amount_untaxed"/>
</td>
</tr>
</t-else>
</xpath>
<xpath expr="//td[@name='td_subtotal']" position="replace">
<td name="td_subtotal_before_discount" class="text-right">
<span t-field="line.price_subtotal_before_discount" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: doc.pricelist_id.currency_id}"/>
</td>
</xpath>
</template>
</odoo>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_discount_sale_view_form" model="ir.ui.view">
<field name="name">gn_discount.sale.order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="priority">99</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount_untaxed']" position="replace">
<field name="amount_undiscounted" widget="monetary" readonly="1"/>
</xpath>
<xpath expr="//field[@name='amount_tax']" position="before">
<field name="amount_untaxed" readonly="1"/>
</xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="replace">
<field name="price_subtotal_before_discount" readonly="1"/>
</xpath>
</field>
</record>
</data>
</odoo>

@ -0,0 +1,2 @@
__pycache__
models/__pycache__

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

@ -0,0 +1,17 @@
{
'name': "Gn Donations",
'version': '14.0.0.1.1',
'author': 'Garage Numérique',
'category': 'Accounting',
'description': """
This module modify the fiscal receipt report so it is similar to Cerfa 11580.
It allows to edit fiscal receipts for in-kind donation (i.e. 0 donation).
""",
'depends': ['donation'],
'data': [
'views/donation_thanks_report.xml',
'views/internal_layout.xml'
],
'translate': True,
'installable': True,
}

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import donation

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
from odoo import fields, models
import num2words
class TaxReceipt(models.Model):
_inherit = 'donation.tax.receipt'
amount_in_words = fields.Char(compute='_compute_amount_in_words', string='Amount in Words')
def _compute_amount_in_words(self):
for record in self:
record.amount_in_words = num2words.num2words(record.amount, lang='fr')
from odoo import _, api
from odoo.exceptions import UserError
from odoo.tools.misc import format_amount
from odoo.addons.account import _auto_install_l10n
# Rewriting donation to allow 0€ fiscal receipts
class DonationDonation(models.Model):
_inherit = 'donation.donation'
def validate(self):
check_total = self.env["res.users"].has_group(
"donation.group_donation_check_total"
)
for donation in self:
if donation.donation_date > fields.Date.context_today(self):
raise UserError(
_(
"The date of donation %s should be today "
"or in the past, not in the future!"
)
% donation.number
)
if not donation.line_ids:
raise UserError(
_(
"Cannot validate donation %s because it doesn't "
"have any lines!"
)
% donation.number
)
'''
# The part we don't want, in order ta validate 0€ fiscal receipts
if donation.currency_id.is_zero(donation.amount_total):
raise UserError(
_("Cannot validate donation %s because the " "total amount is 0!")
% donation.number
)
'''
if donation.state != "draft":
raise UserError(
_(
"Cannot validate donation %s because it is not "
"in draft state."
)
% donation.number
)
if check_total and donation.currency_id.compare_amounts(
donation.check_total, donation.amount_total
):
raise UserError(
_(
"The amount of donation %s (%s) is different "
"from the sum of the donation lines (%s)."
)
% (
donation.number,
format_amount(
self.env, donation.check_total, donation.currency_id
),
format_amount(
self.env, donation.amount_total, donation.currency_id
),
)
)
full_in_kind = all([line.in_kind for line in donation.line_ids])
if not donation.payment_mode_id and not full_in_kind:
raise UserError(
_(
"Payment Mode is not set on donation %s (only fully "
"in-kind donations don't require a payment mode)."
)
% donation.number
)
vals = {"state": "done"}
if full_in_kind and donation.payment_mode_id:
vals["payment_mode_id"] = False
if not full_in_kind:
move_vals = donation._prepare_donation_move()
# when we have a full in-kind donation: no account move
if move_vals:
move = self.env["account.move"].create(move_vals)
move.action_post()
vals["move_id"] = move.id
else:
donation.message_post(
body=_("Full in-kind donation: no account move generated")
)
receipt = donation.generate_each_tax_receipt()
if receipt:
vals["tax_receipt_id"] = receipt.id
donation.write(vals)
if donation.bank_statement_line_id:
donation._reconcile_donation_from_bank_statement()
donation.partner_id._update_donor_rank()
return
def generate_each_tax_receipt(self):
self.ensure_one()
receipt = False
if (
self.tax_receipt_option == "each"
and not self.tax_receipt_id
#and not self.company_currency_id.is_zero(self.tax_receipt_total)
):
receipt_vals = self._prepare_each_tax_receipt()
receipt = self.env["donation.tax.receipt"].create(receipt_vals)
return receipt

@ -0,0 +1,240 @@
<?xml version="1.0"?>
<odoo>
<template id="gn_donations.report_donationtaxreceipt_document" inherit_id="donation_base.report_donationtaxreceipt_document">
<xpath expr="." position="replace">
<t t-foreach="docs" t-as="o">
<t t-call="gn_donations.internal_layout">
<div class="page" style="page-break-after: always;">
<style>
*{
font-family: 'Comic Sans MS', arial;
}
.pointille{
border-bottom: 2px dotted black;
}
.mtl{
text-decoration: underline dotted;
}
.fond-color{
background-color: #f8f4fc;
}
div{
font-size: 25px
}
</style>
<div class="row">
<div class="col-3 mt-4">
Cerfa n° 11580*04
</div>
<div class="col-6 text-center">
<h1>Reçu au titre des dons à certains organismes dintérêt général</h1>
<p class="font-italic" >Article 200, 238 bis et 978 du code général des impôts (CGI)</p>
</div>
<div class="col-3">
Numéro dordre du reçu <span style="border: 1px solid black;" t-field="o.number"/>
</div>
</div>
<div class="border border-2 mt-2 p-2">
<h3 class="bg-secondary text-center font-weight-bold">Bénéficiaire des versements</h3>
<div class="row">
<div class="col-12">
<h4 class="font-weight-bold">Nom ou dénomination :</h4>
<p class="pointille text-dark fond-color" t-field="o.company_id.name"/>
</div>
</div>
<t t-set="street_parts" t-value="o.company_id.street.split(None, 1)"/>
<t t-set="street_part1" t-value="street_parts[0]"/>
<t t-set="street_part2" t-value="street_parts[1] if len(street_parts) &gt; 1 else ''"/>
<div class="row">
<div class="mt-2 col-12">
<h4 class="font-weight-bold">Adresse :</h4>
</div>
</div>
<div class="row">
<div class="col-4">
<p>Numéro: <span t-esc="street_part1"/><hr style="border-style:dotted; background-color: black;"/></p>
</div>
<div class="col-9">
<p>Rue: <span class="pointille" t-esc="street_part2"/></p>
</div>
</div>
<div class="row">
<div class="col-4">
<p>Code Postal: <span class="pointille" t-field="o.company_id.zip"/></p>
</div>
<div class="col-9">
<p>Commune: <span class="pointille" t-field="o.company_id.city"/></p>
</div>
</div>
<h4 class="mt-3 font-weight-bold">Objet: </h4>
<p class="mtl" t-field="o.company_id.partner_id.comment" />
<hr style="height: 2px; width: 100%; background-color: black;"/>
<h4 class="font-weight-bold">Cochez la case concernée (1) :</h4>
<div>
<t t-foreach="[
('option1', 'Association ou fondation reconnue dutilité publique par décret en date du ...... ...... ...... publié au Journal officiel du ...... ...... ....... ou association située dans le département de la Moselle, du Bas-Rhin ou du Haut-Rhin dont la mission a été reconnue dutilité publique par arrêté en date du ...... ...... ......'),
('option2', 'Fondation universitaire ou fondation partenariale mentionnées respectivement aux articles L. 719-12 et L. 719-13 du code de léducation'),
('option3', 'Fondation dentreprise'),
('option4', 'Oeuvre ou organisme dintérêt général'),
('option5', 'Musée de France'),
('option6', 'Etablissement denseignement supérieur ou denseignement artistique public ou privé, dintérêt général, à but non lucratif'),
('option7', 'Organisme ayant pour objectif exclusif de participer financièrement à la création dentreprises'),
('option8', 'Association cultuelle ou de bienfaisance et établissement public reconnus dAlsaceMoselle'),
('option9', 'Organisme ayant pour activité principale lorganisation de festivals'),
('option10', 'Association fournissant gratuitement une aide alimentaire ou des soins médicaux à des personnes en difficultés ou favorisant leur logement'),
('option11', 'Fondation du patrimoine ou fondation ou association qui affecte irrévocablement les dons à la Fondation du patrimoine, en vue de subventionner les travaux prévus par les conventions conclues entre la Fondation du patrimoine et les propriétaires des immeubles (article L. 143-2-1 du code du patrimoine)'),
('option12', 'Etablissement de recherche public ou privé, dintérêt général, à but non lucratif'),
('option13', 'Entreprise dinsertion ou entreprise de travail temporaire dinsertion (articles L. 5132-5 et L. 5132-6 du code du travail)'),
('option14', 'Association intermédiaire (article L.5132-7 du code du travail)'),
('option15', 'Ateliers et chantiers dinsertion (article L.5132-15 du code du travail)'),
('option16', 'Entreprises adaptées (article L.5213-13 du code du travail)'),
('option17', 'Société ou organisme agrée de recherche scientifique ou technique (2)'),
('option18', 'Autres organismes :') ]" t-as="option">
<div>
<input type="checkbox" t-if="o.company_id.company_registry == option[1]" checked="checked"/>
<input type="checkbox" t-if="o.company_id.company_registry != option[1]"/>
<span t-raw="option[1]"/>
</div>
</t>
</div>
</div>
<div>
<span>(1) ou nindiquez que les renseignements concernant lorganisme</span>
<span>(2) dons effectués par les entreprises</span>
</div>
</div>
<div class="page" style="page-break-after: always;">
<div class="border border-2 p-2">
<h3 class="bg-secondary text-center font-weight-bold">Donateur</h3>
<div class="row">
<div class="col-6">
<p class="mb-1">Nom:</p>
<t t-set="uppercase_words" t-value="[word for word in o.partner_id.name.split() if word.isupper()]"/>
<t t-foreach="uppercase_words" t-as="word">
<p class="pointille" t-esc="word"/><span> </span>
</t>
</div>
<div class="col-6">
<p class="mb-1">Prénoms:</p>
<t t-set="first_letter_uppercase_words" t-value="[word for word in o.partner_id.name.split() if word[0].isupper() and not word.isupper()]"/>
<t t-foreach="first_letter_uppercase_words" t-as="word">
<p class="pointille" t-esc="word"/><span> </span>
</t>
</div>
</div>
<div class="row">
<div class="col-12 mt-4">
<p class="mb-1">Adresse :</p>
<p class="pointille" t-esc="o.partner_id.street + (o.partner_id.street2 and '; ' + o.partner_id.street2 or '')"/>
</div>
</div>
<div class="row mt-3">
<div class="col-3">
<p>Code Postal:<span class="pointille" t-field="o.partner_id.zip" /></p>
</div>
<div class="col-9">
<p>Commune: <span class="pointille" t-field="o.partner_id.city"/></p>
</div>
</div>
</div>
<div class="border border-2 mt-4 p-2">
<p>Le bénéficiaire reconnaît avoir reçu au titre des dons et versements ouvrant droit à réduction dimpôt, la somme de :
<span style="border: 1px solid black;" t-esc="o.amount"/> Euros
</p>
<p>Somme en toutes lettres : <span t-esc="o.amount_in_words" /></p>
<p>Date du versement ou du don : <span t-field="o.date" t-options="{'date_format': 'dd MM yyyy'}"/></p>
<p>Le bénéficiaire certifie sur lhonneur que les dons et versements quil reçoit ouvrent droit à la réduction dimpôt prévue à larticle (3) :
<t t-foreach="[ ('200 du CGI', 'person'), ('238 bis du CGI', 'company'), ('978 du CGI', 'other') ]" t-as="option">
<input type="checkbox" t-if="o.partner_id.company_type == option[1]" checked="checked"/>
<input type="checkbox" t-if="o.partner_id.company_type != option[1]"/>
<span t-raw="option[0]"/>
</t>
<hr style="height: 2px; width: 100%; background-color: black;"/>
</p>
<p class="font-weight-bold">Forme du don : </p>
<p>
<t t-foreach="[
('option1', 'Acte authentique'),
('option2', 'Acte sous seing privé'),
('option3', 'Déclaration de don manuel'),
('option4', 'Autres')
]"
t-as="option">
<input type="checkbox" t-if="option[0] == 'option3'" checked="checked"/>
<input type="checkbox" t-if="option[0] != 'option3'"/>
<span t-raw="option[1]"/>
</t>
<hr style="height: 2px; width: 100%; background-color: black; margin-top: 20px;" />
</p>
<p class="font-weight-bold">Nature du don : </p>
<p>
<t t-foreach="[
('option1', 'Numéraire'),
('option2', 'Titres de sociétés cotés'),
('option3', 'Autres (4)'),
]"
t-as="option">
<input type="checkbox" t-if="option[0] == 'option3'" checked="checked"/>
<input type="checkbox" t-if="option[0] != 'option3'"/>
<span t-raw="option[1]"/>
</t>
<hr style="height: 2px; width: 100%; background-color: black;"/>
</p>
<p class="font-weight-bold">En cas de don en numéraire, mode de versement du don : </p>
<p>
<t t-foreach="[
('Espèces (entrant)', 'Remise despèces'),
('Chèque', 'Chèque'),
('Virement', 'Virement, prélèvement, carte bancaire'),
]"
t-as="option">
<input type="checkbox" t-if="option[0] == 'o.payment_mode_id'" checked="checked"/>
<input type="checkbox" t-if="option[0] != 'o.payment_mode_id'"/>
<span t-raw="option[1]"/>
</t>
</p>
</div>
<div>
<div class="row">
<p>(3) Lorganisme bénéficiaire peut cocher une ou plusieurs cases. Lorganisme bénéficiaire peut, en application de larticle L. 80 C du livre des procédures fiscales, demander à ladministration sil relève de lune des catégories dorganismes mentionnées aux articles 200 et 238 bis du code général des impôts. Il est rappelé que la délivrance irrégulière de reçus fiscaux par lorganisme bénéficiaire et susceptible de donner lieu, en application des dispositions de larticle 1740 A du code général des impôts, à une amende fiscale égale à 25% des sommes indûment mentionnées sur ces documents.</p>
<p>(4) Notamment : abandon de revenus ou de produits ; frais engagés par les bénévoles, dont ils renoncent expressément au remboursement.</p>
</div>
<div class="border border-2">
<p style="text-align: right;">Date et signature</p>
</div>
</div>
</div>
</t>
</t>
</xpath>
</template>
</odoo>

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<odoo>
<template id="gn_donations.internal_layout">
<div class="header">
</div>
<div class="article" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-raw="0"/>
</div>
</template>
</odoo>
Loading…
Cancel
Save