Aller au contenu

Fiche admin d'une société emprunteuse (BO Projets)

GET /administration/project-owner-company/:projectOwnerCompanyId, PATCH …/:projectOwnerCompanyId/identity, PATCH …/:projectOwnerCompanyId/lei, PATCH …/:projectOwnerCompanyId/banking et PUT …/:projectOwnerCompanyId/representatives/:representativeId (AdminAuthGuard) servent la fiche « Société » du BO Projets (BRI-2276 socle, BRI-2277 onglet Identité & LEI, BRI-2278 onglet RIB, BRI-2279 onglet Documents, BRI-2280 onglet Bénéficiaires effectifs). Chaque onglet de la fiche étend la vue et ajoute ses écritures dans sa propre issue.

Source : project-owner-company-administration.controller.ts, contrat projectOwnerCompanyDetail.endpoint.ts (api-communication-bricksoffice).

Vue

{ id, status, createdAt, company: { companyName?, siren?, legalForm?, shareCapital?, headquartersAddress?, postalCode?, city?, country? }, lei: { number?, obtainedAt?, expiresAt? }, banking?: { iban?, bic?, bankName?, accountHolder?, label? }, linkedSpvIds, representatives: [{ id, firstName?, lastName?, professionalEmail?, title?, birthDate?, birthCountry?, nationality?, residenceCountry? }], projects: [{ id, name?, category?, status, fundingAmountRequested? }] }

  • projects = les projets de la société (project_financing_request.companyId), du plus récent au plus ancien, avec le nom, le type et le montant demandé (fundingAmountRequested, centimes) de leur présentation et leur statut : la carte « Projets attachés » du BO.
  • createdAt = création de la row, rendue en lecture seule (« Date de création ») : pas de date d'immatriculation stockée. company ne porte pas rcsCity (non rendu) et n'ajoute aucun champ au JSON portail.
  • lei.obtainedAt = banking.leiObtainedAt ; lei.expiresAt = obtention + 1 an (computeLeiExpiresAt), comme les listes, jamais stockée. Un draft n'a pas de bloc banking : lei sort vide.
  • banking = le RIB tel qu'il est stocké, IBAN entier (l'ops le corrige depuis la fiche). Absent en draft, possiblement {} en completed tant que le portail n'a rien posé. bankName / accountHolder / label (libellé du compte) sont des champs additifs écrits uniquement par le PATCH ci-dessous.
  • linkedSpvIds = les properties.spvId distincts des projets financés de la société (la ligne properties n'existe qu'une fois le projet financé). Le BO lit les mandats SDD Lemonway de chacune via GET /administration/spv/:spvId/sdd-mandates ; vide = pas de mandat à afficher.
  • representatives = les personnes physiques de la société, qui sont aussi ses bénéficiaires effectifs (décision BRI-2280 : pas de liste séparée). La vue expose l'identité KYB que Lemonway exige d'un UBO — birthDate, birthCountry, nationality, residenceCountry — mais ni adresse, ni lieu de naissance, ni projectUserId (une ligne ouvre la fiche utilisateur par email). Ils vivent dans project_owner_company_representative (seuls les actifs, archivedAt nul, et seuls les individual : le représentant légal d'un représentant business n'est pas un bénéficiaire effectif de l'emprunteuse) ; professionalEmail y est stocké sous identity.email. title (fonction : « Président »…), birthCountry et nationality sont des champs additifs de l'identité, écrits uniquement par le PUT ci-dessous ; residenceCountry porte un libellé (« France ») quand il vient de l'App PDP, un code alpha-2 quand il vient du BO.
  • 404 project-owner-company-not-found, même code que la complétion portail.

PATCH identité

Body strict { country?, siren?, legalForm?, shareCapital?, headquartersAddress?, postalCode?, city? } ; absent = conservé, null = effacé ; companyName (non modifiable), clé inconnue, SIREN hors 9 chiffres ou pays hors alpha-2 minuscule = 400.

  • Tous statuts : corriger un SIREN sur un dossier complété est le cas ops. Hors draft, completedCompany exige chaque champ → un null répond 409 company-invalid-for-status, sans effet de bord (la row resterait sinon illisible : json est reparsé à chaque lecture).
  • Lock FOR UPDATE, merge produce inline, status intouché. Audit admin_action project-owner-company.update-identity, body complet (données société, pas de PII).

PATCH LEI

Body strict { leiNumber?, leiObtainedAt? } ; leiNumber = 20 caractères alphanumériques (forme ISO 17442, pas de contrôle GLEIF), leiObtainedAt = YYYY-MM-DD stocké à minuit UTC comme le save portail ; null = effacé.

  • completed / finalized uniquement : un draft n'a pas de bloc banking et le save portail comme PUT …/complete reconstruisent la row sans lui → 409 lei-not-editable. Sur completed sans bloc, le bloc naît avec le LEI seul (banking.partial()).
  • Mêmes champs que le formulaire de finalisation portail ; la péremption n'est pas un champ : la fiche et les listes la dérivent (+ 1 an). Un null retire leiObtainedAt.
  • Audit admin_action project-owner-company.update-lei, body complet.

Documents

Les pièces de la société (liste par fichier avec ses projets, dépôt sur les projets choisis, remplacement, retypage, suppression d'un fichier sur un seul projet, URL signée) vivent sur …/:projectOwnerCompanyId/documents* — voir Documents admin.

PUT représentant

Body strict { firstName, lastName, professionalEmail, title?, birthDate?, birthCountry?, nationality?, residenceCountry? } (YYYY-MM-DD, pays en alpha-2 minuscule) ; champ absent = conservé, null = retiré ; clé inconnue, email malformé ou pays hors alpha-2 = 400.

  • Personnes physiques uniquement, identité et KYB seulement : projectUserId, adresse postale et lieu de naissance sont intouchés. Aucune promotion de rôle, aucune invitation.
  • Tous statuts : l'ops complète l'identité KYB après que le porteur a fini (BRI-2280). Hors draft, l'identité complétée exige une date de naissance → birthDate: null ou residenceCountry: null répond 409 company-invalid-for-status, sans effet de bord : une identité complétée le reste. Même règle que l'identité.
  • Lock FOR UPDATE sur la société (il sérialise toute écriture sur ses représentants), merge produce inline sur l'identité, status intouché. Représentant inconnu, archivé ou d'une autre société → 404 representative-not-found ; id non uuid → 400.
  • Audit admin_action project-owner-company.representative-update : body réduit à firstName + lastName (email, naissance, nationalité = PII), cible dans params.
  • Pas de création depuis le BO : un représentant naît dans le portail (avec promotion / invitation à la complétion) ; le BO complète. Consommateur BO : l'onglet « Bénéficiaires effectifs » (une ligne par représentant, dialog d'édition) ; le panneau « Représentants » ouvre la fiche utilisateur.

PATCH RIB

Body strict { iban?, bic?, bankName?, accountHolder?, label? } ; champ absent = conservé ; bankName / accountHolder / label (libellé du compte, BO seulement) à null = retirés ; iban / bic n'acceptent jamais null ; clé inconnue = 400.

  • iban / bic sont validés (ibantools) et normalisés (espaces retirés, majuscules) par le contrat (iban_zod / bic_zod, api-communication-bricksoffice/src/lib) : la valeur stockée est canonique. Le portail, lui, accepte n'importe quelle chaîne — un IBAN saisi côté porteur peut donc être invalide jusqu'à sa première correction BO.
  • finalized seulement : jusque-là le porteur a la main sur le RIB (save et POST …/complete réécrivent le bloc banking) → 409 company-banking-not-editable, sans effet de bord.
  • Lock FOR UPDATE sur la société, merge produce inline champ par champ, status intouché. Sur une completed sans bloc, le bloc naît ; sur une finalized, iban et bic restent présents par construction — le schéma finalized les exige, et un bloc incomplet ferait échouer la relecture de la ligne chez tous ses lecteurs.
  • Audit admin_action project-owner-company.update-banking : body vide (IBAN = donnée bancaire), cible dans params.
  • Aucune génération de mandat SDD : un changement d'IBAN rend le mandat Lemonway existant caduc, le nouveau mandat se signe depuis l'onglet Contrats du projet (BRI-2287). Le BO signale l'écart entre l'IBAN d'un mandat et le RIB.

Choix

  • Pas de liste beneficialOwners[] distincte : les représentants sont les bénéficiaires effectifs (Benoît, 15/09). Le modèle SPV legacy les sépare (beneficialOwners alpha-3, birth imbriqué) : le mapping à la migration part de representatives, et les BE legacy non-signataires n'ont pas de cible — à trancher avec Kevin dans le mapping Bubble.
  • Trois PATCH sous-ressource (/identity, /lei, /banking) plutôt qu'un PATCH /:id { company?, banking? } : une carte = un formulaire = une route, gardes de statut distinctes, un AdminActionName par écriture.
  • POST …/finalization/complete reconstruit banking explicitement au passage finalized : il recopie bankName / accountHolder / label plutôt que de les perdre en silence.
  • Les lectures linkedSpvIds et dossiers liés vivent dans ProjectOwnerCompanyRepository (elles partent de la société) plutôt que dans ProjectFinancingRequestRepository, déjà à la limite de taille.