• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

inclusion-numerique / coop-mediation-numerique / eb13771c-78ed-464f-a6ad-f5f351160114

12 Feb 2026 08:14AM UTC coverage: 7.176% (-3.1%) from 10.253%
eb13771c-78ed-464f-a6ad-f5f351160114

push

circleci

hugues-m
feat: change dataspace contract end date rules for sync

(cherry picked from commit ecb6908c1)

469 of 9914 branches covered (4.73%)

Branch coverage included in aggregate %.

0 of 12 new or added lines in 3 files covered. (0.0%)

1207 existing lines in 143 files now uncovered.

1330 of 15156 relevant lines covered (8.78%)

39.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/apps/web/src/features/structures/findOrCreateStructure.ts
1
import { searchAdresse } from '@app/web/external-apis/apiAdresse'
2
import { banFeatureToAdresseBanData } from '@app/web/external-apis/ban/banFeatureToAdresseBanData'
3
import { prismaClient } from '@app/web/prismaClient'
4
import { toStructureFromCartoStructure } from '@app/web/structure/toStructureFromCartoStructure'
5
import { v4 } from 'uuid'
6

7
export type StructureInput = {
8
  coopId?: string | null
9
  siret: string | null
10
  nom: string
11
  adresse: string
12
  codePostal: string
13
  codeInsee: string
14
  commune: string
15
  // Optional fields
16
  nomReferent?: string | null
17
  courrielReferent?: string | null
18
  telephoneReferent?: string | null
19
  creationParId?: string | null
20
}
21

UNCOV
22
const undeleteStructureIfDeleted = async ({
×
23
  id,
24
  suppression,
25
}: {
26
  id: string
27
  suppression: Date | null
28
}) => {
UNCOV
29
  if (suppression) {
×
30
    await prismaClient.structure.update({
×
31
      where: { id },
32
      data: {
33
        suppression: null,
34
        suppressionParId: null,
35
      },
36
    })
37
  }
38
}
39

40
/**
41
 * Generic helper to find or create a structure following this hierarchy:
42
 * 1. Find existing Structure by SIRET + nom
43
 * 2. Find StructureCartographieNationale by pivot (SIRET) → create Structure from it
44
 * 3. Fallback: Geocode via searchAdresse (BAN API) and create
45
 *
46
 * This is reusable for both V1 imports and Dataspace imports.
47
 */
UNCOV
48
export const findOrCreateStructure = async ({
×
49
  coopId,
50
  siret,
51
  nom,
52
  adresse,
53
  codePostal,
54
  codeInsee,
55
  commune,
56
  nomReferent,
57
  courrielReferent,
58
  telephoneReferent,
59
  creationParId,
60
}: StructureInput): Promise<{ id: string }> => {
61
  // If coopId is provided, it is the surest way to find the structure
UNCOV
62
  if (coopId) {
×
UNCOV
63
    const existingStructure = await prismaClient.structure.findFirst({
×
64
      where: {
65
        id: coopId,
66
      },
67
      select: {
68
        id: true,
69
        suppression: true,
70
      },
71
    })
UNCOV
72
    if (existingStructure) {
×
73
      await undeleteStructureIfDeleted(existingStructure)
×
74
      return existingStructure
×
75
    }
76
  }
77

78
  // Step 1: Find existing Structure by SIRET + nom (only if siret is provided)
UNCOV
79
  if (siret) {
×
UNCOV
80
    const existingStructure = await prismaClient.structure.findFirst({
×
81
      where: {
82
        siret,
83
        codeInsee,
84
        suppression: null,
85
      },
86
      select: {
87
        id: true,
88
        suppression: true,
89
      },
90
      orderBy: [
91
        {
92
          suppression: {
93
            sort: 'desc',
94
            nulls: 'last',
95
          },
96
        },
97
        {
98
          creation: 'desc',
99
        },
100
      ],
101
    })
102

UNCOV
103
    if (existingStructure) {
×
UNCOV
104
      await undeleteStructureIfDeleted(existingStructure)
×
UNCOV
105
      return existingStructure
×
106
    }
107
  }
108

109
  // Step 2: Find StructureCartographieNationale by pivot (SIRET) - only if siret is provided
UNCOV
110
  if (siret) {
×
111
    const cartoStructure =
UNCOV
112
      await prismaClient.structureCartographieNationale.findFirst({
×
113
        where: {
114
          pivot: siret,
115
        },
116
      })
117

UNCOV
118
    if (cartoStructure) {
×
119
      // Create structure from cartographie nationale data (has coordinates)
120
      const structureData = toStructureFromCartoStructure(cartoStructure)
×
121

122
      // Override with referent info if provided
123
      const createdStructure = await prismaClient.structure.create({
×
124
        data: {
125
          ...structureData,
126
          nomReferent: nomReferent ?? null,
×
127
          courrielReferent: courrielReferent ?? structureData.courriels?.at(0),
×
128
          telephoneReferent: telephoneReferent ?? structureData.telephone,
×
129
          creationParId,
130
        },
131
        select: {
132
          id: true,
133
        },
134
      })
135

136
      return createdStructure
×
137
    }
138
  }
139

140
  // Step 2b: Try to find existing structure by nom if no siret
UNCOV
141
  if (!siret) {
×
142
    const existingByNom = await prismaClient.structure.findFirst({
×
143
      where: {
144
        nom,
145
        codeInsee,
146
      },
147
      select: {
148
        id: true,
149
        suppression: true,
150
      },
151
      orderBy: [
152
        {
153
          suppression: {
154
            sort: 'desc',
155
            nulls: 'last',
156
          },
157
        },
158
        {
159
          creation: 'desc',
160
        },
161
      ],
162
    })
163

164
    if (existingByNom) {
×
165
      await undeleteStructureIfDeleted(existingByNom)
×
166
      return existingByNom
×
167
    }
168
  }
169

170
  // Step 3: Fallback - geocode via BAN API and create
UNCOV
171
  const fullAdresse = `${adresse}, ${codePostal} ${commune}`
×
UNCOV
172
  const adresseResult = await searchAdresse(fullAdresse)
×
173

UNCOV
174
  if (adresseResult) {
×
UNCOV
175
    const banData = banFeatureToAdresseBanData(adresseResult)
×
176

UNCOV
177
    return prismaClient.structure.create({
×
178
      data: {
179
        id: v4(),
180
        siret,
181
        nom,
182
        adresse: banData.nom,
183
        commune: banData.commune,
184
        codePostal: banData.codePostal,
185
        codeInsee: banData.codeInsee,
186
        latitude: banData.latitude,
187
        longitude: banData.longitude,
188
        nomReferent,
189
        courrielReferent,
190
        telephoneReferent,
191
        creationParId,
192
      },
193
      select: {
194
        id: true,
195
      },
196
    })
197
  }
198

199
  // No geocoding result - create without coordinates
UNCOV
200
  return prismaClient.structure.create({
×
201
    data: {
202
      id: v4(),
203
      siret,
204
      nom,
205
      adresse,
206
      commune,
207
      codePostal,
208
      codeInsee,
209
      nomReferent,
210
      courrielReferent,
211
      telephoneReferent,
212
      creationParId,
213
    },
214
    select: {
215
      id: true,
216
    },
217
  })
218
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc