Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions sale_partner_primeship/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
======================
Sale Partner Primeship
======================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4a8b2bd09f624001cb742f00bfe88a35f7c7ba7dceca566dae0bb4ed0d49e56e
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_partner_primeship
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_partner_primeship
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module introduces a membership concept called "primeship" for
partners.

Primeship functions as a straightforward, user-friendly membership
system that activates automatically when a sale order containing a
primeship activation product is confirmed. The membership duration is
customizable and defined in months.

The system automatically deactivates the primeship when the specified
duration expires. Users can easily verify a customer's active primeship
status and implement conditional actions based on this membership state.

Key features:

- Simple membership management for partners
- Automatic activation through sales orders
- Configurable duration in months
- Automatic expiration handling
- Easy status verification for conditional business logic

**Table of contents**

.. contents::
:local:

Usage
=====

To configure a product that activates primeship upon sale order
confirmation:

- Edit the desired product and set its type to "Service"
- Navigate to the "Sales" tab in the product form
- Enable the "Activates primeship" checkbox
- Define the primeship duration (in months)

|image1|

You can easily view a customer's current primeship status directly from
their partner record:

|image2|

Clicking on the primeship status widget will redirect you to a detailed
list view showing all primeship records for that specific customer:

|image3|

This interface allows you to track membership history, expiration dates,
and manage customer primeship records efficiently.

.. |image1| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_partner_primeship/static/description/primeship-product.png
.. |image2| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_partner_primeship/static/description/partner-with-primeship.png
.. |image3| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_partner_primeship/static/description/primeship-partner-view.png

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_partner_primeship%0Aversion:%2018.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
-------

* Akretion

Contributors
------------

- `Akretion <https://www.akretion.com>`__:

- Florian Mounier
- Kevin Roche
- Olivier Nibart

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.

.. |maintainer-nayatec| image:: https://github.com/nayatec.png?size=40px
:target: https://github.com/nayatec
:alt: nayatec
.. |maintainer-paradoxxxzero| image:: https://github.com/paradoxxxzero.png?size=40px
:target: https://github.com/paradoxxxzero
:alt: paradoxxxzero

Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-nayatec| |maintainer-paradoxxxzero|

