# © 2019 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging import time from datetime import datetime, timedelta from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError from lxml import etree _logger = logging.getLogger(__name__) class ScopPartner(models.Model): _inherit = "res.partner" def _default_ur(self): return self.env["res.company"]._ur_default_get() def _default_country(self): return self.env.ref("base.fr", False) @api.model def _get_domain_incubator_id(self): try: incubator_id = self.env.ref("cgscop_partner.incubator").id return [ ("is_company", "=", True), ("organization_subtype_id", "=", incubator_id), ] except Exception: return [("is_company", "=", True)] @api.model def _get_domain_revision_company_id(self): try: rev_id = self.env.ref("cgscop_partner.riga_14953").id com_id = self.env.ref("cgscop_partner.riga_16433").id return [ ("is_company", "=", True), ("organization_subtype_id", "in", [com_id, rev_id]), ] except Exception: return [("is_company", "=", True)] # Inherit parent active = fields.Boolean( tracking=True, ) # Infos générales - champs non affichés is_cooperative = fields.Boolean("Est une coopérative") parent_is_cooperative = fields.Boolean( string="Parent est une coopérative", related="parent_id.is_cooperative", ) current_user_ur_id = fields.Many2one( "union.regionale", string="Union Régionale de l'utilisateur", compute="_compute_current_user_ur_id", search="_search_current_user_ur_id", ) # Informations organisme - champs non affichés id_ur = fields.Char("Identifiant UR") # Informations Bandeau project_status = fields.Selection( [ ("1_information", "Phase d'information"), ("2_pre-diagnostic", "Phase de pré-diagnostic"), ("3_accompagnement", "Phase d'accompagnement projet"), ("4_suivi", "Phase de suivi"), ("5_abandonne", "Projet abandonné"), ("6_decede", "Coop Décédée"), ], tracking=True, string="Statut projet", index=True, ) name = fields.Char(index=True, tracking=True, string="Dénomination Sociale") nickname = fields.Char(string="Dénomination interne") cooperative_form_id = fields.Many2one( "res.partner.cooperative.form", string="Cooperative form", ondelete="restrict", tracking=True, ) partner_company_type_id = fields.Many2one( comodel_name="res.partner.company.type", string="Statut Juridique", ondelete="restrict", tracking=True, ) membership_status = fields.Selection( [ ("not_member", "Non Adhérent"), ("adhesion", "Phase d'Adhésion"), ("soumis_cg", "Soumis CG"), ("member", "Adhérent"), ("out", "Radié"), ], string="Statut d'adhésion", default="not_member", tracking=True, ) member_number = fields.Char( "No adhérent (texte)", compute="_compute_membership_number", store=True ) member_number_int = fields.Integer( "No adhérent", group_operator="", ) member_start = fields.Date( "Date d'adhésion", compute="_compute_membership", store=True ) cae = fields.Boolean("CAE", tracking=True) dissolution_date = fields.Date("Date de Décès", tracking=True) dissolution_reason_id = fields.Many2one( "res.partner.dissolution.reason", string="Motif Décès", ondelete="restrict", tracking=True, ) # Infos générales / Contact sigle = fields.Char("Sigle") street = fields.Char("Rue", tracking=1) street2 = fields.Char("Rue 2", tracking=1) street3 = fields.Char("Rue 3", tracking=1) zip = fields.Char("CP", change_default=True, tracking=1) zip_departement = fields.Char( "Num Département", compute="_compute_num_departement", store=True ) region = fields.Many2one( comodel_name="res.country.state", string="Région", compute="_compute_region", store=True, ) city = fields.Char("Ville", tracking=1) cedex = fields.Char("Cedex", tracking=1) country_id = fields.Many2one( "res.country", string="Pays", ondelete="restrict", default=_default_country, tracking=True, ) # Adresse postale postal_street = fields.Char("Adresse postale - Rue", tracking=1) postal_street2 = fields.Char("Adresse postale - Rue 2", tracking=1) postal_street3 = fields.Char("Adresse postale - Rue 3", tracking=1) postal_zip = fields.Char("Adresse postale - CP", tracking=1) postal_city = fields.Char("Adresse postale - Ville", tracking=1) postal_cedex = fields.Char("Adresse postale - Cedex", tracking=1) phone = fields.Char("Téléphone 1", tracking=True) mobile = fields.Char("Téléphone 2", tracking=True) email = fields.Char("eMail administratif", tracking=True) facebook = fields.Char("Facebook") linkedin = fields.Char("LinkedIn") twitter = fields.Char("Twitter") instagram = fields.Char("Instagram") # Infos générales / Suivi UR ur_id = fields.Many2one( "union.regionale", string="Union Régionale", index=True, ondelete="restrict", default=_default_ur, tracking=True, ) creation_delegate_id = fields.Many2one( "res.users", string="Délégué de création", ondelete="restrict", tracking=True, ) followup_delegate_id = fields.Many2one( "res.users", string="Délégué de suivi", domain=[("active", "=", True)], ondelete="restrict", tracking=True, ) support_delegate_id = fields.Many2one( "res.users", string="Délégué en support", domain=[("active", "=", True)], ondelete="restrict", tracking=True, ) segment_1_id = fields.Many2many( "res.partner.segment1", column1="partner_id", column2="segment_id", string="Segmentation 1", ) segment_2_id = fields.Many2many( "res.partner.segment2", column1="partner_id", column2="segment_id", string="Segmentation 2", ) segment_3_id = fields.Many2many( "res.partner.segment3", column1="partner_id", column2="segment_id", string="Segmentation 3", ) segment_4_id = fields.Many2many( "res.partner.segment4", column1="partner_id", column2="segment_id", string="Segmentation 4", ) segment_1_nb = fields.Integer( string="Nb de segments 1", compute="_compute_segment_nb" ) segment_2_nb = fields.Integer( string="Nb de segments 2", compute="_compute_segment_nb" ) segment_3_nb = fields.Integer( string="Nb de segments 3", compute="_compute_segment_nb" ) segment_4_nb = fields.Integer( string="Nb de segments 4", compute="_compute_segment_nb" ) filiere_ids = fields.Many2many( "res.partner.filiere", column1="partner_id", column2="filiere_id", string="Filière", ) is_mucs = fields.Boolean(string="Est adhérent Mucs", tracking=True) is_mucs_update_date = fields.Date(string="Date MAJ Mucs", tracking=True) is_union_sociale = fields.Boolean( string="Est adhérent Union Sociale", tracking=True ) is_union_sociale_update_date = fields.Date( string="Date MAJ Union Sociale", tracking=True ) # Infos générales / Infos activité creation_origin_id = fields.Many2one( "res.partner.creation.origin", string="Origine création en coop", domain=[("parent_id", "=", False)], ondelete="restrict", tracking=True, ) creation_suborigin_id = fields.Many2one( "res.partner.creation.origin", string="Sous-Origine création en coop", domain=[("child_ids", "=", False)], ondelete="restrict", tracking=True, ) date_1st_sign = fields.Date("Date 1ère signature coop", tracking=True) registration_date = fields.Date(string="Date d'immatriculation RCS", tracking=True) social_object = fields.Text("Objet Social", tracking=True) naf_id = fields.Many2one( "res.partner.naf", string="Code NAF", ondelete="restrict", tracking=True, ) secteur_id = fields.Many2one( "res.partner.secteur.activite", string="Secteur d'activité", related="naf_id.secteur_id", store=True, ) activity_desc = fields.Text("Description de l'activité", tracking=True) activity_customers = fields.Selection( [ ("ND", "Non définie"), ("PR", "Clients professionnels"), ("PA", "Clients particuliers"), ("PP", "Clients professionnels et particuliers"), ], string="Relation commerciale", default="ND", tracking=True, ) certification_ids = fields.Many2many( comodel_name="res.partner.certification", string="Agréments", ondelete="restrict", ) other_certification = fields.Char(string="Autre agrément", required=False) siret = fields.Char(string="SIRET du siège", size=14, index=True, tracking=True, copy=False) formatted_siret = fields.Char( string="SIRET formaté", compute="_compute_formatted_siret" ) siren = fields.Char(string="SIREN", size=11, compute="_compute_siren") pappers_url = fields.Char(string="URL Pappers", compute="_compute_siren") capital = fields.Integer("Capital (en €)") first_closeout = fields.Date("1er bilan en coop") closeout_month = fields.Selection( [ ("1", "Janvier"), ("2", "Février"), ("3", "Mars"), ("4", "Avril"), ("5", "Mai"), ("6", "Juin"), ("7", "Juillet"), ("8", "Août"), ("9", "Septembre"), ("10", "Octobre"), ("11", "Novembre"), ("12", "Décembre"), ], string="Mois clôture exercices", ) is_seed_scop = fields.Boolean("Est une SCOP d'amorçage") seed_end = fields.Date("Date de fin du dispositif d'amorçage") is_incubated = fields.Boolean("Est incubé") incubator_id = fields.Many2one( "res.partner", string="Incubateur", domain=_get_domain_incubator_id, ondelete="restrict", ) is_ag_constitution = fields.Boolean("AG constitutive réalisée") is_registration_in_progress = fields.Boolean( string="En cours d'immatriculation", default=False ) is_federation_com = fields.Boolean( string="Fédération de la Communication", compute="_compute_federation", store=True, default=False, ) is_federation_btp = fields.Boolean( string="Fédération du BTP", compute="_compute_federation", store=True, default=False, ) is_federation_indus = fields.Boolean( string="Fédération de l'Industrie", compute="_compute_federation", store=True, default=False, ) is_federation_cae = fields.Boolean( string="Fédération des CAE", compute="_compute_federation", store=True, default=False, ) activity_federation_com_ids = fields.Many2many( comodel_name="scop.federation.com.activity", relation="res_partner_federation_com_activity_rel", column1="partner_id", column2="com_activity_id", string="Domaine d'activité Com", help="Fédération de la Com - Domaine d'activité", ) activity_federation_indus_ids = fields.Many2many( comodel_name="scop.federation.indus.activity", relation="res_partner_federation_indus_activity_rel", column1="partner_id", column2="indus_activity_id", string="Domaine d'activité Industrie", help="Fédération de l'Industrie - Domaine d'activité", ) copadev_member = fields.Boolean(string="Adhérent copadev", tracking=True) # Contacts director_ids = fields.One2many( "res.partner", "parent_id", string="Contacts Dirigeants", domain=[("mandate_id", "!=", False)], ) other_child_ids = fields.One2many( "res.partner", "parent_id", string="Autres Contacts", domain=[("mandate_id", "=", False)], ) # Révisions revision_contract = fields.Date( string="Début du contrat de révision", tracking=True, ) revision_contract_end = fields.Date( string="Fin du contrat de révision", tracking=True, ) revision_contract_tacite = fields.Boolean( string="Tacite reconduction", tracking=True, default=False ) revision_mandat_cac = fields.Boolean( string="Mandat CAC actif", tracking=True, default=False ) revision_company_id = fields.Many2one( "res.partner", string="Organisme de révision", domain=_get_domain_revision_company_id, ondelete="restrict", tracking=True, ) revision_backup_company_id = fields.Many2one( "res.partner", string="Organisme de révision suppléant", domain=_get_domain_revision_company_id, ondelete="restrict", tracking=True, ) revision_person_id = fields.Many2one( comodel_name="res.users", string="Réviseur", ondelete="restrict", tracking=True, ) revision_certified_person_id = fields.Many2one( comodel_name="res.users", string="Réviseur agréé", ondelete="restrict", tracking=True, ) revision_person_assign_date = fields.Date( string="Date de nomination du réviseur", tracking=True ) revision_person_assign_end = fields.Date( string="Date de fin nomination réviseur", tracking=True ) revision_type = fields.Selection( [ ("1y", "Annuelle"), ("5y", "Quinquennale"), ("5ys", "Quinquennale séquencée (annuel)"), ("5ys23", "Quinquennale séquencée (2 ans et 3 ans)"), ], string="Périodicité de la révision", tracking=True, ) revision_type_ok = fields.Boolean( string="Périodicité de révision ok", tracking=True, compute="_compute_revision_type_ok", ) revision_quinq_exercice = fields.Integer( "Quinquénnale exercice à réviser", compute="_compute_revision_quinq_exercice", ) revision_next_date = fields.Date("Prochain exercice révisable (old)") revision_format_id = fields.Many2one( "scop.revision.format", string="Format de révision", ondelete="restrict", tracking=True, ) revision_tarif = fields.Integer( "Tarif de vente", related="revision_format_id.tarif", store=False ) revision_next_exercice = fields.Integer( "Prochain exercice révisable", compute="_compute_revision_next_exercice", store=True, readonly=False, ) revision_same_exercice = fields.Boolean( string="Révision sur l'année de cloture", tracking=True, ) revision_next_year = fields.Integer( "Année prochaine révision", compute="_compute_revision_next_year", store=True, readonly=False, ) revision_ids = fields.One2many( comodel_name="scop.revision", inverse_name="partner_id", string="Liste des Révisions", ) revision_quinq_A1 = fields.Integer( "Quinquénnale année 1", compute="_compute_revision_quinq_annee", ) revision_quinq_A2 = fields.Integer( "Quinquénnale année 2", compute="_compute_revision_quinq_annee", ) revision_quinq_A3 = fields.Integer( "Quinquénnale année 3", compute="_compute_revision_quinq_annee", ) revision_quinq_A4 = fields.Integer( "Quinquénnale année 4", compute="_compute_revision_quinq_annee", ) revision_quinq_A5 = fields.Integer( "Quinquénnale année 5", compute="_compute_revision_quinq_annee", ) revision_activite_A1 = fields.Integer( "Activité révision année 1", tracking=True, ) revision_activite_A2 = fields.Integer( "Activité révision année 2", tracking=True, ) revision_activite_A3 = fields.Integer( "Activité révision année 3", tracking=True, ) revision_activite_A4 = fields.Integer( "Activité révision année 4", tracking=True, ) revision_activite_A5 = fields.Integer( "Activité révision année 5", tracking=True, ) # Action RSE et TE action_rse_ids = fields.One2many( comodel_name="scop.action.rse", inverse_name="partner_id", string="Liste des Actions RSE", ) action_te_ids = fields.Many2many( "scop.action.te", column1="partner_id", column2="action_id", string="Expertises transition éco.", ) is_rse = fields.Boolean( string="Engagement TE", compute="_compute_is_rse", store=True, default=False ) # Historique scop_period_ids = fields.One2many( comodel_name="scop.period", inverse_name="partner_id", string="Historique", ) # Période d'adhésion membership_period_ids = fields.One2many( comodel_name="scop.membership.period", inverse_name="partner_id", string="Périodes d'adhésion", ) # Effectifs staff_ids = fields.One2many( comodel_name="scop.partner.staff", inverse_name="partner_id", string="Effectifs", ) # Liste Ministère staff_last = fields.Integer( string="Dernier effectif connu", compute="_compute_last_effective", store=True, ) staff_shareholder_last = fields.Integer( string="Dernier effectif associés connu", compute="_compute_last_effective", store=True, ) staff_last_date = fields.Date( string="Date dernier effectif connu", compute="_compute_last_effective", store=True, ) # Champs pour personnes birthyear = fields.Integer("Année de naissance") subscription_ids = fields.One2many( comodel_name="res.partner.newsletter.subscription", inverse_name="partner_id", string="Abonnements", ) contact_origin_id = fields.Many2one( "res.partner.rgpd.origin", string="Origine du contact", ondelete="restrict", ) mandate_id = fields.Many2one( "res.partner.mandate", string="Mandat", ondelete="restrict" ) function_lst_id = fields.Many2one( "res.partner.function_lst", string="Fonction", ondelete="restrict" ) contact_legality = fields.Selection( [ ("employee", "Salarié"), ("customer", "Client en contrat"), ("supplier", "Fournisseur en contrat"), ("consent", "Consentement"), ("legitimate", "Intérêt légitime"), ("none", "Aucune"), ], string="Licéité du contact", compute="_compute_contact_legality", store=True, ) associate = fields.Selection( selection=[ ("asso", "Associé extérieur"), ("coop", "Associé coopérateur"), ("none", "Non associé"), ], string="Associé", ) employee = fields.Boolean(string="Salarié", default=True) has_mvt_mandate = fields.Boolean(string="Elu du mouvement", default=False) mvt_mandate_ids = fields.Many2many( comodel_name="res.partner.mvt.mandate", relation="res_partner_mvt_mandate_rel", column1="partner_id", column2="mvt_mandate_id", string="Mandats mouvement", ) mvt_vip_ids = fields.Many2many( comodel_name="res.partner.mvt.vip", relation="res_partner_mvt_vip_rel", column1="partner_id", column2="mvt_vip_id", string="Etiquettes VIP", ) # Champs pour partenaires organization_type_id = fields.Many2one( "res.partner.organization.type", string="Famille", ondelete="restrict", compute="_compute_org_type_id", tracking=True, ) organization_subtype_id = fields.Many2one( "res.partner.organization.type", string="Type", domain=[("child_ids", "=", False)], ondelete="restrict", tracking=True, ) # Droits utilisateurs is_administrative = fields.Boolean(compute="_compute_is_administrative") # Hack pour la création de contacts depuis la fiche organisme parent_id_onchange = fields.Many2one("res.partner") # ------------------------------------------------------ # Constrains # ------------------------------------------------------ @api.constrains("zip_id", "country_id", "city_id", "state_id") def _check_zip(self): return @api.constrains("revision_contract", "revision_contract_end") def _check_revision_contract(self): for rec in self: if rec.revision_contract and \ rec.revision_contract_end and \ rec.revision_contract > rec.revision_contract_end: raise ValidationError( _( "La date de fin du contrat de révision doit être supérieure" " à la date de début du contrat de révision" ) ) return @api.constrains("revision_person_assign_date", "revision_person_assign_end") def _check_revision_person_assign(self): for rec in self: if rec.revision_person_assign_date and \ rec.revision_person_assign_end and \ rec.revision_person_assign_date > rec.revision_person_assign_end: raise ValidationError( _( "La date de fin de nomination du réviseur doit être supérieure" " à la date de début de nomination du réviseur" ) ) return @api.constrains("siret") def _check_siret(self): if self.siret and not self.env.context.get("import_file"): siren = self.siret[:3] + " " + self.siret[3:6] + " " + self.siret[6:9] if ( not self.siret.isdigit() and self.siret != "En attente d'immatriculation" ): raise ValidationError(_("Ce SIRET n'est pas valide")) if len(self.siret) != 14: raise ValidationError(_("La longueur du SIRET doit être égale à 14")) if ( self.search_count( [ ("siret", "=like", siren + "%"), ("is_cooperative", "=", True), ] ) > 1 ): raise ValidationError(_("Ce SIREN existe déjà parmi les coopératives")) elif self.search_count([("siret", "=", self.siret)]) > 1: raise ValidationError(_("Ce SIRET existe déjà")) @api.constrains("is_company", "type", "email", "phone", "mobile", "user_ids") def _check_contact_info(self): # Contrainte de tel ou mail lors de la modification d'un contact if not self.is_company and self.type == "contact" and not self.user_ids: if not self.email and not self.phone and not self.mobile: raise UserError( _("Vous devez saisir au moins un e-mail ou un téléphone pour %s") % self.name ) # ------------------------------------------------------ # Onchange # ------------------------------------------------------ # TODO: à vérifier # Hack pour la création de contacts depuis la fiche organisme @api.onchange("parent_id_onchange") def _onchange_parent_id_onchange(self): self.parent_id = self.parent_id_onchange @api.onchange("nickname") def onchange_nickname(self): if ( self.is_cooperative and self.project_status in ["1_information", "2_pre-diagnostic", "3_accompagnement"] ): self.lastname = self.nickname # TODO: à vérifier @api.onchange("parent_id") def _onchange_parent_id(self): if self.parent_id.ur_id: self.ur_id = self.parent_id.ur_id @api.onchange("ur_id") def _onchange_ur_id(self): if self.child_ids: for child in self.child_ids: child.ur_id = self.ur_id @api.onchange("creation_origin_id") def onchange_creation_origin_id(self): for coop in self: coop.creation_suborigin_id = False # TODO: à vérifier @api.onchange("is_seed_scop") def onchange_is_seed_scop(self): for coop in self: if coop.is_seed_scop: if coop.date_1st_sign: coop.seed_end = coop.date_1st_sign + timedelta(2556) else: coop.seed_end = datetime.today().date() + timedelta(2556) else: coop.seed_end = False @api.onchange("name") def onchange_name(self): if self.search_count([("name", "=ilike", self.name)]) > 0: return { "warning": { "title": "Attention", "message": "Ce nom / cette raison sociale existe déjà, " + "merci de vérifier que vous n'êtes pas en " + "train de créer un doublon", } } @api.onchange("mobile") def onchange_mobile(self): if self.mobile and len(self.mobile) > 0 and len(self.mobile) < 10: raise ValidationError( _("Le numéro de téléphone doit contenir au moins 10 caractères") ) @api.onchange("phone") def onchange_phone(self): if self.phone and len(self.phone) > 0 and len(self.phone) < 10: raise ValidationError( _("Le numéro de téléphone doit contenir au moins 10 caractères") ) @api.onchange("siret") def onchange_siret(self): if self.siret: siren = self.siret[:3] + " " + self.siret[3:6] + " " + self.siret[6:9] if self.search_count([("siret", "=like", self.siret)]) > 0: return { "warning": { "title": "Attention", "message": "Ce SIRET existe déjà, merci de vérifier " + "que vous n'êtes pas en train de créer un" + " doublon", } } elif self.search_count([("siret", "=like", siren + "%")]) > 0: return { "warning": { "title": "Attention", "message": "Ce SIREN existe déjà, merci de vérifier " + "que vous n'êtes pas en train de créer un" + " doublon", } } @api.onchange("cooperative_form_id") def onchange_cooperative_form_id(self): if self.cooperative_form_id == self.env.ref( "cgscop_partner.form_noncooperative" ): self.creation_origin_id = None self.creation_suborigin_id = None self.is_ag_constitution = None self.date_1st_sign = None self.first_closeout = None # ------------------------------------------------------ # Common functions # ------------------------------------------------------ def _create_period(self, partner): new_period = self._add_period(partner) partner.scop_period_ids = new_period def _add_period(self, partner): new_period = self.env["scop.period"].with_context().create( { "partner_id": partner.id, "start": partner.registration_date or fields.Date.today(), "name": partner.name, "cooperative_form_id": partner.cooperative_form_id.id, "partner_company_type_id": partner.partner_company_type_id.id, "siret": ( partner.siret if partner.siret or not partner.is_registration_in_progress else "En attente d'immatriculation" ), "street": partner.street, "street2": partner.street2, "street3": partner.street3, "zip": partner.zip, "zip_id": partner.zip_id.id, "city": partner.city, "cedex": partner.cedex, "state_id": partner.state_id.id, "country_id": partner.country_id.id, "naf_id": partner.naf_id.id, "ur_id": partner.ur_id.id, "cae": partner.cae, } ) return new_period # ------------------------------------------------------ # Override ORM # ------------------------------------------------------ @api.model def _name_search( self, name, args=None, operator="ilike", limit=100, name_get_uid=None ): args = args or [] domain = [ "|", "|", "|", ("name", operator, name), ("member_number", "ilike", name), ("nickname", operator, name), ("sigle", operator, name), ] return self._search(domain + args, limit=limit, access_rights_uid=name_get_uid) # Creation d'une periode lorsque le statut passe en Phase de Suivi def write(self, vals): # Gestion casse des informations if vals.get("name"): vals["name"] = vals.get("name").title() if vals.get("lastname"): vals["lastname"] = vals.get("lastname").title() if vals.get("firstname"): vals["firstname"] = vals.get("firstname").title() if vals.get("city"): vals["city"] = vals.get("city").upper() if len(self) == 1 and self.parent_id: if self.type in ("contact", "invoice", "private"): parent_ur_id = self.parent_id.ur_id.id vals["ur_id"] = parent_ur_id result = super(ScopPartner, self).write(vals) # Hack pour notification lors de la modification du logo if "image_128" in vals: self.message_post( body=_("Modification Logo"), subtype="cgscop_base.cg_values_change", ) for partner in self: # Création d'une période lors du changement de statut en Suivi if vals.get("project_status") == "4_suivi" and not self.env.context.get( "import_file" ): if not partner.scop_period_ids: partner.sudo()._create_period(partner) # Ajout des followers de la fiche partners_to_subscribe = [] if partner.followup_delegate_id != partner.create_uid: partners_to_subscribe.append(partner.followup_delegate_id.partner_id.id) if partner.creation_delegate_id != partner.create_uid: partners_to_subscribe.append(partner.creation_delegate_id.partner_id.id) if partners_to_subscribe: partner.message_subscribe(partner_ids=partners_to_subscribe) return result @api.model_create_multi def create(self, vals_list): """ Création d'une période lors de la création d'une coopérative """ # Gestion casse des informations for vals in vals_list: if vals.get("name"): vals["name"] = vals.get("name").title() if vals.get("lastname"): vals["lastname"] = vals.get("lastname").title() if vals.get("firstname"): vals["firstname"] = vals.get("firstname").title() if vals.get("city"): vals["city"] = vals.get("city").upper() partners = super(ScopPartner, self).create(vals_list) for vals in vals_list: # Création d'une période si la coop est en statut en Suivi if vals.get("is_cooperative") and vals.get("project_status") == "4_suivi": for partner in partners: if not partner.scop_period_ids: partner.sudo()._create_period(partner) return partners @api.model def _address_fields(self): address_fields = super(ScopPartner, self)._address_fields() address_fields.append("cedex") return address_fields # ------------------------------------------------------ # Override parent # ------------------------------------------------------ def _get_contact_name(self, partner, name): super(ScopPartner, self)._get_contact_name(partner, name) return "%s, %s" % ( name, partner.commercial_company_name or partner.sudo().parent_id.name, ) def action_archive(self): """ Lève une erreur si une coopérative qui n'est pas à l'état abandonné fait partie des items à archiver """ coop_ids = self.filtered( lambda p: p.is_cooperative and p.project_status != "5_abandonne" ) if coop_ids: raise UserError( _( "Il n'est pas possible d'archiver une coopérative qui n'est pas en " "statut abandonné." ) ) else: return super().action_archive() # ------------------------------------------------------ # Computed Fields # ------------------------------------------------------ @api.depends("siret") def _compute_siren(self): for partner in self: if partner.siret: partner.siren = ( partner.siret[:3] + " " + partner.siret[3:6] + " " + partner.siret[6:9] ) partner.pappers_url = ( "https://www.pappers.fr/entreprise/" + partner.siret[:9] ) else: partner.siren = None partner.pappers_url = None def _compute_formatted_siret(self): for partner in self: if partner.siret: partner.formatted_siret = ( partner.siret[:3] + " " + partner.siret[3:6] + " " + partner.siret[6:9] + " " + partner.siret[9:] ) else: partner.formatted_siret = None @api.depends("zip") def _compute_num_departement(self): for company in self: if company.zip: zip_starts = company.zip[:3] if zip_starts in ["200", "201"]: zip_departement = "2A" elif zip_starts == "202": zip_departement = "2B" elif zip_starts[:2] == "97": zip_departement = zip_starts else: zip_departement = zip_starts[:2] if zip_departement in ["2A", "2B"] or zip_departement.isdigit(): company.zip_departement = zip_departement else: company.zip_departement = False @api.depends("zip") def _compute_region(self): for partner in self: if partner.zip: zip_id = self.env["res.city.zip"].search([("name", "=", partner.zip)]) if zip_id: partner.region = zip_id[0].city_id[0].state_id @api.model def _compute_current_user_ur_id(self): for partner in self: partner.current_user_ur_id = self.env.company.ur_id def _search_current_user_ur_id(self, operator, value): return [("ur_id", "=", self.env.company.ur_id.id)] @api.depends( "contact_origin_id", "parent_id.cooperative_form_id", "parent_id.membership_status", ) def _compute_contact_legality(self): for partner in self: partner.contact_legality = "none" if partner.contact_origin_id.name == "Fiche contact, site internet": partner.contact_legality = "consent" if partner.contact_origin_id.name in ( "Prospect journée d'info coll", "Prospect (salon, rdv, internet…)", "Elus", ): partner.contact_legality = "legitimate" if partner.contact_origin_id.name in ( "Salariés CG", "Salariés UR", "Salariés Fédération", ): partner.contact_legality = "employee" if partner.contact_origin_id.name in ( "Elus", "VIP, Officiels", "Fournisseurs", ): partner.contact_legality = "legitimate" if not partner.is_company and partner.parent_id: parent = partner.parent_id if ( partner.contact_origin_id.name in ( "Dossiers d'adhésion", "Dossiers annuels non LM (scic, scop47)", ) and parent.cooperative_form_id and parent.membership_status == "member" ): partner.contact_legality = "customer" if ( partner.contact_origin_id.name == ("Dossiers annuels non LM (scic, scop47)") and parent.membership_status != "member" ): partner.contact_legality = "legitimate" if ( partner.contact_origin_id.name == ("Dossiers Liste ministère") and parent.cooperative_form_id.name == "SCIC" ): partner.contact_legality = "customer" @api.depends("action_rse_ids","action_te_ids") def _compute_is_rse(self): """ Est on une coop RSE/TE """ for partner in self: partner.is_rse = False if len(partner.action_rse_ids) != 0: partner.is_rse = True if len(partner.action_te_ids) != 0: partner.is_rse = True @api.depends( "membership_period_ids", "membership_period_ids.end_reason_id", "membership_period_ids.end", "membership_period_ids.start", ) # Todo: A revoir comment on assigne le statut member def _compute_membership(self): for partner in self: if partner.membership_period_ids: type_cg = self.env.ref("cgscop_partner.membership_type_1").id last_membership_period = self.env["scop.membership.period"].search( [("partner_id", "=", partner.id), ("type_id", "=", type_cg)], limit=1, order="start desc,id desc", ) if last_membership_period and not last_membership_period.end: partner.membership_status = "member" partner.member_start = last_membership_period.start elif last_membership_period and last_membership_period.end_reason_id: partner.membership_status = "out" else: partner.membership_status = "not_member" @api.depends("member_number_int") def _compute_membership_number(self): for partner in self: if partner.member_number_int: partner.member_number = str(partner.member_number_int) else: partner.member_number = False @api.depends("membership_period_ids", "membership_period_ids.end") def _compute_federation(self): for partner in self: if partner.is_cooperative: partner.is_federation_com = partner._get_federation( "cgscop_partner.membership_type_2" ) partner.is_federation_btp = partner._get_federation( "cgscop_partner.membership_type_4" ) partner.is_federation_indus = partner._get_federation( "cgscop_partner.membership_type_3" ) partner.is_federation_cae = partner._get_federation( "cgscop_partner.membership_type_5" ) def _get_federation(self, external_id): member_type_id = self.env.ref(external_id).id partner_id = self.id membership_period = self.env["scop.membership.period"].search( [ ("partner_id", "=", partner_id), ("type_id", "=", member_type_id), ("end", "=", False), ] ) if membership_period: return True else: return False @api.depends( "staff_ids", "staff_ids.staff_count", "staff_ids.staff_shareholder_count", "staff_ids.effective_date", ) def _compute_last_effective(self): for partner in self: lines = partner._get_staff_lines() if lines: partner.staff_last = lines[0].staff_count partner.staff_shareholder_last = lines[0].staff_shareholder_count partner.staff_last_date = lines[0].effective_date def _compute_segment_nb(self): for partner in self: # Calcul nombre de segment 1 seg1 = partner.env["res.partner.segment1"].search( [("ur_id", "=", self.env.user.ur_id.id)] ) partner.segment_1_nb = len(seg1) # Calcul nombre de segment 2 seg2 = partner.env["res.partner.segment2"].search( [("ur_id", "=", self.env.user.ur_id.id)] ) partner.segment_2_nb = len(seg2) # Calcul nombre de segment 3 seg3 = partner.env["res.partner.segment3"].search( [("ur_id", "=", self.env.user.ur_id.id)] ) partner.segment_3_nb = len(seg3) # Calcul nombre de segment 4 seg4 = partner.env["res.partner.segment4"].search( [("ur_id", "=", self.env.user.ur_id.id)] ) partner.segment_4_nb = len(seg4) @api.depends("organization_subtype_id") def _compute_org_type_id(self): for partner in self: if partner.organization_subtype_id: partner.organization_type_id = partner.organization_subtype_id.parent_id else: partner.organization_type_id = False def _compute_is_administrative(self): for partner in self: if self.env.user.has_group("cgscop_partner.group_cg_administrative"): partner.is_administrative = True else: partner.is_administrative = False @api.depends( "revision_next_exercice", "revision_same_exercice", ) def _compute_revision_next_year(self): for partner in self: if partner.revision_same_exercice: partner.revision_next_year = partner.revision_next_exercice else: partner.revision_next_year = partner.revision_next_exercice + 1 @api.depends( "revision_type", "revision_ids", "revision_ids.revision_result_year", "first_closeout", ) def _compute_revision_next_exercice(self): for partner in self: # Si aucune périodicité de défini, on n'insiste pas if not partner.revision_type or not partner.first_closeout: partner.revision_next_exercice = False else: # On commence par regarder si l'on a des révisions last_rev = partner.revision_ids.sorted( key=lambda r: r.revision_result_year, reverse=True ) # On calcule l'année de référence du calcul if len(last_rev) > 0: # si On a déjà révisé un exercice il devient la base du calcul base_rev = last_rev[0].revision_result_year else: base_rev = partner.first_closeout.year - 1 # On calcule le prochain exercice révisable # Cas d'une révision annuelle if partner.revision_type == "1y": partner.revision_next_exercice = base_rev + 1 # Cas d'une révision quinquénnale elif partner.revision_type == "5y": self._compute_revision_quinq_exercice() partner.revision_next_exercice = partner.revision_quinq_exercice # Cas d'une révision quinquénnale séquencée (annuelle) elif partner.revision_type == "5ys": partner.revision_next_exercice = base_rev + 1 # Cas d'une révision quinquénnale séquencée (2 et 3) elif partner.revision_type == "5ys23": # On doit regarder l'écart avec la révision précédente if len(last_rev) > 1: # On a une réunion précédente ... # ... il faut regarder l'écart entre les deux ex1 = last_rev[0].revision_result_year ex2 = last_rev[1].revision_result_year # ... prochain exercice = 5 - durée de la précédente révision partner.revision_next_exercice = base_rev + (5 - (ex1 - ex2)) else: # Pas de révision précédente partner.revision_next_exercice = base_rev + 2 @api.depends( "revision_type", "revision_ids", "revision_ids.revision_result_year", "revision_ids.revision_type", "first_closeout", ) def _compute_revision_quinq_exercice(self): for partner in self: # Si aucune périodicité de défini, on n'insiste pas if not partner.revision_type or not partner.first_closeout: partner.revision_quinq_exercice = False else: # On commence par regarder si l'on a des révisions classique ou speciale last_rev = partner.revision_ids.filtered( lambda p: p.revision_type != "int" ).sorted( key=lambda r: r.revision_result_year, reverse=True ) # On calcule l'année de référence du calcul if len(last_rev) > 0: # si On a déjà révisé un exercice il devient la base du calcul base_rev = last_rev[0].revision_result_year else: base_rev = partner.first_closeout.year - 1 # On calcule l'année de révision de la quinquénale partner.revision_quinq_exercice = base_rev + 5 @api.depends( "revision_quinq_exercice", "revision_same_exercice", ) def _compute_revision_quinq_annee(self): for partner in self: if partner.revision_same_exercice: partner.revision_quinq_A5 = partner.revision_quinq_exercice partner.revision_quinq_A4 = partner.revision_quinq_exercice - 1 partner.revision_quinq_A3 = partner.revision_quinq_exercice - 2 partner.revision_quinq_A2 = partner.revision_quinq_exercice - 3 partner.revision_quinq_A1 = partner.revision_quinq_exercice - 4 else: partner.revision_quinq_A5 = partner.revision_quinq_exercice + 1 partner.revision_quinq_A4 = partner.revision_quinq_exercice partner.revision_quinq_A3 = partner.revision_quinq_exercice - 1 partner.revision_quinq_A2 = partner.revision_quinq_exercice - 2 partner.revision_quinq_A1 = partner.revision_quinq_exercice - 3 @api.depends( "revision_type", "revision_mandat_cac", ) def _compute_revision_type_ok(self): for partner in self: partner.revision_type_ok = True if (partner.revision_type == "1y") and (partner.revision_mandat_cac) : partner.revision_type_ok = False if (partner.revision_type == "1y") and (partner.cooperative_form_id.name == "SCIC") : partner.revision_type_ok = False # ------------------------------------------------------ # Button & Action # ------------------------------------------------------ def open_facebook(self): self.ensure_one() return { "type": "ir.actions.act_url", "url": self.facebook, } def open_linkedin(self): self.ensure_one() return { "type": "ir.actions.act_url", "url": self.linkedin, } def open_twitter(self): self.ensure_one() return { "type": "ir.actions.act_url", "url": self.twitter, } def open_instagram(self): self.ensure_one() return { "type": "ir.actions.act_url", "url": self.instagram, } def open_pappers(self): self.ensure_one() return { "type": "ir.actions.act_url", "url": self.pappers_url, } def remove_director(self): self.write({"mandate_id": False}) return {"type": "ir.actions.act_window_close"} def partner_archive(self): self.active = False def scop_prj_adhesion(self): self.write({"membership_status": "adhesion"}) return True def scop_abandonne(self): self.write( { "project_status": "5_abandonne", } ) return True def add_director(self): return { "type": "ir.actions.act_window", "views": [ [ self.env.ref("cgscop_partner.scop_partner_director_form_view").id, "form", ] ], "view_mode": "form", "res_model": "res.partner", "target": "new", "context": { "default_parent_id_onchange": self.id, "default_street": self.street, "default_street2": self.street2, "default_street3": self.street3, "default_city": self.city, "default_city_id": self.city_id.id, "default_cedex": self.cedex, "default_state_id": self.state_id.id, "default_zip": self.zip, "default_country_id": self.country_id.id, "default_lang": self.lang, "default_user_id": self.user_id.id, "ur_id": self.ur_id.id, "default_type": "contact", }, } def edit_director(self): return { "type": "ir.actions.act_window", "views": [ [ self.env.ref("cgscop_partner.scop_partner_director_form_view").id, "form", ] ], "view_mode": "form", "res_model": "res.partner", "res_id": self.id, "target": "new", } def add_contact(self): return { "type": "ir.actions.act_window", "views": [ [ self.env.ref("cgscop_partner.scop_partner_contact_form_view").id, "form", ] ], "view_mode": "form", "res_model": "res.partner", "target": "new", "context": { "default_parent_id_onchange": self.id, "default_street": self.street, "default_street2": self.street2, "default_street3": self.street3, "default_city": self.city, "default_city_id": self.city_id.id, "default_cedex": self.cedex, "default_state_id": self.state_id.id, "default_zip": self.zip, "default_country_id": self.country_id.id, "default_lang": self.lang, "default_user_id": self.user_id.id, }, } def edit_contact(self): return { "type": "ir.actions.act_window", "views": [ [ self.env.ref("cgscop_partner.scop_partner_contact_form_view").id, "form", ] ], "view_mode": "form", "res_model": "res.partner", "res_id": self.id, "target": "new", } def write_contact(self): return self def show_coop(self): """ Affichage des coop avec filtre par défaut """ ctx = { "default_is_company": True, "default_is_cooperative": True, "default_company_type": "company", "default_project_status": "4_suivi", } # Détermine le filtre par défaut pour l'affichage filtre = self.env.company.ur_id.partner_filter if filtre == "1": ctx.update({"search_default_is_adherent": True}) elif filtre == "2": ctx.update({"search_default_my_ur_adherent": True}) elif filtre == "3": ctx.update({"search_default_is_federation_com": True}) elif filtre == "4": ctx.update({"search_default_is_federation_indus": True}) elif filtre == "5": ctx.update({"search_default_my_is_federation_btp": True}) elif filtre == "6": ctx.update({"search_default_my_is_federation_cae": True}) return { "name": "Coopératives", "type": "ir.actions.act_window", "res_model": "res.partner", "search_view_id": ( self.env.ref("cgscop_partner.scop_partner_view_search").id, ), "view_mode": "tree,form,activity", "views": [ ( self.env.ref("cgscop_partner.view_partner_cooperative_tree").id, "tree", ), ( self.env.ref("cgscop_partner.scop_contact_view_form").id, "form", ), ], "target": "current", "domain": [ ("is_cooperative", "=", True), ("project_status", "=", "4_suivi"), ], "context": ctx, } def show_creation_project(self): """ Affichage des projets en création avec filtre par défaut """ ctx = { "default_is_company": True, "default_is_cooperative": True, "default_company_type": "company", "default_project_status": "1_information", } # Détermine le filtre par défaut pour l'affichage filtre = self.env.company.ur_id.partner_filter if filtre == "2": ctx.update({"search_default_my_ur": True}) return { "name": "Projets de création", "type": "ir.actions.act_window", "res_model": "res.partner", "search_view_id": ( self.env.ref("cgscop_partner.scop_partner_view_search").id, ), "view_mode": "kanban,tree,form,activity", "views": [ ( self.env.ref("cgscop_partner.view_partner_cooperative_kanban").id, "kanban", ), ( self.env.ref("cgscop_partner.view_partner_prospect_tree").id, "tree", ), ( self.env.ref("cgscop_partner.scop_contact_view_form").id, "form", ), ], "target": "current", "domain": [ ("is_cooperative", "=", True), ( "project_status", "in", ( "1_information", "2_pre-diagnostic", "3_accompagnement", "5_abandonne", ), ), ], "context": ctx, } def show_organisme(self): """ Affichage des organismes avec filtre par défaut """ ctx = { "default_is_company": True, "default_is_cooperative": True, "default_company_type": "company", "default_project_status": "1_information", } # Détermine le filtre par défaut pour l'affichage filtre = self.env.company.ur_id.partner_filter if filtre == "2": ctx.update({"search_default_my_ur_adherent": True}) return { "name": "Tous les organismes", "type": "ir.actions.act_window", "res_model": "res.partner", "search_view_id": ( self.env.ref("cgscop_partner.scop_partner_view_search").id, ), "view_mode": "tree,form,activity", "views": [ ( self.env.ref("cgscop_partner.view_partner_cooperative_tree").id, "tree", ), ( self.env.ref("cgscop_partner.scop_contact_view_form").id, "form", ), ], "target": "current", "domain": [ ("is_cooperative", "=", True), ], "context": ctx, } def show_processus_adhesion(self): """ Affichage des coopératives dans leur processus d'adhésion par défaut """ ctx = { "default_is_company": True, "default_is_cooperative": True, "default_company_type": "company", "default_project_status": "4_suivi", } # Détermine le filtre par défaut pour l'affichage filtre = self.env.company.ur_id.partner_filter if filtre == "2": ctx.update({"search_default_my_ur": True}) return { "name": "Processus d'adhésion", "type": "ir.actions.act_window", "res_model": "res.partner", "search_view_id": ( self.env.ref("cgscop_partner.scop_partner_view_search").id, ), "view_mode": "kanban,tree,form,activity", "views": [ ( self.env.ref("cgscop_partner.view_partner_adhesion_kanban").id, "kanban", ), ( self.env.ref("cgscop_partner.view_partner_prospect_tree").id, "tree", ), ( self.env.ref("cgscop_partner.scop_contact_view_form").id, "form", ), ], "target": "current", "domain": [ ("is_cooperative", "=", True), ( "membership_status", "in", ("adhesion", "soumis_cg"), ), ], "context": ctx, } # ------------------------------------------------------ # Business Methods # ------------------------------------------------------ def get_formatted_address(self): self.ensure_one() address_fields = [self.street, self.street2, self.street3] address = [f for f in address_fields if f] return "\n".join(address) if address else "" def get_postal_address(self): self.ensure_one() street_address_fields = [self.postal_street, self.postal_street2, self.postal_street3] street_address_list = list(filter(bool, street_address_fields)) city_address_list = list(filter(bool, self.postal_zip, self.postal_city)) street_address = "\n".join(street_address_list) if street_address_list else "" city_address = " ".join(city_address_list) if city_address_list else "" cedex = f" CEDEX {self.postal_cedex}" if self.postal_cedex else "" return f"{street_address}\n{city_address}{cedex}" def _get_staff_lines(self): self.ensure_one() return self.staff_ids.sorted(key="effective_date", reverse=True) # ------------------------------------------------------ # CRON function # ------------------------------------------------------ def _cron_geoloc_data_gouv(self, days=1): # Récupération des valeurs de suivi sur zip/city/street de la veille yesterday = fields.Date.today() - timedelta(days=days) partner_model = self.env["ir.model"].search([("model", "=", "res.partner")]) address_values = self.env["ir.model.fields"].search( [ ("model_id", "=", partner_model.id), ("name", "in", ["street", "street2", "zip", "city"]), ] ) mail_tracking_value_ids = self.env["mail.tracking.value"].search( [ ("field", "in", address_values.ids), ("create_date", ">=", yesterday), ] ) # Récupération des messages de notif sur # res.partner en lien avec les valeurs de suivi mail_mess_ids = self.env["mail.message"].search( [ ("model", "=", "res.partner"), ("message_type", "=", "notification"), ("tracking_value_ids", "in", mail_tracking_value_ids.ids), ] ) partner_list = mail_mess_ids.mapped("res_id") # Récupération des partners pour calcul des données GPS partners = self.env["res.partner"].search( [ "|", "|", ("id", "in", partner_list), ("partner_latitude", "=", 0.0), ("partner_latitude", "=", False), "|", ("membership_status", "=", "member"), ("is_incubated", "=", True), ] ) i = 0 for partner in partners: partner.geo_localize() i += 1 time.sleep(1) _logger.info("Mise à jour de %d coordonnées", i)