Skip to content

Commit

Permalink
Create lcc_stats_api module
Browse files Browse the repository at this point in the history
  • Loading branch information
jojolb authored and vaab committed Feb 9, 2024
1 parent a41338e commit 0464efc
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
.idea/
55 changes: 55 additions & 0 deletions lcc_stats_api/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
=============================
lcc_stats_api
=============================

Lokavaluto statistics module on Local Currency.
It's part of Lokavaluto Project (https://lokavaluto.fr)

Installation
============

Just install lcc_stats_api, all dependencies
will be installed by default.

Known issues / Roadmap
======================

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

Bugs are tracked on `GitHub Issues
<https://github.com/Lokavaluto/lokavaluto-addons/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.

Credits
=======

Images
------

* Lokavaluto: `Icon <https://lokavaluto.fr/web/image/res.company/1/logo?unique=f3db262>`_.

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

* Stéphan SAINLEGER <https://github.com/stephansainleger>
* Nicolas JEUDY <https://github.com/njeudy>
* Lokavaluto Teams

Funders
-------

The development of this module has been financially supported by:

* Lokavaluto (https://lokavaluto.fr)
* Mycéliandre (https://myceliandre.fr)
* Elabore (https://elabore.coop)

Maintainer
----------

This module is maintained by LOKAVALUTO.

LOKAVALUTO, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and ecosystem for local complementary currency organizations.
4 changes: 4 additions & 0 deletions lcc_stats_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import controllers

from . import datamodel
from . import services
21 changes: 21 additions & 0 deletions lcc_stats_api/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "lcc_stats_api",
"summary": "REST Odoo Backend for getting stats about local complementary currency",
"author": "Lokavaluto",
"website": "https://lokavaluto.fr",
"category": "Website",
"version": "12.0.1.0.0",
# any module necessary for this one to work correctly
"depends": [
"base",
"base_rest",
"auth_api_key",
"base_rest_datamodel",
"membership",
"lcc_lokavaluto_app_connection",
],
# always loaded
"data": [],
# only loaded in demonstration mode
"demo": [],
}
1 change: 1 addition & 0 deletions lcc_stats_api/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import rest_api
13 changes: 13 additions & 0 deletions lcc_stats_api/controllers/rest_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo.addons.base_rest.controllers import main


class LokavalutoStatsPublicApiController(main.RestController):
_root_path = "/lokavaluto_api/public/stats/"
_collection_name = "lokavaluto.public.stats.services"
_default_auth = "public"


class LokavalutoStatsPrivateApiController(main.RestController):
_root_path = "/lokavaluto_api/private/stats/"
_collection_name = "lokavaluto.private.stats.services"
_default_auth = "api_key"
1 change: 1 addition & 0 deletions lcc_stats_api/datamodel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import stats_filter
22 changes: 22 additions & 0 deletions lcc_stats_api/datamodel/stats_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from marshmallow import fields

from odoo.addons.datamodel.core import Datamodel


class StatsFilter(Datamodel):
"""
Describes stats API query parameters
"""

_name = "stats.filter"

start_date = fields.Date(
required=False,
allow_none=True,
description="date included, format YYYY-MM-DD",
)
end_date = fields.Date(
required=False,
allow_none=True,
description="date included, format YYYY-MM-DD",
)
4 changes: 4 additions & 0 deletions lcc_stats_api/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import public_stats_mlcc
from . import private_stats_partner

__api_version__ = 1
94 changes: 94 additions & 0 deletions lcc_stats_api/services/build_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from typing_extensions import TypedDict

from ..datamodel.stats_filter import StatsFilter


# Generic stats about currency
class CurrencyStats(TypedDict):
eur_to_mlcc: float
mlcc_to_eur: float
mlcc_circulating: float


# Generic validator for an API returning currency stats
currency_stats_validator = {
"eur_to_mlcc": {"type": "float", "required": True, "empty": False},
"mlcc_to_eur": {"type": "float", "required": True, "empty": False},
"mlcc_circulating": {"type": "float", "required": True, "empty": False},
}


# Stats about partners participating to local currency
class PartnersStats(TypedDict):
nb_individuals: int
nb_companies: int


# validator for an API returning partners stats
partners_stats_validator = {
"nb_individuals": {"type": "integer", "required": True, "empty": False},
"nb_companies": {"type": "integer", "required": True, "empty": False},
}


# Method to compute circulating currency
def build_currency_stats_from_invoices(
invoices_model, partner_id: int = None, stats_filter: StatsFilter = None
) -> CurrencyStats:
"""
Build stats about MLCC from invoices
"""

# Look for paid invoices with LCC products
domain_invoices = [
("state", "=", "paid"),
("type", "in", ["out_invoice", "in_invoice"]),
("has_numeric_lcc_products", "=", True),
]

# Filter for a specific partner if specified
if partner_id:
domain_invoices.append(("partner_id", "=", partner_id))

# Filter date if specified
if stats_filter and stats_filter.start_date:
domain_invoices.append(("date_invoice", ">=", stats_filter.start_date))

if stats_filter and stats_filter.end_date:
domain_invoices.append(("date_invoice", "<=", stats_filter.end_date))

invoices = invoices_model.search(domain_invoices)
mlcc_to_eur = 0.00
eur_to_mlcc = 0.00
for invoice in invoices:
if invoice.type == "out_invoice":
eur_to_mlcc += invoice.amount_total
else:
mlcc_to_eur += invoice.amount_total

return CurrencyStats(
eur_to_mlcc=eur_to_mlcc,
mlcc_to_eur=mlcc_to_eur,
mlcc_circulating=eur_to_mlcc - mlcc_to_eur,
)


# Method to compute the number of partners participating in the currency
def build_currency_partners_stats(partners_model) -> PartnersStats:
# Search active members only
domain_partners = [
(
"membership_state",
"in",
["invoiced", "paid", "free"],
),
("is_main_profile", "=", True),
("active", "=", True),
]

partners = partners_model.search(domain_partners)

individuals = len([p for p in partners if p.is_company == False])
companies = len(partners) - individuals

return PartnersStats(nb_individuals=individuals, nb_companies=companies)
88 changes: 88 additions & 0 deletions lcc_stats_api/services/private_stats_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import logging

from odoo.exceptions import AccessError
from odoo.addons.base_rest import restapi
from odoo.addons.base_rest_datamodel.restapi import Datamodel
from odoo.addons.component.core import Component
from odoo.http import request

from .build_stats import (
CurrencyStats,
build_currency_stats_from_invoices,
currency_stats_validator,
)
from ..datamodel.stats_filter import StatsFilter

_logger = logging.getLogger(__name__)


class PrivateStatsPartnerService(Component):
_inherit = "base.rest.service"
_name = "private_stats_partner.service"
_usage = "partner"
_collection = "lokavaluto.private.stats.services"
_description = """
MLCC Partner Private Stats Services
Get private statistics of a local currency partner.
"""

# The following method are 'public' and can be called from the controller.

@restapi.method(
[(["/<int:id>/get", "/<int:id>"], "GET")],
input_param=Datamodel("stats.filter"),
)
def get(self, _id, stats_filter: StatsFilter) -> CurrencyStats:
"""
Get a partner MLCC stats.
"""

# Check that user is accessing its own data
user_api_key = request.httprequest.headers["Api-Key"]
user = (
self.env["auth.api.key"]
.sudo()
.search([("key", "=", user_api_key)], limit=1)
)
partner = (
self.env["res.partner"]
.sudo()
.search([("odoo_user_id", "=", user.user_id.id)])
)
if _id != partner.id and _id != partner.public_profile_id.id:
raise AccessError(
f"{partner.name} not allowed to access data of partner ID {_id}"
)

# Get currency stats based on partner's invoices
currency_stats: CurrencyStats = build_currency_stats_from_invoices(
self.env["account.invoice"], partner_id=_id, stats_filter=stats_filter
)

return currency_stats

##########################################################
# Validators
##########################################################
def _validator_return_get(self):
res = {
**currency_stats_validator,
}
return res

def _get_openapi_default_parameters(self):
defaults = super(
PrivateStatsPartnerService, self
)._get_openapi_default_parameters()
defaults.append(
{
"name": "API-KEY",
"in": "header",
"description": "Auth API key - get a token using API /lokavaluto_api/public/auth/authenticate",
"required": True,
"schema": {"type": "string"},
"style": "simple",
}
)

return defaults
62 changes: 62 additions & 0 deletions lcc_stats_api/services/public_stats_mlcc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging

from odoo.addons.base_rest import restapi
from odoo.addons.base_rest_datamodel.restapi import Datamodel
from odoo.addons.component.core import Component

from .build_stats import (
build_currency_stats_from_invoices,
CurrencyStats,
currency_stats_validator,
PartnersStats,
build_currency_partners_stats,
partners_stats_validator,
)
from ..datamodel.stats_filter import StatsFilter

_logger = logging.getLogger(__name__)


class MlccStats(CurrencyStats):
nb_individuals: int
nb_companies: int


class PublicStatsMlccService(Component):
_inherit = "base.rest.service"
_name = "public_stats.service"
_usage = "mlcc"
_collection = "lokavaluto.public.stats.services"
_description = """
MLCC Public Stats Services
Get public statistics about a local currency
"""

# The following method are 'public' and can be called from the controller.

@restapi.method(
[(["/get", "/"], "GET")],
input_param=Datamodel("stats.filter"),
)
def get(self, stats_filter: StatsFilter) -> MlccStats:
"""
Get MLCC public stats.
"""

# Get currency stats based on all invoices
currency_stats: CurrencyStats = build_currency_stats_from_invoices(
self.env["account.invoice"].sudo(), stats_filter=stats_filter
)

# Get partners stats
partner_stats: PartnersStats = build_currency_partners_stats(
self.env["res.partner"].sudo()
)
return MlccStats(**currency_stats, **partner_stats)

##########################################################
# Validators
##########################################################
def _validator_return_get(self):
res = {**currency_stats_validator, **partners_stats_validator}
return res

0 comments on commit 0464efc

Please sign in to comment.