This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_partner_primeship>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions sale_partner_primeship/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
22 changes: 22 additions & 0 deletions sale_partner_primeship/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2021 Akretion - Florian Mounier
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Sale Partner Primeship",
"summary": """Allow you to manage time limited prime memberships
and prime membership activation products.""",
"version": "18.0.1.0.0",
"author": "Akretion, Odoo Community Association (OCA)",
"maintainers": ["nayatec", "paradoxxxzero"],
"website": "https://github.com/OCA/sale-workflow",
"category": "Sales",
"depends": ["sale", "account_invoice_start_end_dates"],
"data": [
"views/product_template_views.xml",
"views/sale_primeship_views.xml",
"views/res_partner_views.xml",
"security/ir.model.access.csv",
"security/sale_partner_primeship.xml",
"data/ir_cron.xml",
],
"license": "AGPL-3",
}
17 changes: 17 additions & 0 deletions sale_partner_primeship/data/ir_cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_check_primeship_expired" model="ir.cron">
<field name="name">Check for Expired Partner Primeships</field>
<field name="model_id" ref="sale_partner_primeship.model_res_partner" />
<field name="state">code</field>
<field name="code">model._check_expired_primeships()</field>
<field name="active" eval="True" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field
name="nextcall"
eval="(datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d 00:00:00')"
/>
</record>
</odoo>
1 change: 1 addition & 0 deletions sale_partner_primeship/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import product_template, res_partner, sale_primeship, sale_order
13 changes: 13 additions & 0 deletions sale_partner_primeship/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2021 Akretion France (http://www.akretion.com/)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ProductTemplate(models.Model):
_inherit = "product.template"

primeship_activation = fields.Boolean(string="Activates primeship", default=False)
primeship_duration = fields.Integer(
string="Primeship duration (in months)", default=12
)
47 changes: 47 additions & 0 deletions sale_partner_primeship/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2021 Akretion France (http://www.akretion.com/)
# Copyright 2023 Akretion France (http://www.akretion.com/)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class ResPartner(models.Model):
_inherit = "res.partner"

primeship_ids = fields.One2many(
comodel_name="sale.primeship",
inverse_name="partner_id",
required=True,
)

# store True allow to filter on active_primeship.
# There is a cron job to set it to False when the customer has no active
# primeship anymore.
active_primeship = fields.Boolean(
compute="_compute_active_primeship",
store=True,
)
primeship_count = fields.Integer(
string="Primeships Count", compute="_compute_primeship_count"
)

@api.depends(
"commercial_partner_id.primeship_ids",
"commercial_partner_id.primeship_ids.current",
)
def _compute_active_primeship(self):
for record in self:
record.active_primeship = (
record.commercial_partner_id.primeship_ids.filtered("current")
)

@api.depends("commercial_partner_id.primeship_ids")
def _compute_primeship_count(self):
for record in self:
record.primeship_count = len(record.commercial_partner_id.primeship_ids)

@api.model
def _check_expired_primeships(self):
self.with_context(
active_test=False,
).search([("active_primeship", "=", True)])._compute_active_primeship()
111 changes: 111 additions & 0 deletions sale_partner_primeship/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Copyright 2021 Akretion France (http://www.akretion.com/)
# Copyright 2024 Akretion France (http://www.akretion.com/)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from dateutil.relativedelta import relativedelta

from odoo import api, fields, models


class SaleOrder(models.Model):
_inherit = "sale.order"

def _action_confirm(self):
super()._action_confirm()
sale_primeship = self.env["sale.primeship"]
for record in self:
partner = record.partner_id.commercial_partner_id

for line in record.order_line:
product_template = line.product_template_id

if product_template.primeship_activation:
# We do not take in account product_qty for now:
duration = product_template.primeship_duration
start = fields.Date.context_today(record)
end = start + relativedelta(months=duration)

# If we have already some primeships, we need to check for overlaps
if partner.primeship_ids:
# We assume no overlaps between partner primeships
for primeship in partner.primeship_ids.sorted("start_date"):
if primeship.overlaps(start, end):
start = primeship.end_date
end = start + relativedelta(months=duration)

vals = {
"start_date": start,
"end_date": end,
"partner_id": partner.id,
"order_line_id": line.id,
# this is to reactivate a maybe deactivated existing primeship
"active": True,
}
if line.primeship_id:
# Hm... something seems to have gone wrong here,
# but we handle it nonetheless.
line.primeship_id.write(vals)
else:
# We may have a deactivated primeship because of an order
# cancellation.
primeship = sale_primeship.with_context(
active_test=False,
).search([("order_line_id", "=", line.id)])
if primeship:
primeship.write(vals)
else:
sale_primeship.create(vals)

return True

def _action_cancel(self):
rv = super()._action_cancel()
for record in self:
record.order_line.mapped("primeship_id").active = False
return rv


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

primeship_id = fields.Many2one(
string="Primeships",
comodel_name="sale.primeship",
compute="_compute_primeship_id",
inverse="_inverse_primeship_id",
)

# One2one impl
primeship_ids = fields.One2many(
comodel_name="sale.primeship", inverse_name="order_line_id"
)

@api.depends("primeship_ids")
def _compute_primeship_id(self):
for record in self:
record.primeship_id = record.primeship_ids[:1]

def _inverse_primeship_id(self):
for record in self:
if record.primeship_ids:
primeship = record.env["sale.primeship"].browse(
record.primeship_ids[0].id
)
primeship.order_line_id = record

record.primeship_id.order_line_id = record

def _prepare_invoice_line(self, **optional_values):
"""Update invoice start/end dates.
Set invoice start/end dates to primeship start/end dates
In case of multi quantity, this assumes continuous date ranges."""
self.ensure_one()
res = super()._prepare_invoice_line(**optional_values)
if self.primeship_id:
res.update(
{
"start_date": self.primeship_id.start_date,
"end_date": self.primeship_id.end_date,
}
)
return res
Loading