feat(sale): implement modifications filtering and validation for admin sales

This commit is contained in:
2026-09-03 08:39:45 -03:00
parent 334ffe92e7
commit c2233389d0
6 changed files with 174 additions and 21 deletions

View File

@@ -3,6 +3,7 @@
namespace App\Domains\Sale\Controllers\AdminApp;
use App\Domains\Sale\Requests\AdminAppSaleIndexRequest;
use App\Domains\Sale\Requests\AdminAppSaleModificationIndexRequest;
use App\Domains\Sale\Requests\AdminAppSaleModificationPdfRequest;
use App\Domains\Sale\Requests\AdminAppSalePdfRequest;
use App\Domains\Sale\Resources\AdminApp\SaleDetailResource;
@@ -67,11 +68,13 @@ class SaleController extends Controller
return new SaleResource($this->saleService->cancel($tenant, $sale));
}
public function modifications(Request $request): AnonymousResourceCollection
{
public function modifications(
AdminAppSaleModificationIndexRequest $request,
): AnonymousResourceCollection {
return SaleModificationResource::collection(
$this->saleService->modifications(
$request->user()->tenant()->firstOrFail()
$request->user()->tenant()->firstOrFail(),
$request->validated(),
)
);
}
@@ -93,7 +96,7 @@ class SaleController extends Controller
return $this->salePdfService->downloadModifications(
$tenant,
$this->saleService->modificationsForExport($tenant),
$this->saleService->modificationsForExport($tenant, $request->validated()),
$request->validated('timezone'),
);
}
@@ -116,7 +119,7 @@ class SaleController extends Controller
return $this->saleExcelService->downloadModifications(
$tenant,
$this->saleService->modificationsForExport($tenant),
$this->saleService->modificationsForExport($tenant, $request->validated()),
$request->validated('timezone'),
);
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Domains\Sale\Requests;
class AdminAppSaleModificationIndexRequest extends AdminAppSaleIndexRequest
{
/** @return array<string, list<string>> */
public function rules(): array
{
$rules = parent::rules();
unset($rules['sort_by'], $rules['sort_direction']);
return $rules;
}
}

View File

@@ -3,19 +3,14 @@
namespace App\Domains\Sale\Requests;
use App\Domains\Shared\Rules\ValidTimezone;
use Illuminate\Foundation\Http\FormRequest;
class AdminAppSaleModificationPdfRequest extends FormRequest
class AdminAppSaleModificationPdfRequest extends AdminAppSaleModificationIndexRequest
{
public function authorize(): bool
{
return true;
}
/** @return array<string, list<string>> */
public function rules(): array
{
return [
...parent::rules(),
'timezone' => ['required', 'string', new ValidTimezone],
];
}

View File

@@ -92,18 +92,24 @@ class AdminAppSaleService
return $this->salesQuery($tenant, $filters)->get();
}
/** @return LengthAwarePaginator<ValueChange> */
public function modifications(Tenant $tenant): LengthAwarePaginator
/**
* @param array<string, mixed> $filters
* @return LengthAwarePaginator<ValueChange>
*/
public function modifications(Tenant $tenant, array $filters = []): LengthAwarePaginator
{
return $this->modificationsQuery($tenant)
return $this->modificationsQuery($tenant, $filters)
->paginateFromRequest()
->withQueryString();
}
/** @return Collection<int, ValueChange> */
public function modificationsForExport(Tenant $tenant): Collection
/**
* @param array<string, mixed> $filters
* @return Collection<int, ValueChange>
*/
public function modificationsForExport(Tenant $tenant, array $filters = []): Collection
{
return $this->modificationsQuery($tenant)->get();
return $this->modificationsQuery($tenant, $filters)->get();
}
/** @param array<string, mixed> $filters */
@@ -159,12 +165,50 @@ class AdminAppSaleService
->when($sortBy !== 'id', fn (Builder $query): Builder => $query->orderByDesc('id'));
}
/** @return Builder<ValueChange> */
protected function modificationsQuery(Tenant $tenant): Builder
/**
* @param array<string, mixed> $filters
* @return Builder<ValueChange>
*/
protected function modificationsQuery(Tenant $tenant, array $filters): Builder
{
return ValueChange::query()
->where('tenant_code', $tenant->codigo)
->where('trackable_type', (new Purchase)->getMorphClass())
->when($filters['q'] ?? null, function (Builder $query, string $search): void {
$term = trim($search);
$query->where(function (Builder $query) use ($term): void {
$query
->where('trackable_id', 'like', "%{$term}%")
->orWhereHasMorph(
'trackable',
[Purchase::class],
function (Builder $sales) use ($term): void {
$sales
->where('nombre_apellido', 'like', "%{$term}%")
->orWhere('created_at', 'like', "%{$term}%");
},
);
});
})
->when(
$filters['id'] ?? null,
fn (Builder $query, int $id): Builder => $query->where('trackable_id', $id)
)
->when(
$filters['sale_date'] ?? null,
fn (Builder $query, string $date): Builder => $query->whereHasMorph(
'trackable',
[Purchase::class],
fn (Builder $sales): Builder => $sales->whereDate('created_at', $date),
)
)
->when(
$filters['status'] ?? null,
fn (Builder $query, string $status): Builder => $query
->where('attribute', 'status')
->whereIn('new_value', Purchase::realStatusesForAdminStatus($status))
)
->with(['trackable', 'user'])
->orderByDesc('changed_at')
->orderByDesc('id');

View File

@@ -9,7 +9,8 @@ Provee consultas administrativas y exportaciones de ventas confirmadas, además
- `AdminAppSaleService`: pagina ventas, calcula totales y obtiene colecciones para exportación; también consulta modificaciones.
- `AdminAppSalePdfService`: genera descargas PDF de ventas y de cambios.
- `AdminAppSaleExcelService`: genera descargas Excel de ventas y de cambios.
- `AdminAppSaleIndexRequest`: valida filtros del listado y la exportación.
- `AdminAppSaleIndexRequest`: valida filtros del listado y la exportación de ventas.
- `AdminAppSaleModificationIndexRequest`: valida los filtros compartidos por el historial y sus exportaciones.
- `SaleResource` y `SaleModificationResource`: representan ventas e historial para AdminApp.
- `SaleController`: entrada HTTP del panel.
@@ -27,3 +28,5 @@ Consume compras de `Purchase`, datos del tenant y entradas de `Logging`. No es d
## Consideraciones
La consulta paginada y la colección de exportación deben aplicar los mismos filtros para evitar diferencias entre pantalla, PDF y Excel.
El historial comparte con ventas los filtros de búsqueda, ID, fecha de venta y estado. En el historial, el estado se evalúa sobre `ValueChange.new_value`: representa el resultado de esa modificación y no el estado actual de la venta.