diff --git a/crm/admin.py b/crm/admin.py index c6858ff..819de9a 100644 --- a/crm/admin.py +++ b/crm/admin.py @@ -11,9 +11,9 @@ from .models import ( @admin.register(Prospect) class ProspectAdmin(admin.ModelAdmin): - list_display = ('name', 'email', 'region', 'created_at') - list_filter = ('region', 'created_at') - search_fields = ('name', 'email', 'region') + list_display = ('entity_name', 'first_name', 'last_name', 'email', 'address', 'zip_code', 'city', 'created_at') + list_filter = ('zip_code', 'created_at') + search_fields = ('entity_name', 'first_name', 'last_name', 'email', 'zip_code', 'city') filter_horizontal = ('users',) date_hierarchy = 'created_at' diff --git a/crm/filters.py b/crm/filters.py index a78b63f..002ac07 100644 --- a/crm/filters.py +++ b/crm/filters.py @@ -3,16 +3,12 @@ import django_filters from .models import Event, Status, Prospect class ProspectFilter(django_filters.FilterSet): - region = django_filters.CharFilter(lookup_expr='icontains') + zip_code = django_filters.CharFilter(lookup_expr='icontains') events = django_filters.ModelMultipleChoiceFilter( queryset=Event.objects.all(), field_name='events', ) - statuses = django_filters.ModelMultipleChoiceFilter( - queryset=Status.objects.all(), - field_name='prospectstatus__status', - ) class Meta: model = Prospect - fields = ['region', 'events', 'statuses'] + fields = ['city', 'events'] diff --git a/crm/migrations/0003_remove_prospect_region_prospect_address_and_more.py b/crm/migrations/0003_remove_prospect_region_prospect_address_and_more.py new file mode 100644 index 0000000..7ed0884 --- /dev/null +++ b/crm/migrations/0003_remove_prospect_region_prospect_address_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1 on 2024-12-16 15:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('crm', '0002_alter_event_options_alter_prospect_options_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='prospect', + name='region', + ), + migrations.AddField( + model_name='prospect', + name='address', + field=models.CharField(blank=True, max_length=200, null=True), + ), + migrations.AddField( + model_name='prospect', + name='city', + field=models.CharField(blank=True, max_length=500, null=True), + ), + migrations.AddField( + model_name='prospect', + name='zip_code', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/crm/migrations/0004_remove_prospect_name_prospect_entity_name_and_more.py b/crm/migrations/0004_remove_prospect_name_prospect_entity_name_and_more.py new file mode 100644 index 0000000..47776b9 --- /dev/null +++ b/crm/migrations/0004_remove_prospect_name_prospect_entity_name_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1 on 2024-12-16 16:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('crm', '0003_remove_prospect_region_prospect_address_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='prospect', + name='name', + ), + migrations.AddField( + model_name='prospect', + name='entity_name', + field=models.CharField(blank=True, max_length=200, null=True), + ), + migrations.AddField( + model_name='prospect', + name='first_name', + field=models.CharField(blank=True, max_length=200, null=True), + ), + migrations.AddField( + model_name='prospect', + name='last_name', + field=models.CharField(blank=True, max_length=200, null=True), + ), + ] diff --git a/crm/migrations/0005_prospect_phone.py b/crm/migrations/0005_prospect_phone.py new file mode 100644 index 0000000..1b5a25f --- /dev/null +++ b/crm/migrations/0005_prospect_phone.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-12-16 16:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('crm', '0004_remove_prospect_name_prospect_entity_name_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='prospect', + name='phone', + field=models.CharField(blank=True, max_length=25, null=True), + ), + ] diff --git a/crm/models.py b/crm/models.py index e9be339..8baa4e9 100644 --- a/crm/models.py +++ b/crm/models.py @@ -12,8 +12,13 @@ class EventType(models.TextChoices): class Prospect(models.Model): email = models.EmailField(unique=True) - name = models.CharField(max_length=200) - region = models.CharField(max_length=100) + entity_name = models.CharField(max_length=200, null=True, blank=True) + first_name = models.CharField(max_length=200, null=True, blank=True) + last_name = models.CharField(max_length=200, null=True, blank=True) + address = models.CharField(max_length=200, null=True, blank=True) + zip_code = models.CharField(max_length=20, null=True, blank=True) + city = models.CharField(max_length=500, null=True, blank=True) + phone = models.CharField(max_length=25, null=True, blank=True) users = models.ManyToManyField(get_user_model(), blank=True) created_at = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey( @@ -37,7 +42,7 @@ class Prospect(models.Model): ] def __str__(self): - return f"{self.name} ({self.email})" + return ' - '.join(filter(None, [self.entity_name, self.first_name, self.last_name, f"({self.email})"])) class Status(models.Model): name = models.CharField(max_length=100, unique=True) diff --git a/crm/templates/crm/add_prospect.html b/crm/templates/crm/add_prospect.html index 01aeba1..0c60407 100644 --- a/crm/templates/crm/add_prospect.html +++ b/crm/templates/crm/add_prospect.html @@ -7,8 +7,18 @@
{% csrf_token %}
- - + + +
+ +
+ + +
+ +
+ +
@@ -17,8 +27,23 @@
- - + + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + + +{% endblock %} diff --git a/crm/templates/crm/events.html b/crm/templates/crm/events.html index 7c03a52..d2fcfd5 100644 --- a/crm/templates/crm/events.html +++ b/crm/templates/crm/events.html @@ -7,11 +7,18 @@ {% if request.user|is_crm_manager %}
- - Add Event + + + Prospects + + + Ajouter un évènement + + + Ajouter un prospect - - Add Prospect + + Import
diff --git a/crm/templates/crm/prospect_list.html b/crm/templates/crm/prospect_list.html index 2520d90..f82349b 100644 --- a/crm/templates/crm/prospect_list.html +++ b/crm/templates/crm/prospect_list.html @@ -17,7 +17,7 @@
- Import CSV + Import CSV Send Email
@@ -26,10 +26,12 @@ - Name + Entité + Prénom + Nom Email - Region - Status + Téléphone + Statut Actions @@ -37,9 +39,11 @@ {% for prospect in prospects %} - {{ prospect.name }} - {{ prospect.email }} - {{ prospect.region }} + {{ prospect.entity_name }} + {{ prospect.first_name }} + {{ prospect.last_name }} + {{ prospect.email }} + {{ prospect.phone }} {% for status in prospect.prospectstatus_set.all %} {{ status.status.name }} diff --git a/crm/urls.py b/crm/urls.py index 1b12da6..5a675b8 100644 --- a/crm/urls.py +++ b/crm/urls.py @@ -5,11 +5,11 @@ app_name = 'crm' urlpatterns = [ path('', views.EventListView.as_view(), name='planned_events'),path('', views.EventListView.as_view(), name='planned-events'), - path('events/add/', views.EventCreateView.as_view(), name='add_event'), + path('events/add/', views.EventCreateView.as_view(), name='add-event'), path('events//edit/', views.EditEventView.as_view(), name='edit_event'), path('events//start/', views.StartEventView.as_view(), name='start_event'), path('prospects/', views.ProspectListView.as_view(), name='prospect-list'), - path('add-prospect/', views.add_prospect, name='add_prospect'), - path('prospects/import/', views.CSVImportView.as_view(), name='prospect-import'), + path('add-prospect/', views.add_prospect, name='add-prospect'), + path('prospects/import/', views.CSVImportView.as_view(), name='csv-import'), path('email/send/', views.SendBulkEmailView.as_view(), name='send-bulk-email'), ] diff --git a/crm/views.py b/crm/views.py index 4a772d1..0ee8c43 100644 --- a/crm/views.py +++ b/crm/views.py @@ -13,6 +13,7 @@ from django.contrib.sites.shortcuts import get_current_site from django.template.loader import render_to_string from django.core.mail import send_mail from django.conf import settings +from django.db import IntegrityError from .models import Event, Prospect, EmailTracker, EmailCampaign, EventType from .filters import ProspectFilter @@ -27,15 +28,27 @@ from datetime import datetime @permission_required('crm.view_crm', raise_exception=True) def add_prospect(request): if request.method == 'POST': - name = request.POST.get('name') + entity_name = request.POST.get('entity_name') + first_name = request.POST.get('first_name') + last_name = request.POST.get('last_name') email = request.POST.get('email') - region = request.POST.get('region') + phone = request.POST.get('phone') + address = request.POST.get('address') + zip_code = request.POST.get('zip_code') + city = request.POST.get('city') + # region = request.POST.get('region') try: prospect = Prospect.objects.create( - name=name, + entity_name=entity_name, + first_name=first_name, + last_name=last_name, email=email, - region=region, + phone=phone, + address=address, + zip_code=zip_code, + city=city, + # region=region, created_by=request.user, modified_by=request.user ) @@ -126,6 +139,7 @@ class ProspectListView(CRMAccessMixin, ListView): ) return context + class CSVImportView(CRMAccessMixin, FormView): template_name = 'crm/csv_import.html' form_class = CSVImportForm @@ -134,18 +148,64 @@ class CSVImportView(CRMAccessMixin, FormView): def form_valid(self, form): csv_file = TextIOWrapper( form.cleaned_data['csv_file'].file, - encoding='utf-8' + encoding='utf-8-sig' # Handle potential BOM in CSV ) - reader = csv.DictReader(csv_file) + reader = csv.reader(csv_file, delimiter=';') # Using semicolon delimiter + + # Skip header if exists + next(reader, None) + + created_count = 0 + updated_count = 0 + error_count = 0 for row in reader: - Prospect.objects.create( - email=row['email'], - name=row.get('name', ''), - region=row.get('region', ''), - created_by=self.request.user, - modified_by=self.request.user - ) + try: + if len(row) < 10: # Ensure we have enough columns + continue + + # Extract data from correct columns + entity_name = row[0].strip() + last_name = row[1].strip() + first_name = row[2].strip() + email = row[3].strip() + phone = row[4].strip() + zip_code = row[8].strip() + city = row[9].strip() + + # Try to update existing prospect or create new one + prospect, created = Prospect.objects.update_or_create( + email=email, # Use email as unique identifier + defaults={ + 'entity_name': entity_name, + 'first_name': first_name, + 'last_name': last_name, + 'phone': phone, + 'zip_code': zip_code, + 'city': city, + 'modified_by': self.request.user, + } + ) + + if created: + prospect.created_by = self.request.user + prospect.save() + created_count += 1 + else: + updated_count += 1 + + except Exception as e: + error_count += 1 + messages.error( + self.request, + f"Error processing row with email {email}: {str(e)}" + ) + + # Add success message + messages.success( + self.request, + f"Import completed: {created_count} created, {updated_count} updated, {error_count} errors" + ) return super().form_valid(form) diff --git a/db.sqlite3.main b/db.sqlite3.main deleted file mode 100644 index 9e8eb32..0000000 Binary files a/db.sqlite3.main and /dev/null differ