Skip to content

Commit 5604a1c

Browse files
committed
✨(bulk) add SIREN import option in addition to SIRETs
1 parent 78f8e9e commit 5604a1c

File tree

3 files changed

+84
-45
lines changed

3 files changed

+84
-45
lines changed

src/backend/core/admin.py

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,21 @@ def save(self, commit=True):
5555

5656

5757
class BulkSiretImportForm(forms.ModelForm):
58-
"""Form for bulk importing SIRETs to create OperatorOrganizationRole entries."""
58+
"""Form for bulk importing SIRETs or SIRENs to create OperatorOrganizationRole entries."""
5959

6060
siret_list = forms.CharField(
6161
widget=forms.Textarea(
6262
attrs={
6363
"rows": 15,
6464
"cols": 50,
6565
"placeholder": _(
66-
"Enter one SIRET per line:\n12345678901234\n98765432109876\n..."
66+
"Enter one SIRET (14 digits) or SIREN (9 digits) per line:\n12345678901234\n987654321\n..."
6767
),
6868
}
6969
),
70-
label=_("SIRET List"),
70+
label=_("SIRET/SIREN List"),
7171
help_text=_(
72-
"Enter one SIRET per line. Only 14-digit SIRET codes will be processed."
72+
"Enter one SIRET (14 digits) or SIREN (9 digits) per line. Both formats are accepted."
7373
),
7474
)
7575

@@ -93,38 +93,43 @@ def __init__(self, *args, **kwargs):
9393
self.fields["role"].initial = "admin"
9494

9595
def clean_siret_list(self):
96-
"""Clean and validate the SIRET list."""
96+
"""Clean and validate the SIRET/SIREN list."""
9797
siret_list = self.cleaned_data.get("siret_list", "")
9898
if not siret_list.strip():
99-
raise forms.ValidationError(_("Please enter at least one SIRET."))
99+
raise forms.ValidationError(_("Please enter at least one SIRET or SIREN."))
100100

101101
# Split by lines and clean
102102
siret_lines = [line.strip() for line in siret_list.split("\n") if line.strip()]
103103

104104
if not siret_lines:
105-
raise forms.ValidationError(_("Please enter at least one valid SIRET."))
105+
raise forms.ValidationError(
106+
_("Please enter at least one valid SIRET or SIREN.")
107+
)
106108

107-
# Validate SIRET format (14 digits)
108-
valid_sirets = []
109-
invalid_sirets = []
109+
# Validate SIRET (14 digits) or SIREN (9 digits) format
110+
valid_codes = []
111+
invalid_codes = []
110112

111-
for siret in siret_lines:
113+
for code in siret_lines:
112114
# Remove any spaces or dashes
113-
clean_siret = siret.replace(" ", "").replace("-", "")
114-
if clean_siret.isdigit() and len(clean_siret) == 14:
115-
valid_sirets.append(clean_siret)
115+
clean_code = code.replace(" ", "").replace("-", "")
116+
if clean_code.isdigit() and len(clean_code) in (9, 14):
117+
valid_codes.append(clean_code)
116118
else:
117-
invalid_sirets.append(siret)
119+
invalid_codes.append(code)
118120

