RemiFr82
1 year ago
11 changed files with 441 additions and 0 deletions
-
101membership_initial_discount/README.rst
-
2membership_initial_discount/__init__.py
-
17membership_initial_discount/__manifest__.py
-
65membership_initial_discount/i18n/fr.po
-
6membership_initial_discount/models/__init__.py
-
65membership_initial_discount/models/account_move_line.py
-
18membership_initial_discount/models/membership.py
-
26membership_initial_discount/models/product_product.py
-
58membership_initial_discount/models/product_template.py
-
65membership_initial_discount/models/res_partner.py
-
18membership_initial_discount/views/product_template_views.xml
@ -0,0 +1,101 @@ |
|||||
|
=========================== |
||||
|
Initial fee for memberships |
||||
|
=========================== |
||||
|
|
||||
|
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
||||
|
!! This file is generated by oca-gen-addon-readme !! |
||||
|
!! changes will be overwritten. !! |
||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
||||
|
|
||||
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png |
||||
|
:target: https://odoo-community.org/page/development-status |
||||
|
:alt: Beta |
||||
|
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png |
||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fvertical--association-lightgray.png?logo=github |
||||
|
:target: https://github.com/OCA/vertical-association/tree/14.0/membership_initial_fee |
||||
|
:alt: OCA/vertical-association |
||||
|
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png |
||||
|
:target: https://translation.odoo-community.org/projects/vertical-association-14-0/vertical-association-14-0-membership_initial_fee |
||||
|
:alt: Translate me on Weblate |
||||
|
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png |
||||
|
:target: https://runbot.odoo-community.org/runbot/208/14.0 |
||||
|
:alt: Try me on Runbot |
||||
|
|
||||
|
|badge1| |badge2| |badge3| |badge4| |badge5| |
||||
|
|
||||
|
Charge an initial fee when a partner is invoiced for the first time with a |
||||
|
member product. |
||||
|
|
||||
|
**Table of contents** |
||||
|
|
||||
|
.. contents:: |
||||
|
:local: |
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
Define a member product, and select 'Fixed amount' or 'Percentage of the price' |
||||
|
for invoicing an extra charge in the first invoice that is created with this |
||||
|
member product. |
||||
|
|
||||
|
By default, a line with the description *Membership initial fee*. If you want |
||||
|
to change this text, you can set a different sale description in the product |
||||
|
used for the fee. |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Add initial fee information to membership analysis. |
||||
|
* Add some criteria for adding initial fee: |
||||
|
* Add initial fee if last membership ended x weeks/months/years ago |
||||
|
* Add initial fee if last membership product_id is different |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/vertical-association/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed |
||||
|
`feedback <https://github.com/OCA/vertical-association/issues/new?body=module:%20membership_initial_fee%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
||||
|
|
||||
|
Do not contact contributors directly about support or help with technical issues. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Authors |
||||
|
~~~~~~~ |
||||
|
|
||||
|
* Tecnativa |
||||
|
|
||||
|
Contributors |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
* `Tecnativa <https://www.tecnativa.com>`__: |
||||
|
|
||||
|
* Pedro M. Baeza |
||||
|
* Rafael Blasco |
||||
|
* David Vidal |
||||
|
|
||||
|
* `Onestein <https://onestein.eu>`__: |
||||
|
|
||||
|
* Andrea Stirpe |
||||
|
|
||||
|
Maintainers |
||||
|
~~~~~~~~~~~ |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
This module is part of the `OCA/vertical-association <https://github.com/OCA/vertical-association/tree/14.0/membership_initial_fee>`_ project on GitHub. |
||||
|
|
||||
|
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
@ -0,0 +1,2 @@ |
|||||
|
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0 |
||||
|
from . import models |
@ -0,0 +1,17 @@ |
|||||
|
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0 |
||||
|
|
||||
|
{ |
||||
|
"name": "Initial discount on 1st membership", |
||||
|
"version": "1.0.0", |
||||
|
"license": "AGPL-3", |
||||
|
"category": "Association", |
||||
|
"author": "RemiFr82", |
||||
|
"website": "https://remifr82.me", |
||||
|
# "website": "https://github.com/OCA/vertical-association", |
||||
|
"depends": [ |
||||
|
"membership", |
||||
|
], |
||||
|
"data": [ |
||||
|
"views/product_template_views.xml", |
||||
|
], |
||||
|
} |
@ -0,0 +1,65 @@ |
|||||
|
# Translation of Odoo Server. |
||||
|
# This file contains the translation of the following modules: |
||||
|
# * membership_initial_discount |
||||
|
# |
||||
|
# Translators: |
||||
|
# OCA Transbot <transbot@odoo-community.org>, 2017 |
||||
|
msgid "" |
||||
|
msgstr "" |
||||
|
"Project-Id-Version: Odoo Server 10.0\n" |
||||
|
"Report-Msgid-Bugs-To: \n" |
||||
|
"POT-Creation-Date: 2017-06-08 02:41+0000\n" |
||||
|
"PO-Revision-Date: 2022-05-30 18:05+0000\n" |
||||
|
"Last-Translator: Abdourahmane Wone <abdourahmanewone@gmail.com>\n" |
||||
|
"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" |
||||
|
"Language: fr\n" |
||||
|
"MIME-Version: 1.0\n" |
||||
|
"Content-Type: text/plain; charset=UTF-8\n" |
||||
|
"Content-Transfer-Encoding: \n" |
||||
|
"Plural-Forms: nplurals=2; plural=n > 1;\n" |
||||
|
"X-Generator: Weblate 4.3.2\n" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: model:ir.model.fields,field_description:membership_initial_discount.field_product_product__fixed_discount |
||||
|
#: model:ir.model.fields,field_description:membership_initial_discount.field_product_template__fixed_discount |
||||
|
msgid "Discount amount" |
||||
|
msgstr "Montant de la remise" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: selection:product.template,initial_discount:0 |
||||
|
msgid "Fixed amount" |
||||
|
msgstr "Montant fixe" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: model:ir.model.fields,field_description:membership_initial_discount.field_product_product__initial_discount |
||||
|
#: model:ir.model.fields,field_description:membership_initial_discount.field_product_template__initial_discount |
||||
|
#: model_terms:ir.ui.view,arch_db:membership_initial_discount.membership_products_form_initial_discount |
||||
|
msgid "Initial discount" |
||||
|
msgstr "Remise initiale" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: model:ir.model,name:membership_initial_discount.model_account_invoice_line |
||||
|
msgid "Invoice Line" |
||||
|
msgstr "Ligne de facture" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: selection:product.template,initial_discount:0 |
||||
|
msgid "No initial discount" |
||||
|
msgstr "Pas de remise initiale" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: model:ir.model.fields,field_description:membership_initial_discount.field_product_product__percentage_discount |
||||
|
#: model:ir.model.fields,field_description:membership_initial_discount.field_product_template__percentage_discount |
||||
|
msgid "Discount (%)" |
||||
|
msgstr "Remise en %" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: selection:product.template,initial_discount:0 |
||||
|
msgid "Percentage of the price" |
||||
|
msgstr "Pourcentage du prix" |
||||
|
|
||||
|
#. module: membership_initial_discount |
||||
|
#: model:ir.model,name:membership_initial_discount.model_product_template |
||||
|
msgid "Product Template" |
||||
|
msgstr "Modèle d'article" |
||||
|
|
@ -0,0 +1,6 @@ |
|||||
|
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0 |
||||
|
from . import account_move_line |
||||
|
from . import membership |
||||
|
from . import product_product |
||||
|
from . import product_template |
||||
|
from . import res_partner |
@ -0,0 +1,65 @@ |
|||||
|
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0 |
||||
|
|
||||
|
from odoo import api, models, _ |
||||
|
|
||||
|
|
||||
|
class AccountMoveLine(models.Model): |
||||
|
_inherit = "account.move.line" |
||||
|
|
||||
|
def _get_computed_price_unit(self): |
||||
|
res = super()._get_computed_price_unit() |
||||
|
if self.move_id.is_invoice() and self.initial_membership_check(): |
||||
|
product = self.product_id |
||||
|
if product.initial_discount == "fixed" and product.fixed_discount: |
||||
|
res -= product.fixed_discount |
||||
|
return res |
||||
|
|
||||
|
@api.onchange("product_id") |
||||
|
def _onchange_product_id(self): |
||||
|
super()._onchange_product_id() |
||||
|
for line in self: |
||||
|
if not line.product_id or line.display_type in ( |
||||
|
"line_section", |
||||
|
"line_note", |
||||
|
): |
||||
|
continue |
||||
|
if line.move_id.is_invoice() and line.initial_membership_check(): |
||||
|
product = line.product_id |
||||
|
if product.initial_discount == "percent" and product.percent_discount: |
||||
|
line.discount = product.percent_discount |
||||
|
else: |
||||
|
pass |
||||
|
|
||||
|
def initial_membership_check(self): |
||||
|
""" |
||||
|
Inherit this method to implement a custom method |
||||
|
to decide whether or not to create the initial discount |
||||
|
|
||||
|
:return: |
||||
|
""" |
||||
|
self.ensure_one() |
||||
|
product = self.product_id |
||||
|
if not product or not product.membership or product.initial_discount == "none": |
||||
|
return False |
||||
|
# If we are associated to another partner membership, evaluate that |
||||
|
# partner lines |
||||
|
partner = self.partner_id.associate_member or self.move_id.partner_id |
||||
|
# By default, partner to check is the partner of the invoice, but |
||||
|
# if a special method is found, overwritten in other modules, then |
||||
|
# the partner is got from that method |
||||
|
if hasattr(self, "_get_partner_for_membership"): # pragma: no cover |
||||
|
partner = self._get_partner_for_membership() |
||||
|
# See if partner has any membership line to decide whether or not |
||||
|
# to create the initial discount |
||||
|
member_lines = self.env["membership.membership_line"].search( |
||||
|
[ |
||||
|
("partner", "=", partner.id), |
||||
|
( |
||||
|
"account_invoice_line", |
||||
|
"not in", |
||||
|
[self.id or self._origin.id], |
||||
|
), |
||||
|
("state", "not in", ["none", "canceled"]), |
||||
|
] |
||||
|
) |
||||
|
return not bool(member_lines) |
@ -0,0 +1,18 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details. |
||||
|
|
||||
|
from odoo import api, models |
||||
|
|
||||
|
|
||||
|
class MembershipLine(models.Model): |
||||
|
_inherit = "membership.membership_line" |
||||
|
|
||||
|
@api.model_create_multi |
||||
|
def create(self, vals_list): |
||||
|
res = super().create(vals_list) |
||||
|
for line in res: |
||||
|
discount = line.account_invoice_line.discount |
||||
|
if discount: |
||||
|
line.member_price *= discount / 100.0 |
||||
|
|
||||
|
return res |
@ -0,0 +1,26 @@ |
|||||
|
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0 |
||||
|
from odoo import models, fields, api, _ |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
DISCOUNTS = [ |
||||
|
("none", _("No initial discount")), |
||||
|
("fixed", _("Fixed amount")), |
||||
|
("percent", _("Percentage of the price")), |
||||
|
] |
||||
|
|
||||
|
|
||||
|
class ProductProduct(models.Model): |
||||
|
_inherit = "product.product" |
||||
|
|
||||
|
@api.constrains("lst_price", "initial_discount", "fixed_discount") |
||||
|
def check_discount_fixed(self): |
||||
|
for product in self: |
||||
|
if ( |
||||
|
product.initial_discount == "fixed" |
||||
|
and product.lst_price <= product.initial_discount |
||||
|
): |
||||
|
raise ValidationError( |
||||
|
_( |
||||
|
"Fixed discount for 1st membership must be less than the product template or any of its variants sale price." |
||||
|
) |
||||
|
) |
@ -0,0 +1,58 @@ |
|||||
|
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0 |
||||
|
from odoo import models, fields, api, _ |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
DISCOUNTS = [ |
||||
|
("none", _("No initial discount")), |
||||
|
("fixed", _("Fixed amount")), |
||||
|
("percent", _("Percentage of the price")), |
||||
|
] |
||||
|
|
||||
|
|
||||
|
class ProductTemplate(models.Model): |
||||
|
_inherit = "product.template" |
||||
|
|
||||
|
initial_discount = fields.Selection( |
||||
|
selection=DISCOUNTS, |
||||
|
default="none", |
||||
|
string="Initial discount", |
||||
|
required=True, |
||||
|
) |
||||
|
fixed_discount = fields.Float( |
||||
|
string="Discount amount", |
||||
|
digits="Product Price", |
||||
|
) |
||||
|
percent_discount = fields.Float( |
||||
|
string="Discount (%)", |
||||
|
digits=(12, 2), |
||||
|
) |
||||
|
|
||||
|
@api.constrains("list_price", "initial_discount", "fixed_discount") |
||||
|
def check_fixed_discount(self): |
||||
|
for product in self: |
||||
|
if ( |
||||
|
product.initial_discount == "fixed" |
||||
|
and product.list_price <= product.fixed_discount |
||||
|
): |
||||
|
raise ValidationError( |
||||
|
_( |
||||
|
"Fixed discount for 1st membership must be less than the product template, or any of its variants, sale price." |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
@api.constrains("initial_discount", "percent_discount") |
||||
|
def check_percent_discount(self): |
||||
|
for product in self: |
||||
|
if product.initial_discount == "percent" and product.percent_discount < 0.0: |
||||
|
raise ValidationError( |
||||
|
_( |
||||
|
'Percent discount cannot handle a negative value.\nIf you wan to apply extra fees, please install "Membership initial fee" module (OCA/vertical-association)' |
||||
|
) |
||||
|
) |
||||
|
elif ( |
||||
|
product.initial_discount == "percent" |
||||
|
and product.percent_discount >= 100.0 |
||||
|
): |
||||
|
raise ValidationError( |
||||
|
_("Percent discount must handle a value smaller than 100%.") |
||||
|
) |
@ -0,0 +1,65 @@ |
|||||
|
from odoo import models, api, _ |
||||
|
from odoo.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
class ResPartner(models.Model): |
||||
|
_inherit = "res.partner" |
||||
|
|
||||
|
def initial_membership_check(self): |
||||
|
self.ensure_one() |
||||
|
partner = self.associate_member or self |
||||
|
member_lines = self.env["membership.membership_line"].search( |
||||
|
[ |
||||
|
("partner", "=", partner.id), |
||||
|
("state", "not in", ["none", "canceled"]), |
||||
|
] |
||||
|
) |
||||
|
return not bool(member_lines) |
||||
|
|
||||
|
def prepare_membership_invoice_vals(self, product, amount): |
||||
|
discount = 0.0 |
||||
|
if self.initial_membership_check(): |
||||
|
if product.initial_discount == "fixed" and product.fixed_discount: |
||||
|
print("fixed discount") |
||||
|
fixed_disc = product.fixed_discount |
||||
|
amount -= fixed_disc |
||||
|
elif product.initial_discount == "percent" and product.percent_discount: |
||||
|
print("percent discount") |
||||
|
discount = product.percent_discount |
||||
|
else: |
||||
|
pass |
||||
|
vals = { |
||||
|
"move_type": "out_invoice", |
||||
|
"partner_id": self.id, |
||||
|
"invoice_line_ids": [ |
||||
|
( |
||||
|
0, |
||||
|
None, |
||||
|
{ |
||||
|
"product_id": product.id, |
||||
|
"quantity": 1, |
||||
|
"price_unit": amount, |
||||
|
"discount": discount, |
||||
|
"tax_ids": [(6, 0, product.taxes_id.ids)], |
||||
|
}, |
||||
|
) |
||||
|
], |
||||
|
} |
||||
|
return vals |
||||
|
|
||||
|
def create_membership_invoice(self, product, amount): |
||||
|
"""Create Customer Invoice of Membership for partners.""" |
||||
|
invoice_vals_list = [] |
||||
|
for partner in self: |
||||
|
addr = partner.address_get(["invoice"]) |
||||
|
if partner.free_member: |
||||
|
raise UserError(_("Partner is a free Member.")) |
||||
|
if not addr.get("invoice", False): |
||||
|
raise UserError( |
||||
|
_("Partner doesn't have an address to make the invoice.") |
||||
|
) |
||||
|
invoice_vals_list.append( |
||||
|
partner.prepare_membership_invoice_vals(product, amount) |
||||
|
) |
||||
|
|
||||
|
return self.env["account.move"].create(invoice_vals_list) |
@ -0,0 +1,18 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<record id="membership_products_form_initial_discount" model="ir.ui.view"> |
||||
|
<field name="name">Membership Products (initial discount)</field> |
||||
|
<field name="model">product.template</field> |
||||
|
<field name="inherit_id" ref="membership.membership_products_form" /> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath expr="//group[field[@name='list_price']]" position="after"> |
||||
|
<group string="Initial discount"> |
||||
|
<field name="initial_discount" /> |
||||
|
<field name="fixed_discount" attrs="{'required': [('initial_discount', '=', 'fixed')], 'invisible': [('initial_discount', '!=', 'fixed')]}" /> |
||||
|
<field name="percent_discount" attrs="{'required': [('initial_discount', '=', 'percent')], 'invisible': [('initial_discount', '!=', 'percent')]}" /> |
||||
|
<field name="product_variant_ids" invisible="1" colspan="4"/> |
||||
|
</group> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue