From 4b056038d63576f3b87f3990e7dcaad5eb37a313 Mon Sep 17 00:00:00 2001
From: Nyavokevin <42602932+nyavokevin@users.noreply.github.com>
Date: Wed, 29 Oct 2025 17:17:50 +0300
Subject: [PATCH] add notification et crud fournisseur
---
.../Controllers/Api/ContactController.php | 24 +
.../app/Http/Requests/StoreContactRequest.php | 15 +-
.../Http/Requests/UpdateContactRequest.php | 15 +-
.../Resources/Contact/ContactResource.php | 17 +-
thanasoft-back/app/Models/Contact.php | 8 +-
.../app/Repositories/ContactRepository.php | 7 +
.../ContactRepositoryInterface.php | 2 +
...3700_add_fournisseur_to_contacts_table.php | 41 ++
thanasoft-back/routes/api.php | 1 +
.../src/components/GlobalNotification.vue | 252 ++++++++
.../CRM/AddFournisseurPresentation.vue | 39 ++
.../CRM/FournisseurDetailPresentation.vue | 32 +-
.../Organism/CRM/FournisseurPresentation.vue | 5 +-
.../fournisseur/FournisseurDetailContent.vue | 14 +-
.../molecules/form/NewClientForm.vue | 13 -
.../molecules/form/NewFournisseurForm.vue | 486 ++++++++++++++++
.../fournisseur/FournisseurContactModal.vue | 457 +++++++++++++++
.../fournisseur/FournisseurContactsTab.vue | 269 ++++++++-
.../fournisseur/FournisseurInfoTab.vue | 537 +++++++++++++++++-
.../fournisseur/FournisseurOverview.vue | 22 +-
.../fournisseur/FournisseurTabNavigation.vue | 14 +-
.../templates/CRM/NewFournisseurTemplate.vue | 21 +
thanasoft-front/src/router/index.js | 5 +
thanasoft-front/src/services/contact.ts | 20 +-
thanasoft-front/src/services/fournisseur.ts | 206 +++++++
thanasoft-front/src/stores/contactStore.ts | 33 ++
.../src/stores/fournisseurStore.ts | 308 ++++++++++
.../src/views/pages/CRM/AddClient.vue | 15 +-
.../src/views/pages/CRM/AddContact.vue | 24 +-
.../src/views/pages/CRM/ClientDetails.vue | 34 +-
.../pages/Fournisseurs/AddFournisseur.vue | 59 ++
.../pages/Fournisseurs/FournisseurDetails.vue | 181 +++---
.../views/pages/Fournisseurs/Fournisseurs.vue | 103 +---
33 files changed, 3006 insertions(+), 273 deletions(-)
create mode 100644 thanasoft-back/database/migrations/2025_10_29_103700_add_fournisseur_to_contacts_table.php
create mode 100644 thanasoft-front/src/components/GlobalNotification.vue
create mode 100644 thanasoft-front/src/components/Organism/CRM/AddFournisseurPresentation.vue
create mode 100644 thanasoft-front/src/components/molecules/form/NewFournisseurForm.vue
create mode 100644 thanasoft-front/src/components/molecules/fournisseur/FournisseurContactModal.vue
create mode 100644 thanasoft-front/src/components/templates/CRM/NewFournisseurTemplate.vue
create mode 100644 thanasoft-front/src/services/fournisseur.ts
create mode 100644 thanasoft-front/src/stores/fournisseurStore.ts
create mode 100644 thanasoft-front/src/views/pages/Fournisseurs/AddFournisseur.vue
diff --git a/thanasoft-back/app/Http/Controllers/Api/ContactController.php b/thanasoft-back/app/Http/Controllers/Api/ContactController.php
index 92b0cbc..493962b 100644
--- a/thanasoft-back/app/Http/Controllers/Api/ContactController.php
+++ b/thanasoft-back/app/Http/Controllers/Api/ContactController.php
@@ -187,4 +187,28 @@ class ContactController extends Controller
], 500);
}
}
+
+
+
+ public function getContactsByFournisseur(string $fournisseurId): JsonResponse
+ {
+ try {
+ $intId = (int) $fournisseurId;
+ $contacts = $this->contactRepository->getByFournisseurId($intId);
+ return response()->json([
+ 'data' => ContactResource::collection($contacts),
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error fetching contacts by fournisseur: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'fournisseur_id' => $fournisseurId,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des contacts du fournisseur.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
}
diff --git a/thanasoft-back/app/Http/Requests/StoreContactRequest.php b/thanasoft-back/app/Http/Requests/StoreContactRequest.php
index ad8cf09..26f719c 100644
--- a/thanasoft-back/app/Http/Requests/StoreContactRequest.php
+++ b/thanasoft-back/app/Http/Requests/StoreContactRequest.php
@@ -22,7 +22,8 @@ class StoreContactRequest extends FormRequest
public function rules(): array
{
return [
- 'client_id' => 'required|exists:clients,id',
+ 'client_id' => 'nullable|exists:clients,id',
+ 'fournisseur_id' => 'nullable|exists:fournisseurs,id',
'first_name' => 'nullable|string|max:191',
'last_name' => 'nullable|string|max:191',
'email' => 'nullable|email|max:191',
@@ -34,8 +35,8 @@ class StoreContactRequest extends FormRequest
public function messages(): array
{
return [
- 'client_id.required' => 'Le client est obligatoire.',
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
+ 'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
'last_name.string' => 'Le nom doit être une chaîne de caractères.',
@@ -50,9 +51,17 @@ class StoreContactRequest extends FormRequest
public function withValidator($validator)
{
$validator->after(function ($validator) {
+ // At least one of client_id or fournisseur_id must be provided
+ if (empty($this->client_id) && empty($this->fournisseur_id)) {
+ $validator->errors()->add(
+ 'general',
+ 'Le contact doit être associé à un client ou un fournisseur.'
+ );
+ }
+
if (empty($this->first_name) && empty($this->last_name) && empty($this->email) && empty($this->phone)) {
$validator->errors()->add(
- 'general',
+ 'general',
'Au moins un champ (prénom, nom, email ou téléphone) doit être renseigné.'
);
}
diff --git a/thanasoft-back/app/Http/Requests/UpdateContactRequest.php b/thanasoft-back/app/Http/Requests/UpdateContactRequest.php
index 7f65dd4..f960bf0 100644
--- a/thanasoft-back/app/Http/Requests/UpdateContactRequest.php
+++ b/thanasoft-back/app/Http/Requests/UpdateContactRequest.php
@@ -22,7 +22,8 @@ class UpdateContactRequest extends FormRequest
public function rules(): array
{
return [
- 'client_id' => 'required|exists:clients,id',
+ 'client_id' => 'nullable|exists:clients,id',
+ 'fournisseur_id' => 'nullable|exists:fournisseurs,id',
'first_name' => 'nullable|string|max:191',
'last_name' => 'nullable|string|max:191',
'email' => 'nullable|email|max:191',
@@ -34,8 +35,8 @@ class UpdateContactRequest extends FormRequest
public function messages(): array
{
return [
- 'client_id.required' => 'Le client est obligatoire.',
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
+ 'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
'last_name.string' => 'Le nom doit être une chaîne de caractères.',
@@ -51,9 +52,17 @@ class UpdateContactRequest extends FormRequest
public function withValidator($validator)
{
$validator->after(function ($validator) {
+ // At least one of client_id or fournisseur_id must be provided
+ if (empty($this->client_id) && empty($this->fournisseur_id)) {
+ $validator->errors()->add(
+ 'general',
+ 'Le contact doit être associé à un client ou un fournisseur.'
+ );
+ }
+
if (empty($this->first_name) && empty($this->last_name) && empty($this->email) && empty($this->phone)) {
$validator->errors()->add(
- 'general',
+ 'general',
'Au moins un champ (prénom, nom, email ou téléphone) doit être renseigné.'
);
}
diff --git a/thanasoft-back/app/Http/Resources/Contact/ContactResource.php b/thanasoft-back/app/Http/Resources/Contact/ContactResource.php
index 7cafebb..a377c23 100644
--- a/thanasoft-back/app/Http/Resources/Contact/ContactResource.php
+++ b/thanasoft-back/app/Http/Resources/Contact/ContactResource.php
@@ -18,6 +18,7 @@ class ContactResource extends JsonResource
return [
'id' => $this->id,
'client_id' => $this->client_id,
+ 'fournisseur_id' => $this->fournisseur_id,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'full_name' => $this->full_name,
@@ -28,10 +29,18 @@ class ContactResource extends JsonResource
'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
// Relations
- 'client' => $this->whenLoaded('client', [
- 'id' => $this->client->id,
- 'name' => $this->client->name,
- ]),
+ 'client' => $this->whenLoaded('client', function() {
+ return $this->client ? [
+ 'id' => $this->client->id,
+ 'name' => $this->client->name,
+ ] : null;
+ }),
+ 'fournisseur' => $this->whenLoaded('fournisseur', function() {
+ return $this->fournisseur ? [
+ 'id' => $this->fournisseur->id,
+ 'name' => $this->fournisseur->name,
+ ] : null;
+ }),
];
}
diff --git a/thanasoft-back/app/Models/Contact.php b/thanasoft-back/app/Models/Contact.php
index cfe21ea..5511f8f 100644
--- a/thanasoft-back/app/Models/Contact.php
+++ b/thanasoft-back/app/Models/Contact.php
@@ -16,7 +16,8 @@ class Contact extends Model
'position',
'notes',
'is_primary',
- 'client_id'
+ 'client_id',
+ 'fournisseur_id'
];
protected $casts = [
@@ -28,6 +29,11 @@ class Contact extends Model
return $this->belongsTo(Client::class);
}
+ public function fournisseur(): BelongsTo
+ {
+ return $this->belongsTo(Fournisseur::class);
+ }
+
/**
* Get the contact's full name.
*/
diff --git a/thanasoft-back/app/Repositories/ContactRepository.php b/thanasoft-back/app/Repositories/ContactRepository.php
index 8b4295d..0e56545 100644
--- a/thanasoft-back/app/Repositories/ContactRepository.php
+++ b/thanasoft-back/app/Repositories/ContactRepository.php
@@ -62,4 +62,11 @@ class ContactRepository extends BaseRepository implements ContactRepositoryInter
->where('client_id', $clientId)
->get();
}
+
+ public function getByFournisseurId(int $fournisseurId)
+ {
+ return $this->model->newQuery()
+ ->where('fournisseur_id', $fournisseurId)
+ ->get();
+ }
}
diff --git a/thanasoft-back/app/Repositories/ContactRepositoryInterface.php b/thanasoft-back/app/Repositories/ContactRepositoryInterface.php
index f8b4984..3eeb2db 100644
--- a/thanasoft-back/app/Repositories/ContactRepositoryInterface.php
+++ b/thanasoft-back/app/Repositories/ContactRepositoryInterface.php
@@ -9,4 +9,6 @@ interface ContactRepositoryInterface extends BaseRepositoryInterface
function paginate(int $perPage = 15, array $filters = []);
function getByClientId(int $clientId);
+
+ function getByFournisseurId(int $fournisseurId);
}
diff --git a/thanasoft-back/database/migrations/2025_10_29_103700_add_fournisseur_to_contacts_table.php b/thanasoft-back/database/migrations/2025_10_29_103700_add_fournisseur_to_contacts_table.php
new file mode 100644
index 0000000..52dd402
--- /dev/null
+++ b/thanasoft-back/database/migrations/2025_10_29_103700_add_fournisseur_to_contacts_table.php
@@ -0,0 +1,41 @@
+dropForeign(['client_id']);
+ $table->foreignId('client_id')->nullable()->change();
+ $table->foreign('client_id')->references('id')->on('clients')->onDelete('set null');
+
+ // Add fournisseur_id
+ $table->foreignId('fournisseur_id')->nullable()->after('client_id')->constrained('fournisseurs')->onDelete('set null');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('contacts', function (Blueprint $table) {
+ // Remove fournisseur_id
+ $table->dropForeign(['fournisseur_id']);
+ $table->dropColumn('fournisseur_id');
+
+ // Restore client_id to not nullable with cascade
+ $table->dropForeign(['client_id']);
+ $table->foreignId('client_id')->change();
+ $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
+ });
+ }
+};
diff --git a/thanasoft-back/routes/api.php b/thanasoft-back/routes/api.php
index 50ae280..63bdeff 100644
--- a/thanasoft-back/routes/api.php
+++ b/thanasoft-back/routes/api.php
@@ -53,4 +53,5 @@ Route::middleware('auth:sanctum')->group(function () {
// Fournisseur management
Route::get('/fournisseurs/searchBy', [FournisseurController::class, 'searchBy']);
Route::apiResource('fournisseurs', FournisseurController::class);
+ Route::get('fournisseurs/{fournisseurId}/contacts', [ContactController::class, 'getContactsByFournisseur']);
});
diff --git a/thanasoft-front/src/components/GlobalNotification.vue b/thanasoft-front/src/components/GlobalNotification.vue
new file mode 100644
index 0000000..26e7d70
--- /dev/null
+++ b/thanasoft-front/src/components/GlobalNotification.vue
@@ -0,0 +1,252 @@
+
+ {{ notification.title }}
+
+
Informations du client
- -Informations du fournisseur
+ +Liste des contacts
+Chargement des contacts...
+| + Contact + | ++ Email + | ++ Téléphone + | ++ Poste + | ++ Actions + | +
|---|---|---|---|---|
|
+
+
+
+
+
+ {{ getInitials(contact.full_name) }}
+
+
+
+ {{ contact.full_name }}++ Contact principal + + |
+
+ + {{ contact.email || "-" }} + + |
+
+ + {{ contact.phone || contact.mobile || "-" }} + + |
+
+ + {{ contact.position || "-" }} + + |
+
+
+
+
+
+ |
+
Aucun contact pour ce fournisseur
+Informations détaillées du fournisseur
-{{ fournisseur.name }}
+