<commit_before># Copyright 2017 <NAME>/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from collections import OrderedDict
from datetime import date, datetime
from operator import attrgetter
from uuid import uuid4

from dateutil.relativedelta import relativedelta
from psycopg2.extensions import AsIs

from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import safe_eval
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT

from odoo.addons.mozaik_tools.tools import (
    check_and_format_number,
    check_email,
    format_email,
    get_age,
)

_logger = logging.getLogger(__name__)

MEMBERSHIP_AVAILABLE_STATES = [
    ("draft", "Draft"),
    ("confirm", "Confirmed"),
    ("validate", "Done"),
    ("cancel", "Cancelled"),
]

EMPTY_ADDRESS = "0#0#0#0#0#0#0#0"
MEMBERSHIP_REQUEST_TYPE = [
    ("m", "Member"),
    ("s", "Supporter"),
]
MR_REQUIRED_AGE_KEY = "mr_required_age"


def partner_add_values(mr, partner_values):
    if not mr.is_company:
        partner_values["firstname"] = mr.firstname
        if mr.gender:
            partner_values["gender"] = mr.gender
        if mr.birthdate_date:
            partner_values["birthdate_date"] = mr.birthdate_date

    if mr.nationality_id:
        partner_values["nationality_id"] = mr.nationality_id.id


class MembershipRequest(models.Model):

    _name = "membership.request"
    _inherit = ["mozaik.abstract.model"]
    _description = "Membership Request"
    _inactive_cascade = True
    _terms = ["interest_ids", "competency_ids"]
    _order = "id desc"
    _unicity_keys = "N/A"

    identifier = fields.Char(related="partner_id.identifier", string="Identifier")
    is_company = fields.Boolean("Is a Company", default=False)
    lastname = fields.Char("Name", required=True, tracking=True)
    firstname = fields.Char("Firstname", tracking=True)

    gender = fields.Selection(
        selection=[("male", "Male"), ("female", "Female"), ("other", "Other")],
        string="Gender",
        index=True,
        tracking=True,
    )
    email = fields.Char("Email", tracking=True)
    phone = fields.Char("Phone", tracking=True)
    mobile = fields.Char("Mobile", tracking=True)
    day = fields.Char("Day")
    month = fields.Char("Month")
    year = fields.Char("Year")
    birthdate_date = fields.Date("Birth Date", tracking=True)

    # request and states
    request_type = fields.Selection(
        selection=MEMBERSHIP_REQUEST_TYPE, string="Request Type", tracking=True
    )
    membership_state_id = fields.Many2one(
        comodel_name="membership.state", string="Current State"
    )
    result_type_id = fields.Many2one(
        comodel_name="membership.state", string="Expected State"
    )
    is_update = fields.Boolean(string="Is Update", default=False)
    state = fields.Selection(
        selection=MEMBERSHIP_AVAILABLE_STATES,
        string="State",
        tracking=True,
        default="draft",
    )

    # address
    country_id = fields.Many2one(
        comodel_name="res.country", string="Country", index=True, tracking=True
    )
    enforce_cities = fields.Boolean(related="country_id.enforce_cities", readonly=True)

    city_id = fields.Many2one(comodel_name="res.city", string="City", tracking=True)
    local_zip = fields.Char(related="city_id.zipcode", string="Local Zip", store=True)
    zip_man = fields.Char(string="Zip", tracking=True)

    city_man = fields.Char(string="City (Manual)", tracking=True)

    address_local_street_id = fields.Many2one(
        comodel_name="address.local.street",
        string="Reference Street",
        tracking=True,
    )
    street_man = fields.Char(string="Street", tracking=True)
    street2 = fields.Char(string="Street2", tracking=True)

    number = fields.Char(string="Number", tracking=True)
    box = fields.Char(string="Box", tracking=True)
    sequence = fields.Integer(string="Sequence", tracking=True, group_operator="min")

    technical_name = fields.Char(string="Technical Name")

    # indexes
    interest_ids = fields.Many2many(
        comodel_name="thesaurus.term",
        relation="membership_request_interests_rel",
        column1="membership_id",
        column2="thesaurus_term_id",
        string="Interests",
    )
    competency_ids = fields.Many2many(
        comodel_name="thesaurus.term",
        relation="membership_request_competence_rel",
        column1="membership_id",
        column2="thesaurus_term_id",
        string="Competencies",
    )

    note = fields.Text("Notes")

    # references
    partner_id = fields.Many2one(
        comodel_name="res.partner",
        string="Partner",
        domain="[('membership_state_id', '!=', False)]",
    )

    int_instance_ids = fields.Many2many(
        comodel_name="int.instance",
        string="Internal Instance (real)",
    )
    int_instance_ids_readonly = fields.Many2many(
        related="int_instance_ids",
        string="Internal Instance",
    )
    force_int_instance_id = fields.Many2one(
        comodel_name="int.instance",
        string="Internal Instance (to Force)",
        tracking=True,
    )

    address_id = fields.Many2one(
        comodel_name="address.address", string="Address", tracking=True
    )
    change_ids = fields.One2many(
        comodel_name="membership.request.change",
        inverse_name="membership_request_id",
        string="Changes",
        domain=[("active", "=", True)],
    )
    inactive_change_ids = fields.One2many(
        comodel_name="membership.request.change",
        inverse_name="membership_request_id",
        string="Changes (Inactive)",
        domain=[("active", "=", False)],
    )

    age = fields.Integer(string="Age", compute="_compute_age", search="_search_age")

    local_voluntary = fields.Boolean(tracking=True)
    regional_voluntary = fields.Boolean(tracking=True)
    national_voluntary = fields.Boolean(tracking=True)
    local_only = fields.Boolean(
        tracking=True, help="Partner wishing to be contacted only by the local"
    )

    involvement_category_ids = fields.Many2many(
        "partner.involvement.category",
        relation="membership_request_involvement_category_rel",
        column1="request_id",
        column2="category_id",
        string="Involvement Categories",
    )

    indexation_comments = fields.Text("Indexation comments")

    amount = fields.Float(digits="Product Price", copy=False)
    reference = fields.Char(copy=False)
    effective_time = fields.Datetime(copy=False, string="Involvement Date")

    nationality_id = fields.Many2one(
        comodel_name="res.country", string="Nationality", tracking=True
    )

    @api.model
    def _get_status_values(self, request_type, date_from=False):
        """
        :type request_type: char
        :param request_type: m or s for member or supporter.
            `False` if not defined
        :rtype: dict
        :rparam: affected date resulting of the `request_type`
            and the `status`
        """
        vals = {}
        if request_type in ["m", "s"]:
            vals["accepted_date"] = date_from or fields.Date.today()
            vals["free_member"] = request_type == "s"
        return vals

    @api.constrains("birthdate_date", "is_company", "state")
    def _check_age(self):
        required_age = int(
            self.env["ir.config_parameter"]
            .sudo()
            .get_param(MR_REQUIRED_AGE_KEY, default=16)
        )
        for mr in self.filtered(
            lambda s: not s.is_company
            and s.birthdate_date
            and s.request_type
            and s.state == "validate"
        ):
            if mr.age < required_age:
                raise ValidationError(
                    _("The required age for a membership request is %s") % required_age
                )

    def _search_age(self, operator, value):
        """
        Use birthdate_date to search on age
        """
        age = value
        computed_birthdate_date = date.today() - relativedelta(years=age)
        computed_birthdate_date = datetime.strftime(
            computed_birthdate_date, DEFAULT_SERVER_DATE_FORMAT
        )
        if operator == ">=":
            operator = "<="
        elif operator == "<":
            operator = ">"
        return [("birthdate_date", operator, computed_birthdate_date)]

    def _pop_related(self, vals):
        vals.pop("local_zip", None)
        vals.pop("identifier", None)

    @api.depends("is_company", "birthdate_date")
    def _compute_age(self):
        """
        age computed depending of the birth date of the
        membership request
        """
        for mr in self:
            if not mr.is_company and mr.birthdate_date:
                mr.age = get_age(mr.birthdate_date)
            else:
                mr.age = 0

    def _get_membership_tracked_fields(self):
        """
        This method return a list of tuple defining which fields
        must create a change record.
        Tuple is structured like:
         - sequence: n° of sequence of the change, it respect the display
                     order on the screen view
         - field name: field name on view
         - request path: path to the field value from the request object
         - partner_path: path to the field value from the partner object
         - label: label to be able to ignore comparison
        """
        partner_address_path = "address_address_id"
        return [
            (1, "lastname", "lastname", "lastname", ""),
            (2, "firstname", "firstname", "firstname", ""),
            (3, "birthdate_date", "birthdate_date", "birthdate_date", ""),
            (4, "phone", "phone", "phone", ""),
            (5, "gender", "gender", "gender", ""),
            (6, "mobile", "mobile", "mobile", ""),
            (7, "email", "email", "email", ""),
            (
                8,
                "country_id",
                "country_id.display_name",
                partner_address_path + ".country_id.display_name",
                "",
            ),
            # address_local_zip_id set on request AND on partner
            (
                9,
                "city_id",
                "city_id.display_name",
                partner_address_path + ".city_id.display_name",
                "ZIP_REQUEST_PARTNER",
            ),
            # city_id set on request AND not on partner
            (
                9,
                "city_id",
                "city_id.zipcode",
                partner_address_path + ".zip_man",
                "ZIP_REQUEST_NO_PARTNER",
            ),
            (
                9,
                "city_id",
                "city_id.name",
                partner_address_path + ".city_man",
                "ZIP_REQUEST_NO_PARTNER",
            ),
            # city_id not set on request but on partner
            (
                9,
                "zip_man",
                "zip_man",
                partner_address_path + "city_id.zipcode",
                "ZIP_NO_REQUEST_PARTNER",
            ),
            (
                9,
                "city_man",
                "city_man",<commit_msg>[FIX] Membership request tracked fields<commit_after># Copyright 2017 <NAME>/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from collections import OrderedDict
from datetime import date, datetime
from operator import attrgetter
from uuid import uuid4

from dateutil.relativedelta import relativedelta
from psycopg2.extensions import AsIs

from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import safe_eval
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT

from odoo.addons.mozaik_tools.tools import (
    check_and_format_number,
    check_email,
    format_email,
    get_age,
)

_logger = logging.getLogger(__name__)

MEMBERSHIP_AVAILABLE_STATES = [
    ("draft", "Draft"),
    ("confirm", "Confirmed"),
    ("validate", "Done"),
    ("cancel", "Cancelled"),
]

EMPTY_ADDRESS = "0#0#0#0#0#0#0#0"
MEMBERSHIP_REQUEST_TYPE = [
    ("m", "Member"),
    ("s", "Supporter"),
]
MR_REQUIRED_AGE_KEY = "mr_required_age"


def partner_add_values(mr, partner_values):
    if not mr.is_company:
        partner_values["firstname"] = mr.firstname
        if mr.gender:
            partner_values["gender"] = mr.gender
        if mr.birthdate_date:
            partner_values["birthdate_date"] = mr.birthdate_date

    if mr.nationality_id:
        partner_values["nationality_id"] = mr.nationality_id.id


class MembershipRequest(models.Model):

    _name = "membership.request"
    _inherit = ["mozaik.abstract.model"]
    _description = "Membership Request"
    _inactive_cascade = True
    _terms = ["interest_ids", "competency_ids"]
    _order = "id desc"
    _unicity_keys = "N/A"

    identifier = fields.Char(related="partner_id.identifier", string="Identifier")
    is_company = fields.Boolean("Is a Company", default=False)
    lastname = fields.Char("Name", required=True, tracking=True)
    firstname = fields.Char("Firstname", tracking=True)

    gender = fields.Selection(
        selection=[("male", "Male"), ("female", "Female"), ("other", "Other")],
        string="Gender",
        index=True,
        tracking=True,
    )
    email = fields.Char("Email", tracking=True)
    phone = fields.Char("Phone", tracking=True)
    mobile = fields.Char("Mobile", tracking=True)
    day = fields.Char("Day")
    month = fields.Char("Month")
    year = fields.Char("Year")
    birthdate_date = fields.Date("Birth Date", tracking=True)

    # request and states
    request_type = fields.Selection(
        selection=MEMBERSHIP_REQUEST_TYPE, string="Request Type", tracking=True
    )
    membership_state_id = fields.Many2one(
        comodel_name="membership.state", string="Current State"
    )
    result_type_id = fields.Many2one(
        comodel_name="membership.state", string="Expected State"
    )
    is_update = fields.Boolean(string="Is Update", default=False)
    state = fields.Selection(
        selection=MEMBERSHIP_AVAILABLE_STATES,
        string="State",
        tracking=True,
        default="draft",
    )

    # address
    country_id = fields.Many2one(
        comodel_name="res.country", string="Country", index=True, tracking=True
    )
    enforce_cities = fields.Boolean(related="country_id.enforce_cities", readonly=True)

    city_id = fields.Many2one(comodel_name="res.city", string="City", tracking=True)
    local_zip = fields.Char(related="city_id.zipcode", string="Local Zip", store=True)
    zip_man = fields.Char(string="Zip", tracking=True)

    city_man = fields.Char(string="City (Manual)", tracking=True)

    address_local_street_id = fields.Many2one(
        comodel_name="address.local.street",
        string="Reference Street",
        tracking=True,
    )
    street_man = fields.Char(string="Street", tracking=True)
    street2 = fields.Char(string="Street2", tracking=True)

    number = fields.Char(string="Number", tracking=True)
    box = fields.Char(string="Box", tracking=True)
    sequence = fields.Integer(string="Sequence", tracking=True, group_operator="min")

    technical_name = fields.Char(string="Technical Name")

    # indexes
    interest_ids = fields.Many2many(
        comodel_name="thesaurus.term",
        relation="membership_request_interests_rel",
        column1="membership_id",
        column2="thesaurus_term_id",
        string="Interests",
    )
    competency_ids = fields.Many2many(
        comodel_name="thesaurus.term",
        relation="membership_request_competence_rel",
        column1="membership_id",
        column2="thesaurus_term_id",
        string="Competencies",
    )

    note = fields.Text("Notes")

    # references
    partner_id = fields.Many2one(
        comodel_name="res.partner",
        string="Partner",
        domain="[('membership_state_id', '!=', False)]",
    )

    int_instance_ids = fields.Many2many(
        comodel_name="int.instance",
        string="Internal Instance (real)",
    )
    int_instance_ids_readonly = fields.Many2many(
        related="int_instance_ids",
        string="Internal Instance",
    )
    force_int_instance_id = fields.Many2one(
        comodel_name="int.instance",
        string="Internal Instance (to Force)",
        tracking=True,
    )

    address_id = fields.Many2one(
        comodel_name="address.address", string="Address", tracking=True
    )
    change_ids = fields.One2many(
        comodel_name="membership.request.change",
        inverse_name="membership_request_id",
        string="Changes",
        domain=[("active", "=", True)],
    )
    inactive_change_ids = fields.One2many(
        comodel_name="membership.request.change",
        inverse_name="membership_request_id",
        string="Changes (Inactive)",
        domain=[("active", "=", False)],
    )

    age = fields.Integer(string="Age", compute="_compute_age", search="_search_age")

    local_voluntary = fields.Boolean(tracking=True)
    regional_voluntary = fields.Boolean(tracking=True)
    national_voluntary = fields.Boolean(tracking=True)
    local_only = fields.Boolean(
        tracking=True, help="Partner wishing to be contacted only by the local"
    )

    involvement_category_ids = fields.Many2many(
        "partner.involvement.category",
        relation="membership_request_involvement_category_rel",
        column1="request_id",
        column2="category_id",
        string="Involvement Categories",
    )

    indexation_comments = fields.Text("Indexation comments")

    amount = fields.Float(digits="Product Price", copy=False)
    reference = fields.Char(copy=False)
    effective_time = fields.Datetime(copy=False, string="Involvement Date")

    nationality_id = fields.Many2one(
        comodel_name="res.country", string="Nationality", tracking=True
    )

    @api.model
    def _get_status_values(self, request_type, date_from=False):
        """
        :type request_type: char
        :param request_type: m or s for member or supporter.
            `False` if not defined
        :rtype: dict
        :rparam: affected date resulting of the `request_type`
            and the `status`
        """
        vals = {}
        if request_type in ["m", "s"]:
            vals["accepted_date"] = date_from or fields.Date.today()
            vals["free_member"] = request_type == "s"
        return vals

    @api.constrains("birthdate_date", "is_company", "state")
    def _check_age(self):
        required_age = int(
            self.env["ir.config_parameter"]
            .sudo()
            .get_param(MR_REQUIRED_AGE_KEY, default=16)
        )
        for mr in self.filtered(
            lambda s: not s.is_company
            and s.birthdate_date
            and s.request_type
            and s.state == "validate"
        ):
            if mr.age < required_age:
                raise ValidationError(
                    _("The required age for a membership request is %s") % required_age
                )

    def _search_age(self, operator, value):
        """
        Use birthdate_date to search on age
        """
        age = value
        computed_birthdate_date = date.today() - relativedelta(years=age)
        computed_birthdate_date = datetime.strftime(
            computed_birthdate_date, DEFAULT_SERVER_DATE_FORMAT
        )
        if operator == ">=":
            operator = "<="
        elif operator == "<":
            operator = ">"
        return [("birthdate_date", operator, computed_birthdate_date)]

    def _pop_related(self, vals):
        vals.pop("local_zip", None)
        vals.pop("identifier", None)

    @api.depends("is_company", "birthdate_date")
    def _compute_age(self):
        """
        age computed depending of the birth date of the
        membership request
        """
        for mr in self:
            if not mr.is_company and mr.birthdate_date:
                mr.age = get_age(mr.birthdate_date)
            else:
                mr.age = 0

    def _get_membership_tracked_fields(self):
        """
        This method return a list of tuple defining which fields
        must create a change record.
        Tuple is structured like:
         - sequence: n° of sequence of the change, it respect the display
                     order on the screen view
         - field name: field name on view
         - request path: path to the field value from the request object
         - partner_path: path to the field value from the partner object
         - label: label to be able to ignore comparison
        """
        partner_address_path = "address_address_id"
        return [
            (1, "lastname", "lastname", "lastname", ""),
            (2, "firstname", "firstname", "firstname", ""),
            (3, "birthdate_date", "birthdate_date", "birthdate_date", ""),
            (4, "phone", "phone", "phone", ""),
            (5, "gender", "gender", "gender", ""),
            (6, "mobile", "mobile", "mobile", ""),
            (7, "email", "email", "email", ""),
            (
                8,
                "country_id",
                "country_id.display_name",
                partner_address_path + ".country_id.display_name",
                "",
            ),
            # address_local_zip_id set on request AND on partner
            (
                9,
                "city_id",
                "city_id.display_name",
                partner_address_path + ".city_id.display_name",
                "ZIP_REQUEST_PARTNER",
            ),
            # city_id set on request AND not on partner
            (
                9,
                "city_id",
                "city_id.zipcode",
                partner_address_path + ".zip_man",
                "ZIP_REQUEST_NO_PARTNER",
            ),
            (
                9,
                "city_id",
                "city_id.name",
                partner_address_path + ".city_man",
                "ZIP_REQUEST_NO_PARTNER",
            ),
            # city_id not set on request but on partner
            (
                9,
                "zip_man",
                "zip_man",
                partner_address_path + ".city_id.zipcode",
                "ZIP_NO_REQUEST_PARTNER",
            ),
            (
                9,
                "city_man",
                "city_man",
{'fraction_non_alphanumeric': 0.11584471027704896, 'fraction_numerical': 0.005158536043047094, 'mean_word_length': 2.441689623507805, 'pattern_counts': {'":': 4, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3104136', 'n_tokens_mistral': 6815, 'n_tokens_neox': 6584, 'n_words': 1351}