diff --git a/thanasoft-back/app/Http/Controllers/Api/AvoirController.php b/thanasoft-back/app/Http/Controllers/Api/AvoirController.php new file mode 100644 index 0000000..8bf3591 --- /dev/null +++ b/thanasoft-back/app/Http/Controllers/Api/AvoirController.php @@ -0,0 +1,151 @@ +avoirRepository->all(); + return AvoirResource::collection($avoirs); + } catch (\Exception $e) { + Log::error('Error fetching avoirs: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la récupération des avoirs.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + /** + * Store a newly created credit. + */ + public function store(StoreAvoirRequest $request): AvoirResource|JsonResponse + { + try { + $avoir = $this->avoirRepository->create($request->validated()); + return new AvoirResource($avoir); + } catch (\Exception $e) { + Log::error('Error creating avoir: ' . $e->getMessage(), [ + 'exception' => $e, + 'data' => $request->validated(), + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la création de l\'avoir.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + /** + * Display the specified credit. + */ + public function show(string $id): AvoirResource|JsonResponse + { + try { + $avoir = $this->avoirRepository->find($id); + + if (!$avoir) { + return response()->json([ + 'message' => 'Avoir non trouvé.', + ], 404); + } + + return new AvoirResource($avoir); + } catch (\Exception $e) { + Log::error('Error fetching avoir: ' . $e->getMessage(), [ + 'exception' => $e, + 'avoir_id' => $id, + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la récupération de l\'avoir.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + /** + * Update the specified credit. + */ + public function update(UpdateAvoirRequest $request, string $id): AvoirResource|JsonResponse + { + try { + $updated = $this->avoirRepository->update($id, $request->validated()); + + if (!$updated) { + return response()->json([ + 'message' => 'Avoir non trouvé ou échec de la mise à jour.', + ], 404); + } + + $avoir = $this->avoirRepository->find($id); + return new AvoirResource($avoir); + } catch (\Exception $e) { + Log::error('Error updating avoir: ' . $e->getMessage(), [ + 'exception' => $e, + 'avoir_id' => $id, + 'data' => $request->validated(), + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la mise à jour de l\'avoir.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + /** + * Remove the specified credit. + */ + public function destroy(string $id): JsonResponse + { + try { + $deleted = $this->avoirRepository->delete($id); + + if (!$deleted) { + return response()->json([ + 'message' => 'Avoir non trouvé ou échec de la suppression.', + ], 404); + } + + return response()->json([ + 'message' => 'Avoir supprimé avec succès.', + ], 200); + } catch (\Exception $e) { + Log::error('Error deleting avoir: ' . $e->getMessage(), [ + 'exception' => $e, + 'avoir_id' => $id, + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la suppression de l\'avoir.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } +} diff --git a/thanasoft-back/app/Http/Requests/StoreAvoirRequest.php b/thanasoft-back/app/Http/Requests/StoreAvoirRequest.php new file mode 100644 index 0000000..2e5202a --- /dev/null +++ b/thanasoft-back/app/Http/Requests/StoreAvoirRequest.php @@ -0,0 +1,48 @@ + 'required|exists:clients,id', + 'invoice_id' => 'nullable|exists:invoices,id', + 'group_id' => 'nullable|exists:client_groups,id', + 'status' => 'required|in:brouillon,emis,applique,annule', + 'avoir_date' => 'required|date', + 'due_date' => 'nullable|date|after_or_equal:avoir_date', + 'currency' => 'required|string|size:3', + 'total_ht' => 'required|numeric', + 'total_tva' => 'required|numeric', + 'total_ttc' => 'required|numeric', + 'reason_type' => 'required|in:remboursement_total,remboursement_partiel,reduction,erreur_facturation,retour_marchandise,accord_commercial,autre', + 'reason_description' => 'nullable|string', + 'e_invoice_status' => 'nullable|in:cree,transmis,accepte,refuse,en_litige,acquitte,archive', + 'refund_status' => 'nullable|in:non_rembourse,en_cours,partiellement_rembourse,rembourse,compense', + 'refund_date' => 'nullable|date', + 'refund_method' => 'nullable|in:virement,cheque,carte_credit,compensation_future,autre', + 'compensation_invoice_id' => 'nullable|exists:invoices,id', + 'compensation_amount' => 'nullable|numeric|min:0', + 'lines' => 'required|array|min:1', + 'lines.*.product_id' => 'nullable|exists:products,id', + 'lines.*.invoice_line_id' => 'nullable|exists:invoice_lines,id', + 'lines.*.description' => 'required|string', + 'lines.*.quantity' => 'required|numeric', + 'lines.*.unit_price' => 'required|numeric', + 'lines.*.tva_rate' => 'required|numeric', + 'lines.*.total_ht' => 'required|numeric', + 'lines.*.total_tva' => 'required|numeric', + 'lines.*.total_ttc' => 'required|numeric', + 'lines.*.notes' => 'nullable|string', + ]; + } +} diff --git a/thanasoft-back/app/Http/Requests/UpdateAvoirRequest.php b/thanasoft-back/app/Http/Requests/UpdateAvoirRequest.php new file mode 100644 index 0000000..2fa4248 --- /dev/null +++ b/thanasoft-back/app/Http/Requests/UpdateAvoirRequest.php @@ -0,0 +1,27 @@ + 'sometimes|required|in:brouillon,emis,applique,annule', + 'avoir_date' => 'sometimes|required|date', + 'due_date' => 'nullable|date|after_or_equal:avoir_date', + 'refund_status' => 'nullable|in:non_rembourse,en_cours,partiellement_rembourse,rembourse,compense', + 'refund_date' => 'nullable|date', + 'refund_method' => 'nullable|in:virement,cheque,carte_credit,compensation_future,autre', + 'reason_description' => 'nullable|string', + 'notes' => 'nullable|string', + ]; + } +} diff --git a/thanasoft-back/app/Http/Resources/AvoirLineResource.php b/thanasoft-back/app/Http/Resources/AvoirLineResource.php new file mode 100644 index 0000000..1f53d95 --- /dev/null +++ b/thanasoft-back/app/Http/Resources/AvoirLineResource.php @@ -0,0 +1,28 @@ + $this->id, + 'avoir_id' => $this->avoir_id, + 'product_id' => $this->product_id, + 'invoice_line_id' => $this->invoice_line_id, + 'description' => $this->description, + 'quantity' => $this->quantity, + 'unit_price' => $this->unit_price, + 'tva_rate' => $this->tva_rate, + 'total_ht' => $this->total_ht, + 'total_tva' => $this->total_tva, + 'total_ttc' => $this->total_ttc, + 'notes' => $this->notes, + 'product' => $this->whenLoaded('product'), + ]; + } +} diff --git a/thanasoft-back/app/Http/Resources/AvoirResource.php b/thanasoft-back/app/Http/Resources/AvoirResource.php new file mode 100644 index 0000000..f0e54e2 --- /dev/null +++ b/thanasoft-back/app/Http/Resources/AvoirResource.php @@ -0,0 +1,42 @@ + $this->id, + 'client_id' => $this->client_id, + 'invoice_id' => $this->invoice_id, + 'group_id' => $this->group_id, + 'avoir_number' => $this->avoir_number, + 'status' => $this->status, + 'avoir_date' => $this->avoir_date, + 'due_date' => $this->due_date, + 'currency' => $this->currency, + 'total_ht' => $this->total_ht, + 'total_tva' => $this->total_tva, + 'total_ttc' => $this->total_ttc, + 'reason_type' => $this->reason_type, + 'reason_description' => $this->reason_description, + 'e_invoice_status' => $this->e_invoice_status, + 'refund_status' => $this->refund_status, + 'refund_date' => $this->refund_date, + 'refund_method' => $this->refund_method, + 'compensation_invoice_id' => $this->compensation_invoice_id, + 'compensation_amount' => $this->compensation_amount, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + 'client' => $this->whenLoaded('client'), + 'invoice' => $this->whenLoaded('invoice'), + 'group' => $this->whenLoaded('group'), + 'lines' => AvoirLineResource::collection($this->whenLoaded('lines')), + 'history' => DocumentStatusHistoryResource::collection($this->whenLoaded('history')), + ]; + } +} diff --git a/thanasoft-back/app/Models/Avoir.php b/thanasoft-back/app/Models/Avoir.php new file mode 100644 index 0000000..6257d6e --- /dev/null +++ b/thanasoft-back/app/Models/Avoir.php @@ -0,0 +1,98 @@ + 'date', + 'due_date' => 'date', + 'refund_date' => 'date', + 'total_ht' => 'decimal:2', + 'total_tva' => 'decimal:2', + 'total_ttc' => 'decimal:2', + 'compensation_amount' => 'decimal:2', + ]; + + protected static function booted() + { + static::creating(function ($avoir) { + // Auto-generate avoir number if not provided + if (empty($avoir->avoir_number)) { + $prefix = 'AV-' . now()->format('Ym') . '-'; + $lastAvoir = self::where('avoir_number', 'like', $prefix . '%') + ->orderBy('avoir_number', 'desc') + ->first(); + + if ($lastAvoir) { + // Extract numeric part + preg_match('/(\d+)$/', $lastAvoir->avoir_number, $matches); + $newNumber = $matches ? intval($matches[1]) + 1 : 1; + } else { + $newNumber = 1; + } + + $avoir->avoir_number = $prefix . str_pad((string)$newNumber, 4, '0', STR_PAD_LEFT); + } + }); + } + + public function client() + { + return $this->belongsTo(Client::class); + } + + public function invoice() + { + return $this->belongsTo(Invoice::class); + } + + public function group() + { + return $this->belongsTo(ClientGroup::class, 'group_id'); + } + + public function lines() + { + return $this->hasMany(AvoirLine::class); + } + + public function compensationInvoice() + { + return $this->belongsTo(Invoice::class, 'compensation_invoice_id'); + } + + public function history() + { + return $this->hasMany(DocumentStatusHistory::class, 'document_id') + ->where('document_type', 'avoir') + ->orderBy('changed_at', 'desc'); + } +} diff --git a/thanasoft-back/app/Models/AvoirLine.php b/thanasoft-back/app/Models/AvoirLine.php new file mode 100644 index 0000000..bd7e691 --- /dev/null +++ b/thanasoft-back/app/Models/AvoirLine.php @@ -0,0 +1,49 @@ + 'decimal:3', + 'unit_price' => 'decimal:4', + 'tva_rate' => 'decimal:2', + 'total_ht' => 'decimal:2', + 'total_tva' => 'decimal:2', + 'total_ttc' => 'decimal:2', + ]; + + public function avoir() + { + return $this->belongsTo(Avoir::class); + } + + public function product() + { + return $this->belongsTo(Product::class); + } + + public function invoiceLine() + { + return $this->belongsTo(InvoiceLine::class); + } +} diff --git a/thanasoft-back/app/Providers/AppServiceProvider.php b/thanasoft-back/app/Providers/AppServiceProvider.php index 6d1c9b4..c5414be 100644 --- a/thanasoft-back/app/Providers/AppServiceProvider.php +++ b/thanasoft-back/app/Providers/AppServiceProvider.php @@ -81,6 +81,16 @@ class AppServiceProvider extends ServiceProvider $this->app->bind(\App\Repositories\InvoiceLineRepositoryInterface::class, \App\Repositories\InvoiceLineRepository::class); + $this->app->bind(\App\Repositories\AvoirRepositoryInterface::class, function ($app) { + return new \App\Repositories\AvoirRepository( + $app->make(\App\Models\Avoir::class), + $app->make(\App\Repositories\AvoirLineRepositoryInterface::class), + $app->make(\App\Repositories\ClientActivityTimelineRepositoryInterface::class) + ); + }); + + $this->app->bind(\App\Repositories\AvoirLineRepositoryInterface::class, \App\Repositories\AvoirLineRepository::class); + $this->app->bind(\App\Repositories\QuoteRepositoryInterface::class, function ($app) { return new \App\Repositories\QuoteRepository( $app->make(\App\Models\Quote::class), diff --git a/thanasoft-back/app/Repositories/AvoirLineRepository.php b/thanasoft-back/app/Repositories/AvoirLineRepository.php new file mode 100644 index 0000000..4014105 --- /dev/null +++ b/thanasoft-back/app/Repositories/AvoirLineRepository.php @@ -0,0 +1,15 @@ +model->with(['client', 'lines.product'])->get($columns); + } + + public function create(array $data): Avoir + { + return DB::transaction(function () use ($data) { + try { + // Create the avoir + $avoir = parent::create($data); + + // Create the avoir lines + if (isset($data['lines']) && is_array($data['lines'])) { + foreach ($data['lines'] as $lineData) { + $lineData['avoir_id'] = $avoir->id; + $this->avoirLineRepository->create($lineData); + } + } + + // Record initial status history + $this->recordHistory((int)$avoir->id, null, $avoir->status, 'Avoir created'); + + try { + $this->timelineRepository->logActivity([ + 'client_id' => $avoir->client_id, + 'actor_type' => 'user', + 'actor_user_id' => auth()->id(), + 'event_type' => 'avoir_created', + 'entity_type' => 'avoir', + 'entity_id' => $avoir->id, + 'title' => 'Nouvel avoir créé', + 'description' => "L'avoir #{$avoir->avoir_number} a été créé.", + 'created_at' => now(), + ]); + } catch (\Exception $e) { + Log::error("Failed to log avoir creation activity: " . $e->getMessage()); + } + + return $avoir; + } catch (\Exception $e) { + Log::error('Error creating avoir with lines: ' . $e->getMessage(), [ + 'exception' => $e, + 'data' => $data, + ]); + throw $e; + } + }); + } + + public function update(int|string $id, array $attributes): bool + { + return DB::transaction(function () use ($id, $attributes) { + try { + $avoir = $this->find($id); + if (!$avoir) { + return false; + } + + $oldStatus = $avoir->status; + + // Update the avoir + $updated = parent::update($id, $attributes); + + if ($updated) { + $newStatus = $attributes['status'] ?? $oldStatus; + + // If status changed, record history + if ($oldStatus !== $newStatus) { + $this->recordHistory((int) $id, $oldStatus, $newStatus, 'Avoir status updated'); + } + } + + return $updated; + } catch (\Exception $e) { + Log::error('Error updating avoir: ' . $e->getMessage(), [ + 'id' => $id, + 'attributes' => $attributes, + 'exception' => $e, + ]); + throw $e; + } + }); + } + + public function find(int|string $id, array $columns = ['*']): ?Avoir + { + return $this->model->with(['client', 'lines.product', 'history.user'])->find($id, $columns); + } + + private function recordHistory(int $avoirId, ?string $oldStatus, string $newStatus, ?string $comment = null): void + { + \App\Models\DocumentStatusHistory::create([ + 'document_type' => 'avoir', + 'document_id' => $avoirId, + 'old_status' => $oldStatus, + 'new_status' => $newStatus, + 'changed_by' => auth()->id(), + 'comment' => $comment, + 'changed_at' => now(), + ]); + } +} diff --git a/thanasoft-back/app/Repositories/AvoirRepositoryInterface.php b/thanasoft-back/app/Repositories/AvoirRepositoryInterface.php new file mode 100644 index 0000000..df45f61 --- /dev/null +++ b/thanasoft-back/app/Repositories/AvoirRepositoryInterface.php @@ -0,0 +1,9 @@ +id(); + $table->unsignedBigInteger('client_id'); + $table->unsignedBigInteger('invoice_id')->nullable(); // The original invoice being credited + $table->unsignedBigInteger('group_id')->nullable(); + + $table->string('avoir_number', 191); + $table->enum('status', ['brouillon', 'emis', 'applique', 'annule'])->default('brouillon'); + + // Dates + $table->date('avoir_date')->useCurrent(); + $table->date('due_date')->nullable(); + + // Currency and amounts (typically negative or representing credit) + $table->char('currency', 3)->default('EUR'); + $table->decimal('total_ht', 14, 2)->default(0); + $table->decimal('total_tva', 14, 2)->default(0); + $table->decimal('total_ttc', 14, 2)->default(0); + + // Reason for credit note + $table->enum('reason_type', [ + 'remboursement_total', + 'remboursement_partiel', + 'reduction', + 'erreur_facturation', + 'retour_marchandise', + 'accord_commercial', + 'autre' + ])->default('autre'); + $table->text('reason_description')->nullable(); + + // E-invoicing status (if applicable) + $table->enum('e_invoice_status', [ + 'cree', 'transmis', 'accepte', 'refuse', 'en_litige', 'acquitte', 'archive' + ])->nullable()->default('cree'); + + // Refund information (for accounting reconciliation) + $table->enum('refund_status', [ + 'non_rembourse', 'en_cours', 'partiellement_rembourse', 'rembourse', 'compense' + ])->default('non_rembourse'); + $table->date('refund_date')->nullable(); + $table->enum('refund_method', [ + 'virement', 'cheque', 'carte_credit', 'compensation_future', 'autre' + ])->nullable(); + + // Compensation (if credit is applied to future invoices) + $table->unsignedBigInteger('compensation_invoice_id')->nullable(); + $table->decimal('compensation_amount', 14, 2)->default(0); + + $table->timestamps(); + + // Indexes for performance + $table->index(['status', 'due_date'], 'idx_avoirs_status_due'); + $table->index('invoice_id', 'idx_avoirs_invoice'); + $table->index('avoir_number', 'idx_avoirs_number'); + $table->index('refund_status', 'idx_avoirs_refund_status'); + $table->index('avoir_date', 'idx_avoirs_date'); + + // Foreign key constraints + $table->foreign('client_id', 'fk_avoirs_client')->references('id')->on('clients')->onDelete('cascade'); + $table->foreign('invoice_id', 'fk_avoirs_invoice')->references('id')->on('invoices')->onDelete('restrict'); + $table->foreign('group_id', 'fk_avoirs_group')->references('id')->on('client_groups')->onDelete('set null'); + $table->foreign('compensation_invoice_id', 'fk_avoirs_compensation')->references('id')->on('invoices')->onDelete('set null'); + }); + + Schema::create('avoir_lines', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('avoir_id'); + $table->unsignedBigInteger('product_id')->nullable(); + $table->unsignedBigInteger('invoice_line_id')->nullable(); // Reference to the original invoice line if applicable + + $table->string('description', 500); + $table->decimal('quantity', 10, 3)->default(1); + $table->decimal('unit_price', 14, 4); + $table->decimal('tva_rate', 5, 2)->default(0); + $table->decimal('total_ht', 14, 2); + $table->decimal('total_tva', 14, 2)->default(0); + $table->decimal('total_ttc', 14, 2)->default(0); + $table->text('notes')->nullable(); + + $table->timestamps(); + + $table->index('avoir_id', 'idx_avoir_lines_avoir'); + + $table->foreign('avoir_id', 'fk_avoir_lines_avoir')->references('id')->on('avoirs')->onDelete('cascade'); + $table->foreign('product_id', 'fk_avoir_lines_product')->references('id')->on('products')->onDelete('set null'); + $table->foreign('invoice_line_id', 'fk_avoir_lines_invoice_line')->references('id')->on('invoice_lines')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('avoir_lines'); + Schema::dropIfExists('avoirs'); + } +}; diff --git a/thanasoft-back/database/migrations/2026_01_26_120000_create_supplier_system_tables.php b/thanasoft-back/database/migrations/2026_01_26_120000_create_supplier_system_tables.php new file mode 100644 index 0000000..85082d9 --- /dev/null +++ b/thanasoft-back/database/migrations/2026_01_26_120000_create_supplier_system_tables.php @@ -0,0 +1,152 @@ +id(); + $table->unsignedBigInteger('fournisseur_id'); // Use existing fournisseurs table + + $table->string('po_number', 191); + $table->enum('status', ['brouillon', 'confirmee', 'livree', 'facturee', 'annulee'])->default('brouillon'); + $table->date('order_date')->useCurrent(); + $table->date('expected_date')->nullable(); + $table->char('currency', 3)->default('EUR'); + $table->decimal('total_ht', 14, 2)->default(0); + $table->decimal('total_tva', 14, 2)->default(0); + $table->decimal('total_ttc', 14, 2)->default(0); + $table->text('notes')->nullable(); + $table->string('delivery_address')->nullable(); + + $table->timestamps(); + + $table->unique('po_number', 'uq_po_number'); + $table->index('status', 'idx_po_status'); + $table->index('order_date', 'idx_po_order_date'); + + $table->foreign('fournisseur_id', 'fk_po_fournisseur')->references('id')->on('fournisseurs')->onDelete('cascade'); + }); + + // Purchase Order Lines + Schema::create('purchase_order_lines', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('purchase_order_id'); + $table->unsignedBigInteger('product_id')->nullable(); + + $table->text('description'); + $table->decimal('quantity', 14, 3)->default(1); + $table->decimal('unit_price', 12, 2); + $table->decimal('tva_rate', 5, 2)->default(0); // Use rate directly, no FK to tva_rates + $table->decimal('discount_pct', 5, 2)->default(0); + $table->decimal('total_ht', 14, 2); + + $table->timestamps(); + + $table->foreign('purchase_order_id', 'fk_pol_po')->references('id')->on('purchase_orders')->onDelete('cascade'); + $table->foreign('product_id', 'fk_pol_product')->references('id')->on('products')->onDelete('set null'); + }); + + // Goods Receipts (Réceptions de marchandises) + Schema::create('goods_receipts', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('purchase_order_id'); + + $table->string('receipt_number', 191); + $table->date('receipt_date')->useCurrent(); + $table->enum('status', ['brouillon', 'valide', 'annule'])->default('brouillon'); + $table->text('notes')->nullable(); + + $table->timestamps(); + + $table->unique(['purchase_order_id', 'receipt_number'], 'uq_gr_po_number'); + + $table->foreign('purchase_order_id', 'fk_gr_po')->references('id')->on('purchase_orders')->onDelete('cascade'); + }); + + // Goods Receipt Lines + Schema::create('goods_receipt_lines', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('goods_receipt_id'); + $table->unsignedBigInteger('product_id'); + $table->unsignedBigInteger('purchase_order_line_id')->nullable(); // Link to original order line + + $table->decimal('quantity_received', 14, 3); + $table->decimal('unit_price', 12, 2)->nullable(); + $table->text('notes')->nullable(); + + $table->timestamps(); + + $table->foreign('goods_receipt_id', 'fk_grl_gr')->references('id')->on('goods_receipts')->onDelete('cascade'); + $table->foreign('product_id', 'fk_grl_product')->references('id')->on('products'); + $table->foreign('purchase_order_line_id', 'fk_grl_pol')->references('id')->on('purchase_order_lines')->onDelete('set null'); + }); + + // Supplier Invoices (Factures Fournisseurs) + Schema::create('supplier_invoices', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('fournisseur_id'); + $table->unsignedBigInteger('purchase_order_id')->nullable(); // Optional link to purchase order + + $table->string('invoice_number', 191); + $table->date('invoice_date')->useCurrent(); + $table->date('due_date')->nullable(); + $table->enum('status', ['brouillon', 'en_attente', 'payee', 'annulee'])->default('brouillon'); + $table->char('currency', 3)->default('EUR'); + $table->decimal('total_ht', 14, 2)->default(0); + $table->decimal('total_tva', 14, 2)->default(0); + $table->decimal('total_ttc', 14, 2)->default(0); + $table->text('notes')->nullable(); + + $table->timestamps(); + + $table->unique(['fournisseur_id', 'invoice_number'], 'uq_supplier_invoice'); + $table->index('status', 'idx_si_status'); + $table->index('invoice_date', 'idx_si_invoice_date'); + + $table->foreign('fournisseur_id', 'fk_si_fournisseur')->references('id')->on('fournisseurs'); + $table->foreign('purchase_order_id', 'fk_si_po')->references('id')->on('purchase_orders')->onDelete('set null'); + }); + + // Supplier Invoice Lines + Schema::create('supplier_invoice_lines', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('supplier_invoice_id'); + $table->unsignedBigInteger('product_id')->nullable(); + $table->unsignedBigInteger('purchase_order_line_id')->nullable(); // Link to original order line + + $table->text('description'); + $table->decimal('quantity', 14, 3)->default(1); + $table->decimal('unit_price', 12, 2); + $table->decimal('tva_rate', 5, 2)->default(0); + $table->decimal('total_ht', 14, 2); + + $table->timestamps(); + + $table->foreign('supplier_invoice_id', 'fk_sil_si')->references('id')->on('supplier_invoices')->onDelete('cascade'); + $table->foreign('product_id', 'fk_sil_product')->references('id')->on('products')->onDelete('set null'); + $table->foreign('purchase_order_line_id', 'fk_sil_pol')->references('id')->on('purchase_order_lines')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('supplier_invoice_lines'); + Schema::dropIfExists('supplier_invoices'); + Schema::dropIfExists('goods_receipt_lines'); + Schema::dropIfExists('goods_receipts'); + Schema::dropIfExists('purchase_order_lines'); + Schema::dropIfExists('purchase_orders'); + } +}; diff --git a/thanasoft-back/routes/api.php b/thanasoft-back/routes/api.php index 10c363e..3eaa06e 100644 --- a/thanasoft-back/routes/api.php +++ b/thanasoft-back/routes/api.php @@ -80,6 +80,9 @@ Route::middleware('auth:sanctum')->group(function () { Route::post('/invoices/from-quote/{quoteId}', [\App\Http\Controllers\Api\InvoiceController::class, 'createFromQuote']); Route::apiResource('invoices', \App\Http\Controllers\Api\InvoiceController::class); + // Avoir management + Route::apiResource('avoirs', \App\Http\Controllers\Api\AvoirController::class); + // Fournisseur management Route::get('/fournisseurs/searchBy', [FournisseurController::class, 'searchBy']); Route::apiResource('fournisseurs', FournisseurController::class); diff --git a/thanasoft-front/src/components/Organism/FactureFournisseur/FactureFournisseurDetailPresentation.vue b/thanasoft-front/src/components/Organism/FactureFournisseur/FactureFournisseurDetailPresentation.vue new file mode 100644 index 0000000..7c8b844 --- /dev/null +++ b/thanasoft-front/src/components/Organism/FactureFournisseur/FactureFournisseurDetailPresentation.vue @@ -0,0 +1,91 @@ + + + diff --git a/thanasoft-front/src/components/Organism/FactureFournisseur/FactureFournisseurListPresentation.vue b/thanasoft-front/src/components/Organism/FactureFournisseur/FactureFournisseurListPresentation.vue new file mode 100644 index 0000000..1dd64f4 --- /dev/null +++ b/thanasoft-front/src/components/Organism/FactureFournisseur/FactureFournisseurListPresentation.vue @@ -0,0 +1,60 @@ + + + diff --git a/thanasoft-front/src/components/Organism/FactureFournisseur/NewFactureFournisseurPresentation.vue b/thanasoft-front/src/components/Organism/FactureFournisseur/NewFactureFournisseurPresentation.vue new file mode 100644 index 0000000..29d0443 --- /dev/null +++ b/thanasoft-front/src/components/Organism/FactureFournisseur/NewFactureFournisseurPresentation.vue @@ -0,0 +1,42 @@ + + + diff --git a/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurHeader.vue b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurHeader.vue new file mode 100644 index 0000000..c4ab51b --- /dev/null +++ b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurHeader.vue @@ -0,0 +1,36 @@ + + + diff --git a/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurLinesTable.vue b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurLinesTable.vue new file mode 100644 index 0000000..43bb8a6 --- /dev/null +++ b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurLinesTable.vue @@ -0,0 +1,61 @@ + + + diff --git a/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurListControls.vue b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurListControls.vue new file mode 100644 index 0000000..b0fc6d5 --- /dev/null +++ b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurListControls.vue @@ -0,0 +1,87 @@ + + + diff --git a/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurSummary.vue b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurSummary.vue new file mode 100644 index 0000000..bce9e65 --- /dev/null +++ b/thanasoft-front/src/components/molecules/FactureFournisseur/FactureFournisseurSummary.vue @@ -0,0 +1,62 @@ + + + diff --git a/thanasoft-front/src/components/molecules/FactureFournisseur/NewFactureFournisseurForm.vue b/thanasoft-front/src/components/molecules/FactureFournisseur/NewFactureFournisseurForm.vue new file mode 100644 index 0000000..45d6ef0 --- /dev/null +++ b/thanasoft-front/src/components/molecules/FactureFournisseur/NewFactureFournisseurForm.vue @@ -0,0 +1,305 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/Tables/Fournisseurs/FactureFournisseurTable.vue b/thanasoft-front/src/components/molecules/Tables/Fournisseurs/FactureFournisseurTable.vue new file mode 100644 index 0000000..6856beb --- /dev/null +++ b/thanasoft-front/src/components/molecules/Tables/Fournisseurs/FactureFournisseurTable.vue @@ -0,0 +1,218 @@ + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue b/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue index 9f5c40f..b62ab8f 100644 --- a/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue +++ b/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue @@ -45,13 +45,13 @@ :is-active="activeTab === 'notes'" @click="$emit('change-tab', 'notes')" /> - + /> --> +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + diff --git a/thanasoft-front/src/components/templates/FactureFournisseur/ListFactureFournisseurTemplate.vue b/thanasoft-front/src/components/templates/FactureFournisseur/ListFactureFournisseurTemplate.vue new file mode 100644 index 0000000..aabe343 --- /dev/null +++ b/thanasoft-front/src/components/templates/FactureFournisseur/ListFactureFournisseurTemplate.vue @@ -0,0 +1,18 @@ + + + diff --git a/thanasoft-front/src/examples/Sidenav/SidenavCollapse.vue b/thanasoft-front/src/examples/Sidenav/SidenavCollapse.vue index 92f9aab..4c08ae0 100644 --- a/thanasoft-front/src/examples/Sidenav/SidenavCollapse.vue +++ b/thanasoft-front/src/examples/Sidenav/SidenavCollapse.vue @@ -7,7 +7,6 @@ class="nav-link" v-bind="$attrs" type="button" - @click="isExpanded = !isExpanded" >