119-
if invalid_sirets:
121+
if invalid_codes:
120122
raise forms.ValidationError(
121-
_("Invalid SIRET format: {}. SIRETs must be exactly 14 digits.").format(
122-
", ".join(invalid_sirets[:5])
123-
+ ("..." if len(invalid_sirets) > 5 else "")
123+
_(
124+
"Invalid format: {}. SIRETs must be exactly 14 digits, "
125+
"SIRENs must be exactly 9 digits."
126+
).format(
127+
", ".join(invalid_codes[:5])
128+
+ ("..." if len(invalid_codes) > 5 else "")
124129
)
125130
)
126131

127-
return valid_sirets
132+
return valid_codes
128133

129134

130135
class OperatorUsersInline(admin.TabularInline):
@@ -511,15 +516,50 @@ def bulk_import_view(self, request):
511516
role = form.cleaned_data["role"]
512517
siret_list = form.cleaned_data["siret_list"]
513518

514-
# Process SIRETs
519+
# Process SIRETs and SIRENs
515520
created_count = 0
516-
not_found_sirets = []
521+
not_found_codes = []
517522
already_exists_count = 0
518523

519524
with transaction.atomic():
520-
for siret in siret_list:
521-
try:
522-
organization = models.Organization.objects.get(siret=siret)
525+
for code in siret_list:
526+
# Determine if it's a SIRET (14 digits) or SIREN (9 digits)
527+
is_siren = len(code) == 9
528+
529+
if is_siren:
530+
# Look up by SIREN (may match multiple organizations)
531+
organizations = models.Organization.objects.filter(
532+
siren=code
533+
)
534+
if not organizations.exists():
535+
not_found_codes.append(code)
536+
continue
537+
538+
# Create relationships for all organizations with this SIREN
539+
for organization in organizations:
540+
# Check if the relationship already exists
541+
if models.OperatorOrganizationRole.objects.filter(
542+
operator=operator, organization=organization
543+
).exists():
544+
already_exists_count += 1
545+
continue
546+
547+
# Create the relationship
548+
models.OperatorOrganizationRole.objects.create(
549+
operator=operator,
550+
organization=organization,
551+
role=role,
552+
)
553+
created_count += 1
554+
else:
555+
try:
556+
# Look up by SIRET (exact match, one organization)
557+
organization = models.Organization.objects.get(
558+
siret=code
559+
)
560+
except models.Organization.DoesNotExist:
561+
not_found_codes.append(code)
562+
continue
523563

524564
# Check if the relationship already exists
525565
if models.OperatorOrganizationRole.objects.filter(
@@ -534,31 +574,28 @@ def bulk_import_view(self, request):
534574
)
535575
created_count += 1
536576

537-
except models.Organization.DoesNotExist:
538-
not_found_sirets.append(siret)
539-
540577
# Show success message with detailed results
541578
message_parts = [
542579
_("Bulk import completed:"),
543580
_("{created} created").format(created=created_count),
544581
_("{already_exists} already existed").format(
545582
already_exists=already_exists_count
546583
),
547-
_("{not_found} not found").format(not_found=len(not_found_sirets)),
584+
_("{not_found} not found").format(not_found=len(not_found_codes)),
548585
]
549586

550-
if not_found_sirets:
587+
if not_found_codes:
551588
message_parts.append(
552-
_("SIRETs not found: {sirets}").format(
553-
sirets=", ".join(not_found_sirets[:10])
554-
+ ("..." if len(not_found_sirets) > 10 else "")
589+
_("Codes not found: {codes}").format(
590+
codes=", ".join(not_found_codes[:10])
591+
+ ("..." if len(not_found_codes) > 10 else "")
555592
)
556593
)
557594

558595
# Store results in session for results page
559596
request.session["bulk_import_results"] = {
560597
"created_count": created_count,
561-
"not_found_sirets": not_found_sirets,
598+
"not_found_codes": not_found_codes,
562599
"already_exists_count": already_exists_count,
563600
"operator_name": operator.name,
564601
"role": role,
@@ -574,7 +611,7 @@ def bulk_import_view(self, request):
574611
).format(
575612
created=created_count,
576613
already_exists=already_exists_count,
577-
not_found=len(not_found_sirets),
614+
not_found=len(not_found_codes),
578615
),
579616
)
580617

@@ -585,7 +622,9 @@ def bulk_import_view(self, request):
585622
form = BulkSiretImportForm()
586623

587624
# Use Django's native admin add_view method with custom form
588-
return self.add_view(request, extra_context={"title": _("Bulk Import SIRETs")})
625+
return self.add_view(
626+
request, extra_context={"title": _("Bulk Import SIRETs/SIRENs")}
627+
)
589628

590629
def bulk_import_results_view(self, request):
591630
"""View for displaying bulk import results."""

src/backend/core/templates/admin/core/operatororganizationrole/bulk_import_results.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ <h2>{% trans 'Summary' %}</h2>
2727
<td>{{ results.role|capfirst }}</td>
2828
</tr>
2929
<tr>
30-
<th>{% trans 'Total SIRETs processed' %}:</th>
30+
<th>{% trans 'Total codes processed' %}:</th>
3131
<td>{{ results.total_processed }}</td>
3232
</tr>
3333
<tr>
@@ -40,27 +40,27 @@ <h2>{% trans 'Summary' %}</h2>
4040
</tr>
4141
<tr>
4242
<th>{% trans 'Not found in database' %}:</th>
43-
<td><strong style="color: red;">{{ results.not_found_sirets|length }}</strong></td>
43+
<td><strong style="color: red;">{{ results.not_found_codes|length }}</strong></td>
4444
</tr>
4545
</table>
4646
</div>
4747

48-
{% if results.not_found_sirets %}
48+
{% if results.not_found_codes %}
4949
<div class="not-found-sirets">
50-
<h2>{% trans 'SIRETs Not Found in Organization Database' %}</h2>
51-
<p>{% trans 'The following SIRETs were not found in the Organization database and could not be processed:' %}</p>
50+
<h2>{% trans 'Codes Not Found in Organization Database' %}</h2>
51+
<p>{% trans 'The following SIRETs or SIRENs were not found in the Organization database and could not be processed:' %}</p>
5252

5353
<div class="siret-list">
54-
<textarea readonly rows="10" cols="20" style="font-family: monospace; font-size: 12px;">{% for siret in results.not_found_sirets %}{{ siret }}
54+
<textarea readonly rows="10" cols="20" style="font-family: monospace; font-size: 12px;">{% for code in results.not_found_codes %}{{ code }}
5555
{% endfor %}</textarea>
5656
</div>
5757

58-
<p><strong>{% trans 'Note' %}:</strong> {% trans 'These SIRETs may need to be added to the Organization database first, or they may be invalid.' %}</p>
58+
<p><strong>{% trans 'Note' %}:</strong> {% trans 'These codes may need to be added to the Organization database first, or they may be invalid.' %}</p>
5959
</div>
6060
{% endif %}
6161

6262
<div class="actions">
63-
<a href="{% url 'admin:core_operatororganizationrole_bulk_import' %}" class="button">{% trans 'Import More SIRETs' %}</a>
63+
<a href="{% url 'admin:core_operatororganizationrole_bulk_import' %}" class="button">{% trans 'Import More SIRETs/SIRENs' %}</a>
6464
<a href="{% url 'admin:core_operatororganizationrole_changelist' %}" class="button">{% trans 'View All Operator Organization Roles' %}</a>
6565
</div>
6666

src/backend/core/templates/admin/core/operatororganizationrole/change_list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{% block object-tools-items %}
55
<li>
66
<a href="{{ bulk_import_url }}" class="addlink">
7-
{% trans 'Bulk Import SIRETs' %}
7+
{% trans 'Bulk Import SIRETs/SIRENs' %}
88
</a>
99
</li>
1010
{{ block.super }}

0 commit comments

Comments
 (0)