feat: agregar gestión de facturas con controlador, solicitudes y migraciones
This commit is contained in:
parent
c7b28d9053
commit
8eedb89172
232
app/Http/Controllers/App/BillController.php
Normal file
232
app/Http/Controllers/App/BillController.php
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
<?php namespace App\Http\Controllers\App;
|
||||||
|
|
||||||
|
use App\Http\Requests\App\BillStoreRequest;
|
||||||
|
use App\Http\Requests\App\BillUpdateRequest;
|
||||||
|
use App\Models\Bill;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class BillController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$query = Bill::query();
|
||||||
|
|
||||||
|
if (request()->filled('q')) {
|
||||||
|
$query->where('name', 'like', '%' . request()->q . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$bills = $query->orderByDesc('created_at')
|
||||||
|
->paginate(config('app.pagination', 15));
|
||||||
|
|
||||||
|
return ApiResponse::OK->response(['bills' => $bills]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(BillStoreRequest $request)
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
if ($request->hasFile('file')) {
|
||||||
|
$data['file_path'] = $request->file('file')->store('bills', 'public');
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($data['file']);
|
||||||
|
|
||||||
|
$bill = Bill::create($data);
|
||||||
|
|
||||||
|
return ApiResponse::CREATED->response(['bill' => $bill]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Bill $bill)
|
||||||
|
{
|
||||||
|
return ApiResponse::OK->response(['bill' => $bill]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(BillUpdateRequest $request, Bill $bill)
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
if ($request->hasFile('file')) {
|
||||||
|
if ($bill->file_path) {
|
||||||
|
Storage::disk('public')->delete($bill->file_path);
|
||||||
|
}
|
||||||
|
$data['file_path'] = $request->file('file')->store('bills', 'public');
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($data['file']);
|
||||||
|
|
||||||
|
$bill->update($data);
|
||||||
|
|
||||||
|
return ApiResponse::OK->response(['bill' => $bill->fresh()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(Bill $bill)
|
||||||
|
{
|
||||||
|
if ($bill->file_path) {
|
||||||
|
Storage::disk('public')->delete($bill->file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bill->delete();
|
||||||
|
|
||||||
|
return ApiResponse::OK->response();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alterna el estado de pago de la factura.
|
||||||
|
*/
|
||||||
|
public function togglePaid(Bill $bill)
|
||||||
|
{
|
||||||
|
$bill->update(['paid' => !$bill->paid]);
|
||||||
|
|
||||||
|
return ApiResponse::OK->response(['bill' => $bill->fresh()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exporta a Excel las facturas pendientes de pago.
|
||||||
|
*/
|
||||||
|
public function export()
|
||||||
|
{
|
||||||
|
$bills = Bill::where('paid', false)
|
||||||
|
->orderBy('deadline')
|
||||||
|
->orderBy('created_at')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
$sheet->setTitle('Facturas por Pagar');
|
||||||
|
|
||||||
|
$sheet->getParent()->getDefaultStyle()->getFont()->setName('Arial')->setSize(10);
|
||||||
|
|
||||||
|
$styleHeader = [
|
||||||
|
'font' => ['bold' => true, 'size' => 10, 'color' => ['rgb' => 'FFFFFF']],
|
||||||
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
|
||||||
|
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
|
||||||
|
];
|
||||||
|
|
||||||
|
$styleData = [
|
||||||
|
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D0D0D0']]],
|
||||||
|
'alignment' => ['vertical' => Alignment::VERTICAL_CENTER],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Título
|
||||||
|
$sheet->mergeCells('A1:E1');
|
||||||
|
$sheet->setCellValue('A1', 'FACTURAS PENDIENTES DE PAGO');
|
||||||
|
$sheet->getStyle('A1')->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'size' => 14],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sheet->mergeCells('A2:E2');
|
||||||
|
$sheet->setCellValue('A2', 'Generado el ' . Carbon::now()->format('d/m/Y H:i'));
|
||||||
|
$sheet->getStyle('A2')->applyFromArray([
|
||||||
|
'font' => ['italic' => true, 'color' => ['rgb' => '666666']],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Encabezados
|
||||||
|
$headers = ['A4' => '#', 'B4' => 'NOMBRE', 'C4' => 'COSTO', 'D4' => 'FECHA LÍMITE', 'E4' => 'DÍAS RESTANTES'];
|
||||||
|
foreach ($headers as $cell => $text) {
|
||||||
|
$sheet->setCellValue($cell, $text);
|
||||||
|
}
|
||||||
|
$sheet->getStyle('A4:E4')->applyFromArray($styleHeader);
|
||||||
|
$sheet->getRowDimension(4)->setRowHeight(22);
|
||||||
|
|
||||||
|
// Datos
|
||||||
|
$row = 5;
|
||||||
|
$total = 0;
|
||||||
|
foreach ($bills as $i => $bill) {
|
||||||
|
$deadline = $bill->deadline ? Carbon::parse($bill->deadline) : null;
|
||||||
|
// Positivo = días que faltan, negativo = días vencida
|
||||||
|
$daysLeft = $deadline ? (int) Carbon::today()->diffInDays($deadline, false) : null;
|
||||||
|
|
||||||
|
if ($daysLeft === null) {
|
||||||
|
$daysLabel = '—';
|
||||||
|
} elseif ($daysLeft === 0) {
|
||||||
|
$daysLabel = 'Hoy';
|
||||||
|
} elseif ($daysLeft > 0) {
|
||||||
|
$daysLabel = "+{$daysLeft}";
|
||||||
|
} else {
|
||||||
|
$daysLabel = (string) $daysLeft; // ya incluye el signo negativo
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet->setCellValue('A' . $row, $i + 1);
|
||||||
|
$sheet->setCellValue('B' . $row, $bill->name);
|
||||||
|
$sheet->setCellValue('C' . $row, (float) $bill->cost);
|
||||||
|
$sheet->setCellValue('D' . $row, $deadline?->format('d/m/Y') ?? '—');
|
||||||
|
$sheet->setCellValue('E' . $row, $daysLabel);
|
||||||
|
|
||||||
|
$sheet->getStyle('C' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
|
||||||
|
$sheet->getStyle('A' . $row . ':E' . $row)->applyFromArray($styleData);
|
||||||
|
$sheet->getStyle('A' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
$sheet->getStyle('C' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
|
||||||
|
$sheet->getStyle('D' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
$sheet->getStyle('E' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
|
||||||
|
// Verde si quedan días, rojo si está vencida
|
||||||
|
if ($daysLeft !== null && $daysLeft > 0) {
|
||||||
|
$sheet->getStyle('E' . $row)->getFont()->getColor()->setRGB('1A7A1A');
|
||||||
|
} elseif ($daysLeft !== null && $daysLeft < 0) {
|
||||||
|
$sheet->getStyle('D' . $row . ':E' . $row)->getFont()->getColor()->setRGB('CC0000');
|
||||||
|
$sheet->getStyle('D' . $row . ':E' . $row)->getFont()->setBold(true);
|
||||||
|
} elseif ($daysLeft === 0) {
|
||||||
|
$sheet->getStyle('E' . $row)->getFont()->getColor()->setRGB('B45309');
|
||||||
|
$sheet->getStyle('E' . $row)->getFont()->setBold(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total += (float) $bill->cost;
|
||||||
|
$row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total
|
||||||
|
$sheet->setCellValue('B' . $row, 'TOTAL PENDIENTE');
|
||||||
|
$sheet->setCellValue('C' . $row, $total);
|
||||||
|
$sheet->getStyle('C' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
|
||||||
|
$sheet->getStyle('B' . $row . ':C' . $row)->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'size' => 11],
|
||||||
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFF2CC']],
|
||||||
|
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Anchos de columna
|
||||||
|
$sheet->getColumnDimension('A')->setWidth(6);
|
||||||
|
$sheet->getColumnDimension('B')->setWidth(40);
|
||||||
|
$sheet->getColumnDimension('C')->setWidth(18);
|
||||||
|
$sheet->getColumnDimension('D')->setWidth(16);
|
||||||
|
$sheet->getColumnDimension('E')->setWidth(16);
|
||||||
|
|
||||||
|
// Generar archivo
|
||||||
|
$fileName = 'Facturas_Pendientes_' . Carbon::now()->format('Ymd_His') . '.xlsx';
|
||||||
|
$filePath = storage_path('app/temp/' . $fileName);
|
||||||
|
if (!file_exists(dirname($filePath))) mkdir(dirname($filePath), 0755, true);
|
||||||
|
|
||||||
|
(new Xlsx($spreadsheet))->save($filePath);
|
||||||
|
|
||||||
|
return response()->download($filePath, $fileName, [
|
||||||
|
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
])->deleteFileAfterSend(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/Http/Requests/App/BillStoreRequest.php
Normal file
45
app/Http/Requests/App/BillStoreRequest.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\App;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class BillStoreRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'cost' => ['required', 'numeric', 'min:0'],
|
||||||
|
'deadline' => ['nullable', 'date'],
|
||||||
|
'paid' => ['boolean'],
|
||||||
|
'file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom validation messages
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => 'El nombre de la factura es obligatorio.',
|
||||||
|
'cost.required' => 'El costo es obligatorio.',
|
||||||
|
'cost.numeric' => 'El costo debe ser un número válido.',
|
||||||
|
'cost.min' => 'El costo debe ser un valor positivo.',
|
||||||
|
'file.mimes' => 'El archivo debe ser PDF o imagen (jpg, jpeg, png).',
|
||||||
|
'file.max' => 'El archivo no puede superar los 10 MB.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/Http/Requests/App/BillUpdateRequest.php
Normal file
45
app/Http/Requests/App/BillUpdateRequest.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\App;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class BillUpdateRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'cost' => ['required', 'numeric', 'min:0'],
|
||||||
|
'deadline' => ['nullable', 'date'],
|
||||||
|
'paid' => ['boolean'],
|
||||||
|
'file' => ['sometimes', 'nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom validation messages
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => 'El nombre de la factura es obligatorio.',
|
||||||
|
'cost.required' => 'El costo es obligatorio.',
|
||||||
|
'cost.numeric' => 'El costo debe ser un número válido.',
|
||||||
|
'cost.min' => 'El costo debe ser un valor positivo.',
|
||||||
|
'file.mimes' => 'El archivo debe ser PDF o imagen (jpg, jpeg, png).',
|
||||||
|
'file.max' => 'El archivo no puede superar los 10 MB.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Models/Bill.php
Normal file
31
app/Models/Bill.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php namespace App\Models;
|
||||||
|
/**
|
||||||
|
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Bill extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'cost',
|
||||||
|
'file_path',
|
||||||
|
'deadline',
|
||||||
|
'paid',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'cost' => 'decimal:2',
|
||||||
|
'paid' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $appends = ['file_url'];
|
||||||
|
|
||||||
|
public function getFileUrlAttribute(): ?string
|
||||||
|
{
|
||||||
|
if (!$this->file_path) return null;
|
||||||
|
return asset('storage/' . $this->file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
database/migrations/2026_03_21_092446_create_bills_table.php
Normal file
32
database/migrations/2026_03_21_092446_create_bills_table.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('bills', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->decimal('cost', 12, 2);
|
||||||
|
$table->string('file_path')->nullable();
|
||||||
|
$table->date('deadline')->nullable();
|
||||||
|
$table->boolean('paid')->default(false);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('bills');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\PermissionType;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$type = PermissionType::firstOrCreate([
|
||||||
|
'name' => 'Facturas / Gastos'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$billIndex = Permission::firstOrCreate(['name' => 'bills.index'], ['guard_name' => 'api', 'description' => 'Mostrar facturas', 'permission_type_id' => $type->id]);
|
||||||
|
$billCreate = Permission::firstOrCreate(['name' => 'bills.create'], ['guard_name' => 'api', 'description' => 'Subir facturas', 'permission_type_id' => $type->id]);
|
||||||
|
$billEdit = Permission::firstOrCreate(['name' => 'bills.edit'], ['guard_name' => 'api', 'description' => 'Editar facturas', 'permission_type_id' => $type->id]);
|
||||||
|
$billDestroy = Permission::firstOrCreate(['name' => 'bills.destroy'], ['guard_name' => 'api', 'description' => 'Eliminar facturas', 'permission_type_id' => $type->id]);
|
||||||
|
|
||||||
|
// Asignar permisos
|
||||||
|
$developer = Role::findByName('developer', 'api');
|
||||||
|
$developer->givePermissionTo($billIndex, $billCreate, $billEdit, $billDestroy);
|
||||||
|
$admin = Role::findByName('admin', 'api');
|
||||||
|
$admin->givePermissionTo($billIndex, $billCreate, $billEdit, $billDestroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$admin = Role::findByName('admin', 'api');
|
||||||
|
$admin->revokePermissionTo(['bills.index', 'bills.create', 'bills.edit', 'bills.destroy']);
|
||||||
|
|
||||||
|
$developer = Role::findByName('developer', 'api');
|
||||||
|
$developer->revokePermissionTo(['bills.index', 'bills.create', 'bills.edit', 'bills.destroy']);
|
||||||
|
|
||||||
|
Permission::whereIn('name', ['bills.index', 'bills.create', 'bills.edit', 'bills.destroy'])->delete();
|
||||||
|
|
||||||
|
PermissionType::where('name', 'Facturas / Gastos')->delete();
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\App\BillController;
|
||||||
use App\Http\Controllers\App\BundleController;
|
use App\Http\Controllers\App\BundleController;
|
||||||
use App\Http\Controllers\App\CashRegisterController;
|
use App\Http\Controllers\App\CashRegisterController;
|
||||||
use App\Http\Controllers\App\CategoryController;
|
use App\Http\Controllers\App\CategoryController;
|
||||||
@ -179,6 +180,17 @@
|
|||||||
Route::get('/{supplier}/compras', [SupplierController::class, 'purchases']);
|
Route::get('/{supplier}/compras', [SupplierController::class, 'purchases']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FACTURAS / GASTOS
|
||||||
|
Route::prefix('bills')->name('admin.bills.')->group(function () {
|
||||||
|
Route::get('/', [BillController::class, 'index'] )->name('index');
|
||||||
|
Route::post('/', [BillController::class, 'store'] )->name('store');
|
||||||
|
Route::get('/pending/excel', [BillController::class, 'export'] )->name('export');
|
||||||
|
Route::get('/{bill}', [BillController::class, 'show'] )->name('show');
|
||||||
|
Route::post('/{bill}', [BillController::class, 'update'] )->name('update');
|
||||||
|
Route::delete('/{bill}', [BillController::class, 'destroy'] )->name('destroy');
|
||||||
|
Route::patch('/{bill}/toggle-paid', [BillController::class, 'togglePaid'])->name('toggle-paid');
|
||||||
|
});
|
||||||
|
|
||||||
// WHATSAPP
|
// WHATSAPP
|
||||||
Route::prefix('whatsapp')->group(function () {
|
Route::prefix('whatsapp')->group(function () {
|
||||||
Route::post('/send-document', [WhatsappController::class, 'sendDocument']);
|
Route::post('/send-document', [WhatsappController::class, 'sendDocument']);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user