Merge pull request 'funcion/pedido-ollas' (#47) from funcion/pedido-ollas into master

Reviewed-on: #47
Reviewed-by: Rodrigo <rodrigopdm@protonmail.com>
This commit is contained in:
Rodrigo 2025-07-15 11:27:29 -03:00
commit 45a7688fbd
60 changed files with 1652 additions and 465 deletions

View file

@ -46,17 +46,24 @@ class Filtro extends Model
//Obtener nombre del método (snake_case a camelCase)
$metodo = str_replace('_', '', lcfirst(ucwords($filtro, '_')));
if(!method_exists($this, $metodo)) { continue; }
if (!method_exists($this, $metodo))
continue;
//Llamar métodos sin argumentos
if ($valor === null|| (is_a($valor,'String') && trim($valor)=='')){ $this->$metodo(); continue; }
if ($valor === null || (is_a($valor,'String') && trim($valor)=='')) {
$this->$metodo();
continue;
}
//Llamar métodos con argumentos
try {
$this->$metodo($valor);
} catch (Throwable $th) {
if (is_a($th,'TypeError') ) { throw new HttpException(400, sprintf($this->MENSAJES_ERROR['ARGUMENTO'],$filtro)); }
throw $th;
} catch (Throwable $error) {
if (is_a($error,'TypeError')) {
$mensaje = sprintf($this->MENSAJES_ERROR['ARGUMENTO'], $filtro);
throw new HttpException(400, $mensaje);
}
throw $error;
}
}
@ -66,12 +73,16 @@ class Filtro extends Model
//Buscar un término en el nombre
public function nombre(String $valor)
{
$this->builder->where('nombre', "LIKE", "%" . $valor . "%")->orderByRaw("IF(nombre = '$valor',2,IF(nombre LIKE '$valor%',1,0)) DESC");
$this->builder
->where('nombre', "LIKE", "%" . $valor . "%")
->orderByRaw("IF(nombre = '$valor',2,IF(nombre LIKE '$valor%',1,0)) DESC");
}
public function alfabetico(String $order = 'asc')
{
if(!in_array($order,['asc','desc'])) { throw new TypeError(); }
if (!in_array($order,['asc','desc']))
throw new TypeError();
$this->builder->orderBy('nombre', $order);
}
}

View file

@ -8,7 +8,17 @@ class FiltroDeSubpedido extends Filtro
{
public function grupoDeCompra(String $valor)
{
if (!is_numeric($valor)) { throw new TypeError();}
if (!is_numeric($valor))
throw new TypeError();
$this->builder->where('grupo_de_compra_id', intval($valor));
}
public function tipoPedido(String $valor)
{
if (!is_numeric($valor))
throw new TypeError();
$this->builder->where('tipo_pedido_id', intval($valor));
}
}

View file

@ -12,6 +12,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use League\Csv\Exception;
use Mpdf\MpdfException;
class GrupoDeCompra extends Model
{
@ -23,6 +24,11 @@ class GrupoDeCompra extends Model
return $this->hasMany(Subpedido::class);
}
public function users(): HasMany
{
return $this->hasMany(User::class);
}
public function toggleDevoluciones(): bool
{
$this->devoluciones_habilitadas = !$this->devoluciones_habilitadas;
@ -32,7 +38,14 @@ class GrupoDeCompra extends Model
public function pedidosAprobados()
{
return $this->subpedidos->where('aprobado', 1);
return $this->pedidosHogares()
->where('aprobado', 1);
}
public function pedidosHogares()
{
return $this->subpedidos
->where('tipo_pedido_id', '=', 1);
}
public function totalARecaudar()
@ -115,11 +128,15 @@ class GrupoDeCompra extends Model
return TransporteHelper::cantidadTransporte($this->totalCentralesQuePaganTransporte());
}
/**
* @throws MpdfException
*/
public function exportarPedidosAPdf()
{
$subpedidos = $this->pedidosAprobados();
$fecha = now()->format('Y-m-d');
PdfHelper::exportarPedidos($this->nombre . '-' . $fecha . '.pdf', $subpedidos);
$filepath = $this->nombre . '-' . $fecha . '.pdf';
PdfHelper::exportarPedidos($filepath, $subpedidos);
}
function pedidoParaPdf(): array
@ -149,126 +166,15 @@ class GrupoDeCompra extends Model
return $view->render();
}
/**
* @throws MpdfException
*/
public static function exportarPedidosBarrialesAPdf()
{
$barrios = GrupoDeCompra::barriosMenosPrueba()->get();
$fecha = now()->format('Y-m-d');
PdfHelper::exportarPedidos('pedidos_por_barrio-' . $fecha . '.pdf', $barrios);
}
static function filaVacia(string $product, int $columns): array
{
$fila = [$product];
for ($i = 1; $i <= $columns; $i++) {
$fila[$i] = "0";
}
return $fila;
}
//Asume que los productos están gruadados en orden de fila
/**
* @throws Exception
*/
public static function obtenerTemplateDeFilasVacias(int $columns): array
{
$productosFilaID = Producto::productosFilaID();
$productosIDNombre = Producto::productosIDNombre();
$num_fila = 1;
$template = [];
foreach ($productosFilaID as $fila => $id) {
for ($i = $num_fila; $i < $fila; $i++) {
$template[$i] = GrupoDeCompra::filaVacia("", $columns);
}
$template[$fila] = GrupoDeCompra::filaVacia($productosIDNombre[$id], $columns);
$num_fila = $fila + 1;
}
$template[TransporteHelper::filaTransporte()] = GrupoDeCompra::filaVacia("Bonos de transporte", $columns);
return $template;
}
/**
* @throws Exception
*/
public function exportarPedidoEnCSV()
{
$records = $this->generarColumnaCantidades();
$fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-' . $fecha . '.csv', $records);
}
/**
* @throws Exception
*/
public function generarColumnaCantidades(): array
{
$productos_en_pedido = $this->productosPedidos();
//si no hay pedidos aprobados, salir
if ($productos_en_pedido->count() == 0) {
Log::debug("El grupo de compra " . $this->nombre . " no tiene pedidos aprobados.");
return [];
}
$records = $this->obtenerTemplateDeFilasVacias(1);
$productos_id_fila = Producto::productosIDFila();
foreach ($productos_en_pedido as $id => $producto_pedido) {
$fila = $productos_id_fila[$id];
$records[$fila][1] = $producto_pedido->cantidad_pedida;
}
$records[TransporteHelper::filaTransporte()][1] = $this->cantidadTransporte();
return $records;
}
/**
* @throws Exception
*/
public function exportarPedidoConNucleosEnCSV()
{
$productos_en_pedido = $this->productosPedidos();
// si no hay pedidos aprobados, salir
if ($productos_en_pedido->count() == 0) {
Log::debug("El grupo de compra " . $this->nombre . " no tiene pedidos aprobados.");
return;
}
$pedidos = $this->pedidosAprobados();
// Generar tabla vacía con una columna por núcleo
$records = $this->obtenerTemplateDeFilasVacias($pedidos->count());
$productos_id_fila = Producto::productosIDFila();
foreach ($productos_en_pedido as $id => $producto_pedido) {
$fila = $productos_id_fila[$id];
$i = 1;
// Poner cantidad de cada producto para cada núcleo
foreach ($pedidos as $pedido) {
list($records, $i, $_) = $this->agregarCantidad($pedido, $id, $records, $fila, $i);
}
}
// Insertar lista de núcleos en la primera fila
$nucleos = [""];
$i = 1;
foreach ($pedidos as $pedido) {
$nucleos[$i] = $pedido->nombre;
$i++;
}
array_splice($records, 0, 0, array($nucleos));
$fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-completo-' . $fecha . '.csv', $records);
}
public function agregarCantidad($pedido, $id, array $records, $fila, int $i): array
{
$producto = $pedido->productos()->find($id);
$cantidad = $producto == NULL ? 0 : $producto->pivot->cantidad;
$records[$fila][$i] = $cantidad;
$i++;
return array($records, $i, $cantidad);
$filepath = 'pedidos_por_barrio-' . $fecha . '.pdf';
PdfHelper::exportarPedidos($filepath, $barrios);
}
public static function barriosMenosPrueba(): Builder
@ -278,18 +184,6 @@ class GrupoDeCompra extends Model
->orderBy('nombre');
}
public static function transportePorBarrio(): array
{
$result = [];
$barrios = GrupoDeCompra::barriosMenosPrueba()->get();
foreach ($barrios as $barrio) {
$result[] = $barrio->cantidadTransporte();
}
return $result;
}
public function productosPedidos($excluirBonos = false, $orderBy = 'producto_nombre'): Collection
{
$query = DB::table('pedidos_aprobados')

View file

@ -2,6 +2,7 @@
namespace App\Helpers;
use App\Http\Controllers\ComisionesController;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Iterator;
@ -13,15 +14,13 @@ use League\Csv\Writer;
class CsvHelper
{
/**
* @throws Exception
*/
public static function getRecords($filePath, $message): Iterator {
$csv = Reader::createFromPath($filePath);
try {
$csv->setDelimiter("|");
$csv->setEnclosure("'");
$csv->setHeaderOffset(0);
$csv = self::getReader($filePath);
return $csv->getRecords();
} catch (InvalidArgument|Exception $e) {
Log::error($e->getMessage());
@ -29,20 +28,74 @@ class CsvHelper
}
}
public static function generarCsv($filePath, $contenido, $headers = null): void
/**
* @throws Exception
*/
public static function cambiarParametro(string $id, string $valor): void
{
if (!File::exists(storage_path('csv/exports'))) {
File::makeDirectory(storage_path('csv/exports'), 0755, true);
}
try {
$writer = Writer::createFromPath(storage_path($filePath), 'w');
if ($headers) {
$writer->insertOne($headers);
$updated = false;
$filePath = resource_path(ComisionesController::PARAMETROS_PATH);
$csv = self::getReader($filePath);
$headers = $csv->getHeader();
$records = array_map(fn($r) => (array) $r, iterator_to_array($csv->getRecords()));
foreach ($records as &$record) {
if ($record['id'] === $id) {
$record['valor'] = $valor;
$updated = true;
break;
}
}
$writer->insertAll($contenido);
} catch (CannotInsertRecord $e) {
Log::error($e->getMessage(), $e->getTrace());
unset($record);
if (!$updated)
throw new Exception("Parametro '{$id}' no encontrado.");
self::generarCsv($filePath, $records, $headers, "|", "'", false);
} catch (CannotInsertRecord | InvalidArgument $e) {
Log::error("Error al actualizar csv: " . $e->getMessage());
throw new Exception("Error al actualizar csv", $e);
}
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
*/
public static function generarCsv($filePath, $contenido, $headers = null, $delimiter = null, $enclosure = null, $export = true): void
{
$path = $filePath;
if ($export) {
if (!File::exists(storage_path('csv/exports')))
File::makeDirectory(storage_path('csv/exports'), 0755, true);
$path = storage_path($filePath);
}
$writer = Writer::createFromPath($path, 'w');
if ($delimiter)
$writer->setDelimiter($delimiter);
if ($enclosure)
$writer->setEnclosure($enclosure);
if ($headers)
$writer->insertOne($headers);
$writer->insertAll($contenido);
}
/**
* @param string $filePath
* @return Reader
* @throws InvalidArgument
* @throws Exception
*/
private static function getReader(string $filePath): Reader
{
$csv = Reader::createFromPath($filePath);
$csv->setDelimiter("|");
$csv->setEnclosure("'");
$csv->setHeaderOffset(0);
return $csv;
}
}

View file

@ -0,0 +1,244 @@
<?php
namespace App\Helpers;
use App\GrupoDeCompra;
use App\TipoPedido;
use Closure;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\InvalidArgument;
class PedidosExportHelper
{
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
static public function pedidosBarriales()
{
$filePath = "csv/exports/pedidos-por-barrio-" . now()->format('Y-m-d') . ".csv";
$barrios = GrupoDeCompra::barriosMenosPrueba()->get();
self::exportarCSV(
$filePath,
$barrios,
self::generarContenidoCSV(
$barrios,
fn ($grupoId) =>
"subpedidos.grupo_de_compra_id = $grupoId
AND subpedidos.aprobado = 1
AND subpedidos.tipo_pedido_id = 1"
)
);
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
static public function pedidosDeOllas()
{
$filePath = "csv/exports/pedidos-de-ollas-" . now()->format('Y-m-d') . ".csv";
$barrios = GrupoDeCompra::barriosMenosPrueba()->get();
$contenido = self::generarContenidoCSV($barrios,
fn($grupoId) => "subpedidos.grupo_de_compra_id = $grupoId
AND subpedidos.tipo_pedido_id = 2");
$ollas = self::cantidadDeOllasParaCSV($barrios, $contenido);
self::exportarCSV(
$filePath,
$barrios,
$contenido->concat([$ollas])
);
}
/**
* @throws Exception
*/
public static function cantidadDeOllasParaCSV(Collection $barrios, Collection $contenido)
{
$tipo_olla = TipoPedido::where('nombre', 'olla')->first()->id;
$parametros = collect(CsvHelper::getRecords(resource_path("csv/parametros.csv"), "No se pudo leer el archivo."));
$fila = [
"producto" => "Cantidad de ollas",
"precio" => $parametros->where('id','monto-olla')->pluck('valor')->first(),
"paga_transporte" => false,
"fila" => $contenido->last()->fila + 1,
];
foreach ($barrios as $barrio) {
$pedido = $barrio->subpedidos()->where('tipo_pedido_id', $tipo_olla)->first();
$fila[$barrio->nombre] = $pedido->cantidad_ollas;
}
return (object) $fila;
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
static public function pedidoTotalDeBarrio(GrupoDeCompra $grupo)
{
$filePath = "csv/exports/" . $grupo->nombre . "-" . now()->format('Y-m-d') . ".csv";
$falsoBarrio = new GrupoDeCompra(['nombre' => 'Total']);
$falsoBarrio->id = $grupo->id;
$header = collect([$falsoBarrio]);
self::exportarCSV(
$filePath,
$header,
self::generarContenidoCSV(
$header,
fn($grupoId) => "subpedidos.grupo_de_compra_id = $grupoId
AND subpedidos.aprobado = 1"
),
);
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
static public function pedidosDeBarrio(GrupoDeCompra $grupo)
{
$filePath = "csv/exports/" . $grupo->nombre . "-completo-" . now()->format('Y-m-d') . ".csv";
$subpedidos = $grupo->subpedidos()->where('aprobado', true)->get();
self::exportarCSV(
$filePath,
$subpedidos,
self::generarContenidoCSV(
$subpedidos,
fn ($subpedidoId) => "subpedidos.id = $subpedidoId",
)
);
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
private static function exportarCSV(
string $filename,
Collection $headers,
Collection $contenido
): void {
$nombresColumnas = $headers->pluck('nombre')->toArray();
$columnas = array_merge(['Producto'], $nombresColumnas);
$filaTransporte = TransporteHelper::filaTransporte();
$planilla = [];
$ultimaFila = 1;
foreach ($contenido as $fila) {
$filaActual = $fila->fila;
while ($filaActual - $ultimaFila > 1) {
$ultimaFila++;
$planilla[$ultimaFila] = [$ultimaFila === $filaTransporte ? 'Bono de transporte' : '---'];
}
$planilla[$filaActual] = [$fila->producto];
foreach ($nombresColumnas as $nombre)
$planilla[$filaActual][] = $fila->$nombre ?? 0;
$ultimaFila = $filaActual;
}
$planilla[$filaTransporte] = array_merge(['Bono de transporte'], self::cantidadesTransporte($nombresColumnas, $contenido));
ksort($planilla);
CsvHelper::generarCsv($filename, $planilla, $columnas);
}
/**
* @param Collection $headers
* @param Closure $filtroCallback
* <br>
* Ejemplo de uso:
* ``
* PedidosExportHelper::generarContenidoCSV(GrupoDeCompra::barriosMenosPrueba(),
* fn($gdc_id) => "subpedidos.grupo_de_compra_id = $gdc_id");
* ``
* @return Collection
* Los elementos son de la forma
* {
* fila: int (fila del producto),
* producto: string (nombre del producto),
* precio: float (precio del producto),
* paga_transporte: bool (1 o 0, calculado a partir de bono y categoria),
* barrio_1: string (cantidad pedida por barrio_1),
* ...
* barrio_n: string (cantidad pedida por barrio_n)
* }
*/
public static function generarContenidoCSV(
Collection $headers,
Closure $filtroCallback
): Collection {
$expresionesColumnas = $headers->map(function ($header) use ($filtroCallback) {
$id = $header['id'];
$nombre = $header['nombre'];
$filtro = $filtroCallback($id);
return DB::raw("
SUM(CASE WHEN $filtro THEN producto_subpedido.cantidad ELSE 0 END) as `$nombre`
");
})->toArray();
$query = DB::table('productos')
->where('productos.nombre', 'not like', '%barrial%')
->leftJoin('producto_subpedido', 'productos.id', '=', 'producto_subpedido.producto_id')
->leftJoin('subpedidos', 'subpedidos.id', '=', 'producto_subpedido.subpedido_id');
$columnasProducto = [
'productos.fila as fila',
'productos.nombre as producto',
'productos.precio as precio',
self::pagaTransporte(),
];
return $query->select(array_merge(
$columnasProducto,
$expresionesColumnas
))->groupBy('productos.fila', 'productos.id', 'productos.nombre')
->orderBy('productos.fila')
->get();
}
/**
* @return Expression
*/
public static function pagaTransporte(): Expression
{
return DB::raw('CASE WHEN productos.bono OR productos.categoria LIKE "%SUBSIDIADO%" THEN 0 ELSE 1 END as paga_transporte');
}
/**
* @param array $nombresColumnas
* @param Collection $contenido
* @return array
*/
public static function cantidadesTransporte(array $nombresColumnas, Collection $contenido): array
{
$transporte = [];
foreach ($nombresColumnas as $nombre) {
$suma = 0;
foreach ($contenido as $fila) {
if ($fila->paga_transporte) {
$cantidad = $fila->$nombre ?? 0;
$precio = $fila->precio ?? 0;
$suma += $cantidad * $precio;
}
}
$transporte[] = TransporteHelper::cantidadTransporte($suma);
}
return $transporte;
}
}

View file

@ -4,21 +4,29 @@ namespace App\Helpers;
use App\CanastaLog;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
use League\Csv\Exception;
class TransporteHelper
{
const COSTO_TRANSPORTE = 15;
const MONTO_TRANSPORTE = 500;
private const COSTO_TRANSPORTE = "bono-transporte";
private const MONTO_TRANSPORTE = "monto-transporte";
private static ?array $parametros = null;
/**
* @throws Exception
*/
public static function cantidadTransporte($monto)
{
return ceil($monto / self::MONTO_TRANSPORTE);
return ceil($monto / self::getParametro(self::MONTO_TRANSPORTE));
}
/**
* @throws Exception
*/
public static function totalTransporte($monto)
{
return self::cantidadTransporte($monto) * self::COSTO_TRANSPORTE;
return self::cantidadTransporte($monto) * self::getParametro(self::COSTO_TRANSPORTE);
}
/**
@ -41,4 +49,28 @@ class TransporteHelper
Log::error($error);
throw new Exception($error);
}
/**
* @throws Exception
*/
public static function getParametro(string $id): int
{
if (self::$parametros === null) {
$records = CsvHelper::getRecords(resource_path('csv/parametros.csv'), "No se pudo leer el archivo.");
self::$parametros = [];
foreach ($records as $row) {
self::$parametros[$row['id']] = $row;
}
}
if (!isset(self::$parametros[$id])) {
throw new InvalidArgumentException("Parámetro '$id' no encontrado.");
}
return (int) self::$parametros[$id]['valor'];
}
public static function resetParametros(): void {
self::$parametros = null;
}
}

View file

@ -3,7 +3,9 @@
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Helpers\PedidosExportHelper;
use League\Csv\Exception;
use Mpdf\MpdfException;
class AdminController extends Controller
{
@ -17,13 +19,18 @@ class AdminController extends Controller
}
public function exportarPedidosAPdf(GrupoDeCompra $gdc) {
$gdc->exportarPedidosAPdf();
try {
$gdc->exportarPedidosAPdf();
return response();
} catch (MpdfException $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
public function exportarPedidoACSV(GrupoDeCompra $gdc)
{
try {
$gdc->exportarPedidoEnCSV();
PedidosExportHelper::pedidoTotalDeBarrio($gdc);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()]);
}
@ -40,7 +47,7 @@ class AdminController extends Controller
public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc)
{
try {
$gdc->exportarPedidoConNucleosEnCSV();
PedidosExportHelper::pedidosDeBarrio($gdc);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()]);
}

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Producto;
use App\TipoPedido;
use Illuminate\Http\Request;
use App\Filtros\FiltroDeSubpedido;
use App\Subpedido;
@ -12,7 +13,6 @@ use App\Http\Resources\SubpedidoResource;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpKernel\Exception\HttpException;
class SubpedidoController extends Controller
{
public function index(FiltroDeSubpedido $filtros, Request $request)
@ -25,17 +25,25 @@ class SubpedidoController extends Controller
return SubpedidoResource::collection(Subpedido::filtrar($filtros)->get());
}
public function store(Request $request)
public function store(Request $request)
{
$validado = $this->validateSubpedido();
if (Subpedido::where("nombre",$validado["nombre"])->where("grupo_de_compra_id",$validado["grupo_de_compra_id"])->get()->count()) {
throw new HttpException(400, "Ya existe un subpedido con este nombre");
}
$s = new Subpedido();
$s->nombre = $validado["nombre"];
$s->grupo_de_compra_id = $validado["grupo_de_compra_id"];
$s->save();
return $this->show($s);
if (Subpedido::where([
"nombre" => $validado["nombre"],
"tipo_pedido_id" => $validado["tipo_id"],
"grupo_de_compra_id" => $validado["grupo_de_compra_id"]])
->get()
->count())
throw new HttpException(400, "Ya existe un pedido con este nombre");
$pedido = new Subpedido();
$pedido->nombre = $validado["nombre"];
$pedido->grupo_de_compra_id = $validado["grupo_de_compra_id"];
$pedido->tipo_pedido_id = $validado["tipo_id"];
$pedido->save();
return $this->show($pedido);
}
protected function validateSubpedido(): array
@ -45,7 +53,11 @@ class SubpedidoController extends Controller
'grupo_de_compra_id' => [
'required',
Rule::in(GrupoDeCompra::all()->pluck('id')),
]
],
'tipo_id' => [
'required',
Rule::in(TipoPedido::all()->pluck('id')),
],
]);
}

View file

@ -5,15 +5,19 @@ namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Helpers\CanastaHelper;
use App\Helpers\CsvHelper;
use App\Helpers\PedidosExportHelper;
use App\Helpers\TransporteHelper;
use App\Http\Resources\GrupoDeCompraResource;
use App\Producto;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use League\Csv\Exception;
use Mpdf\MpdfException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ComisionesController
{
const PARAMETROS_PATH = 'csv/parametros.csv';
const CANASTAS_PATH = 'csv/canastas/';
const BARRIO = "Barrio";
const SALDO = "Saldo";
@ -26,7 +30,7 @@ class ComisionesController
public function descargarPedidos()
{
try {
Producto::planillaTotales();
PedidosExportHelper::pedidosBarriales();
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
@ -40,6 +44,23 @@ class ComisionesController
return response()->download($files[0]);
}
public function descargarPedidosDeOllas()
{
try {
PedidosExportHelper::pedidosDeOllas();
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
$pattern = storage_path('csv/exports/pedidos-de-ollas-*.csv');
$files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
}
public function descargarNotas(): BinaryFileResponse
{
Producto::planillaNotas();
@ -54,7 +75,12 @@ class ComisionesController
}
public function pdf() {
GrupoDeCompra::exportarPedidosBarrialesAPdf();
try {
GrupoDeCompra::exportarPedidosBarrialesAPdf();
return response();
} catch (MpdfException $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
public function cargarCanasta(Request $request): JsonResponse
@ -104,4 +130,43 @@ class ComisionesController
return response()->json(GrupoDeCompraResource::collection(GrupoDeCompra::all()));
}
public function obtenerParametros(): JsonResponse
{
try {
$records = self::parametrosRecords();
$result = [];
foreach ($records as $record)
$result[] = $record;
return response()->json($result);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
public function modificarParametros(string $parametro_id, Request $request) {
try {
if (collect(self::parametrosRecords())
->contains('id', $parametro_id)) {
$valid = $request->validate([
'valor' => ['required', 'numeric', 'gte:0'],
]);
CsvHelper::cambiarParametro($parametro_id, $valid['valor']);
TransporteHelper::resetParametros();
return response()->noContent();
}
return response()->json(['message' => 'Parametro no encontrado.'], 404);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
/**
* @throws Exception
*/
private static function parametrosRecords(): array
{
$records = CsvHelper::getRecords(resource_path(self::PARAMETROS_PATH), "No se pudo leer el archivo.");
return iterator_to_array($records);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Http\Resources\PedidoOllasResource;
use App\TipoPedido;
use Illuminate\Http\Request;
class OllasController extends Controller
{
public function show()
{
return view('auth/login');
}
public function pedido(GrupoDeCompra $gdc)
{
$tipoOlla = TipoPedido::firstOrCreate(['nombre' => 'olla']);
$pedido = $gdc->subpedidos()->firstOrCreate([
'nombre' => 'Pedido de Ollas de ' . $gdc->nombre,
'tipo_pedido_id' => $tipoOlla->id,
'cantidad_ollas' => 0,
]);
return response()->json(new PedidoOllasResource($pedido));
}
public function actualizarCantidadOllas(GrupoDeCompra $gdc, Request $request)
{
$valid = $request->validate([
'cantidad' => 'required|numeric|min:0',
]);
$pedido = $gdc->subpedidos()->where([
'nombre' => 'Pedido de Ollas de ' . $gdc->nombre
])->first();
$pedido->cantidad_ollas = $valid['cantidad'];
$pedido->save();
return response()->noContent();
}
}

View file

@ -15,6 +15,7 @@ class RouteController extends Controller
$barrio = UserRole::where('nombre', 'barrio')->first();
$admin = UserRole::where('nombre', 'admin_barrio')->first();
$comision = UserRole::where('nombre', 'comision')->first();
$ollas = UserRole::where('nombre', 'ollas')->first();
switch ($request->user()->role_id) {
case $barrio->id:
@ -23,6 +24,8 @@ class RouteController extends Controller
return redirect('/admin');
case $comision->id:
return redirect('/comisiones');
case $ollas->id:
return redirect('/ollas');
default:
abort(400, 'Rol de usuario invalido');
}

View file

@ -11,6 +11,11 @@ use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
public function user(Request $request)
{
return ['user' => $request->user()->name];
}
public function rol(Request $request) {
return ["rol" => UserRole::find($request->user()->role_id)->nombre];
}
@ -21,6 +26,7 @@ class UserController extends Controller
$result = [ 'grupo_de_compra' => null, ];
$grupo_de_compra = GrupoDeCompra::find($user->grupo_de_compra_id);
switch (UserRole::findOrFail($user->role_id)->nombre) {
case 'ollas':
case 'barrio':
$result['grupo_de_compra'] = new GrupoDeCompraPedidoResource($grupo_de_compra);
break;

View file

@ -3,16 +3,17 @@
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @param Request $request
* @return string|null
*/
protected function redirectTo($request)
protected function redirectTo($request): string
{
if (!$request->expectsJson()) {
$path = $request->path();
@ -20,7 +21,10 @@ class Authenticate extends Middleware
return route('admin.login');
if (preg_match('~^comisiones.*~i', $path))
return route('comisiones.login');
if (preg_match('~^ollas.*~i', $path))
return route('ollas.login');
return route('login');
}
return '';
}
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class GrupoDeCompraPedidoResource extends JsonResource
@ -9,7 +10,7 @@ class GrupoDeCompraPedidoResource extends JsonResource
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @param Request $request
* @return array
*/
public function toArray($request): array {

View file

@ -19,7 +19,7 @@ class GrupoDeCompraResource extends JsonResource
'id' => $this->id,
'nombre' => $this->nombre,
'devoluciones_habilitadas' => $this->devoluciones_habilitadas,
'pedidos' => SubpedidoResource::collection($this->subpedidos),
'pedidos' => SubpedidoResource::collection($this->pedidosHogares()),
'total_a_recaudar' => number_format($this->totalARecaudar(),2),
'saldo' => number_format($this->saldo, 2, ".", ""),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Resources;
use App\TipoPedido;
use Illuminate\Http\Resources\Json\JsonResource;
class PedidoOllasResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request): array
{
$productos = $this->productos;
foreach ($productos as $producto) {
$producto['pivot']['total'] = number_format($producto->pivot->cantidad * $producto->precio, 2);
}
return [
'id' => $this->id,
'nombre' => $this->nombre,
'productos' => $productos,
'total' => number_format($this->totalCentralesSinTransporte(),2),
'cantidad_de_ollas' => $this->cantidad_ollas,
];
}
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Resources;
use App\TipoPedido;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@ -29,7 +30,11 @@ class SubpedidoResource extends JsonResource
'cantidad_transporte' => number_format($this->cantidadTransporte()),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),
'devoluciones_total' => number_format($this->devoluciones_total,2),
'devoluciones_notas' => $this->devoluciones_notas
'devoluciones_notas' => $this->devoluciones_notas,
'tipo' => [
'id' => $this->tipo_pedido_id,
'nombre' => TipoPedido::find($this->tipo_pedido_id)->nombre
],
];
}
}

View file

@ -11,7 +11,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\InvalidArgument;
class Producto extends Model
{
@ -19,15 +21,10 @@ class Producto extends Model
public function subpedidos(): BelongsToMany
{
return $this->belongsToMany(Subpedido::class, 'productos_subpedidos')->withPivot(["cantidad", "notas"]);
return $this->belongsToMany(Subpedido::class, 'productos_subpedidos')
->withPivot(["cantidad", "notas"]);
}
public static function noBarriales()
{
return self::where('nombre', 'not like', '%barrial%');
}
// Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
public function scopeFiltrar($query, FiltroDeProducto $filtros): Builder
{
return $filtros->aplicar($query);
@ -35,7 +32,9 @@ class Producto extends Model
public static function getPaginar(Request $request): int
{
return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::all()->count();
return $request->has('paginar') && intval($request->input('paginar')) ?
intval($request->input('paginar')) :
self::all()->count();
}
public static function productosFilaID()
@ -53,77 +52,20 @@ class Producto extends Model
return self::noBarriales()->pluck('nombre', 'id')->all();
}
static public function cantidadesPorBarrio(): Collection
public static function noBarriales()
{
$barrios = GrupoDeCompra::barriosMenosPrueba()
->pluck('id', 'nombre');
$columnasBarrios = $barrios->map(function ($id, $nombre) {
return DB::raw("SUM(CASE WHEN subpedidos.grupo_de_compra_id = $id AND subpedidos.aprobado = 1 THEN producto_subpedido.cantidad ELSE 0 END) as `$nombre`");
})->toArray();
return DB::table('productos')
->where('productos.nombre', 'not like', '%barrial%')
->leftJoin('producto_subpedido', 'productos.id', '=', 'producto_subpedido.producto_id')
->leftJoin('subpedidos', 'subpedidos.id', '=', 'producto_subpedido.subpedido_id')
->select(array_merge(
['productos.fila as fila'],
['productos.nombre as producto'],
$columnasBarrios
))
->groupBy('productos.fila', 'productos.id', 'productos.nombre')
->orderBy('productos.fila')
->get();
}
/**
* @throws Exception
*/
static public function planillaTotales()
{
$headers = ['Producto'];
$barrios = GrupoDeCompra::barriosMenosPrueba()
->pluck('nombre')->toArray();
$headers = array_merge($headers, $barrios);
$cantidadesPorBarrio = self::cantidadesPorBarrio();
$transportePorBarrio = GrupoDeCompra::transportePorBarrio();
$planilla = [];
$ultimaFila = 1;
$filaTransporte = TransporteHelper::filaTransporte();
foreach ($cantidadesPorBarrio as $productoCantidades) {
$fila = $productoCantidades->fila;
while ($fila - $ultimaFila > 1) {
$ultimaFila++;
if ($ultimaFila == $filaTransporte) {
$planilla[$ultimaFila] = ['Bono de transporte'];
} else {
$planilla[$ultimaFila] = ['---'];
}
}
$planilla[$fila] = [$productoCantidades->producto];
foreach ($barrios as $barrio) {
$planilla[$fila][] = $productoCantidades->$barrio ?? 0;
}
$ultimaFila = $fila;
}
foreach ($transportePorBarrio as $key => $cantidad) {
$planilla[$filaTransporte][] = $cantidad;
}
$fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/pedidos-por-barrio- ' . $fecha . '.csv', $planilla, $headers);
return self::where('nombre', 'not like', '%barrial%');
}
public static function notasPorBarrio(): Collection
{
return DB::table('productos')
->where('productos.nombre', 'not like', '%barrial%')
->join('producto_subpedido', 'productos.id', '=', 'producto_subpedido.producto_id')
->join('subpedidos', 'producto_subpedido.subpedido_id', '=', 'subpedidos.id')
->join('grupos_de_compra', 'subpedidos.grupo_de_compra_id', '=', 'grupos_de_compra.id')
->where('productos.requiere_notas', 1)
->where('subpedidos.tipo_pedido_id', '=', 1)
->select(
'productos.nombre as producto',
'grupos_de_compra.nombre as barrio',
@ -133,6 +75,10 @@ class Producto extends Model
->groupBy('producto');
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
*/
static public function planillaNotas() {
$headers = ['Producto'];
$barrios = GrupoDeCompra::barriosMenosPrueba()
@ -145,13 +91,16 @@ class Producto extends Model
foreach ($notasPorBarrio as $producto => $notasGrupo) {
$fila = [$producto];
foreach ($barrios as $barrio) {
$notas = $notasGrupo->where('barrio', $barrio)->pluck('notas')->implode('; ');
$notas = $notasGrupo->where('barrio', $barrio)
->pluck('notas')
->implode('; ');
$fila[] = $notas ?: '';
}
$planilla[] = $fila;
}
$fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/notas-por-barrio-' . $fecha . '.csv', $planilla, $headers);
$filePath = 'csv/exports/notas-por-barrio-' . $fecha . '.csv';
CsvHelper::generarCsv($filePath, $planilla, $headers);
}
}

View file

@ -12,7 +12,15 @@ use App\Filtros\FiltroDeSubpedido;
class Subpedido extends Model
{
protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre', 'devoluciones_total', 'devoluciones_notas'];
protected $fillable = [
'grupo_de_compra_id',
'aprobado',
'nombre',
'devoluciones_total',
'devoluciones_notas',
'tipo_pedido_id',
'cantidad_ollas'
];
public function productos(): BelongsToMany
{
@ -24,6 +32,11 @@ class Subpedido extends Model
return $this->belongsTo(GrupoDeCompra::class);
}
public function tipoPedido(): BelongsTo
{
return $this->belongsTo(TipoPedido::class);
}
// Permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
public function scopeFiltrar($query, FiltroDeSubpedido $filtros): Builder
{
@ -52,7 +65,11 @@ class Subpedido extends Model
public function totalCentral()
{
return $this->totalCentralesQueNoPaganTransporte() + $this->totalCentralesQuePaganTransporte() + $this->totalTransporte();
return $this->totalCentralesSinTransporte() + $this->totalTransporte();
}
public function totalCentralesSinTransporte() {
return $this->totalCentralesQueNoPaganTransporte() + $this->totalCentralesQuePaganTransporte();
}
public function totalCentralesQueNoPaganTransporte()

10
app/TipoPedido.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class TipoPedido extends Model
{
protected $fillable = ["nombre"];
}

View file

@ -67,7 +67,7 @@ return [
|
*/
'timezone' => 'UTC',
'timezone' => 'America/Montevideo',
/*
|--------------------------------------------------------------------------

View file

@ -0,0 +1,43 @@
<?php
use App\TipoPedido;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTipoPedidosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tipo_pedidos', function (Blueprint $table) {
$table->id();
$table->string("nombre");
$table->timestamps();
});
$hogar = TipoPedido::firstOrCreate(['nombre' => 'hogar']);
TipoPedido::firstOrCreate(['nombre' => 'olla']);
Schema::table('subpedidos', function (Blueprint $table) use ($hogar) {
$table->foreignId('tipo_pedido_id')->default($hogar->id);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tipo_pedidos');
Schema::table('subpedidos', function (Blueprint $table) {
$table->dropColumn('tipo_pedido_id');
});
}
}

View file

@ -0,0 +1,40 @@
<?php
use App\GrupoDeCompra;
use App\User;
use App\UserRole;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Hash;
class UserRoleOllas extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$ollasRol = UserRole::firstOrCreate(['nombre' => 'ollas']);
$barrios = GrupoDeCompra::all();
foreach ($barrios as $barrio) {
$barrio->users()->firstOrCreate([
'name' => $barrio->nombre . '_ollas',
'password' => Hash::make('123'),
'role_id' => $ollasRol->id
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$ollasRol = UserRole::where('nombre', 'ollas')->firstOrFail();
User::where('role_id', $ollasRol->id)->delete();
$ollasRol->delete();
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CantidadOllasPedidos extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('subpedidos', function (Blueprint $table) {
$table->integer('cantidad_ollas')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('subpedidos', function (Blueprint $table) {
$table->dropColumn('cantidad_ollas');
});
}
}

View file

@ -24,5 +24,6 @@ class DatabaseSeeder extends Seeder
$this->call(CanastaSeeder::class);
$this->call(GrupoDeCompraSeeder::class);
$this->call(UserSeeder::class);
$this->call(UsuarioOllasSeeder::class);
}
}

View file

@ -0,0 +1,34 @@
<?php
use App\GrupoDeCompra;
use App\User;
use App\UserRole;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UsuarioOllasSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$barrios = GrupoDeCompra::all();
$usersToInsert = [];
$ollas_id = UserRole::where('nombre', 'ollas')->first()->id;
foreach ($barrios as $barrio) {
$usersToInsert[] = DatabaseSeeder::addTimestamps([
'name' => $barrio->nombre . '_ollas',
'password' => Hash::make('123'),
'role_id' => $ollas_id,
'grupo_de_compra_id' => $barrio->id,
]);
}
foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
User::insert($chunk);
}
}

25
package-lock.json generated
View file

@ -1648,19 +1648,6 @@
"hasInstallScript": true,
"optional": true,
"dependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
@ -2056,7 +2043,6 @@
"dependencies": {
"@babel/parser": "^7.23.5",
"postcss": "^8.4.14",
"prettier": "^1.18.2 || ^2.0.0",
"source-map": "^0.6.1"
},
"optionalDependencies": {
@ -2102,7 +2088,6 @@
"merge-source-map": "^1.1.0",
"postcss": "^7.0.36",
"postcss-selector-parser": "^6.0.2",
"prettier": "^1.18.2 || ^2.0.0",
"source-map": "~0.6.1",
"vue-template-es2015-compiler": "^1.9.0"
},
@ -3555,7 +3540,6 @@
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
"fsevents": "^1.2.7",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
@ -7975,9 +7959,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@ -11334,7 +11315,6 @@
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
"dev": true,
"dependencies": {
"@parcel/watcher": "^2.4.1",
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
@ -13424,10 +13404,8 @@
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
"dev": true,
"dependencies": {
"chokidar": "^3.4.1",
"graceful-fs": "^4.1.2",
"neo-async": "^2.5.0",
"watchpack-chokidar2": "^2.0.1"
"neo-async": "^2.5.0"
},
"optionalDependencies": {
"chokidar": "^3.4.1",
@ -13493,7 +13471,6 @@
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",

View file

View file

@ -0,0 +1,4 @@
id|nombre|valor
bono-transporte|'Bono de transporte'|15
monto-transporte|'Monto para bono de transporte'|500
monto-olla|'Monto por olla'|1200
1 id nombre valor
2 bono-transporte 'Bono de transporte' 15
3 monto-transporte 'Monto para bono de transporte' 500
4 monto-olla 'Monto por olla' 1200

View file

@ -5,9 +5,10 @@ import ComisionesBody from "./comisiones/Body.vue";
import AdminBody from "./admin/Body.vue";
import PedidosBody from "./pedidos/Body.vue";
import InfoTags from "./comunes/InfoTags.vue";
import OllasBody from "./ollas/OllasBody.vue";
export default {
name: 'Main',
components: { InfoTags, ComisionesBody, AdminBody, PedidosBody, NavBar },
components: { OllasBody, InfoTags, ComisionesBody, AdminBody, PedidosBody, NavBar },
computed: {
...mapState("login", ["rol"]),
},
@ -26,6 +27,7 @@ export default {
<pedidos-body v-if="rol === 'barrio'"/>
<admin-body v-else-if="rol === 'admin_barrio'"/>
<comisiones-body v-else-if="rol === 'comision'"/>
<ollas-body v-else-if="rol === 'ollas'"/>
<info-tags/>
</div>
</template>

View file

@ -0,0 +1,25 @@
<script>
import { mapGetters, mapMutations, mapState } from "vuex";
export default {
name: "AdminNavBarBrand",
computed: {
...mapState('ui', ["burger_activa"]),
...mapGetters('admin', ["barrio"])
},
methods: {
...mapMutations('ui', ["toggleBurger"]),
},
}
</script>
<template>
<div class="navbar-item hide-below-1024">
<p>
{{`Barrio: ${barrio}`}}
</p>
</div>
</template>
<style scoped>
</style>

View file

@ -18,6 +18,11 @@
:class="seccionActiva === 'saldos-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<saldos-seccion/>
</div>
<div class="block pb-6"
id="parametros-comisiones-seccion"
:class="seccionActiva === 'parametros-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<parametros-seccion/>
</div>
</div>
</template>
@ -27,11 +32,12 @@ import DropdownDescargar from "./DropdownDescargar.vue";
import InputFileButton from "../comunes/InputFileButton.vue";
import CanastaSeccion from "./canasta/CanastaSeccion.vue";
import SaldosSeccion from "./saldos/SaldosSeccion.vue";
import { mapActions } from "vuex";
import ParametrosSeccion from "./parametros/ParametrosSeccion.vue";
export default {
name: "ComisionesBody",
components: {
ParametrosSeccion,
SaldosSeccion,
CanastaSeccion,
TabsSecciones,
@ -44,6 +50,7 @@ export default {
{ id: "pedidos-comisiones", nombre: "Pedidos" },
{ id: "canasta-comisiones", nombre: "Canasta" },
{ id: "saldos-comisiones", nombre: "Saldos" },
{ id: "parametros-comisiones", nombre: "Parámetros" },
],
tabActiva: "pedidos-comisiones",
seccionActiva: "pedidos-comisiones-seccion",

View file

@ -0,0 +1,31 @@
<script>
import { mapMutations, mapState } from "vuex";
export default {
name: "ComisionesNavBarBrand",
computed: {
...mapState('ui', ["burger_activa"])
},
methods: {
...mapMutations('ui', ["toggleBurger"]),
},
data() {
return {
nombre: '',
}
},
async mounted() {
const response = await axios.get('/user');
this.nombre = response.data.user;
}
}
</script>
<template>
<div class="navbar-item hide-below-1024">
<p>{{ nombre }}</p>
</div>
</template>
<style scoped>
</style>

View file

@ -15,14 +15,17 @@
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="/comisiones/pedidos/descargar" class="dropdown-item">
Pedidos por barrio
Pedidos por barrio en csv
</a>
<a href="/comisiones/pedidos/notas" class="dropdown-item">
Notas por barrio
Notas por barrio en csv
</a>
<a href="/comisiones/pedidos/pdf" class="dropdown-item">
Pedidos por barrio en pdf
</a>
<a href="/comisiones/pedidos/ollas" class="dropdown-item">
Pedidos de ollas en csv
</a>
</div>
</div>
</div>

View file

@ -0,0 +1,71 @@
<script>
import {mapActions, mapGetters} from "vuex";
export default {
props: {
parametro: {
type: Object,
required: true,
}
},
data() {
return {
control: this.parametro.valor,
};
},
computed: {
hayCambios() {
return this.control !== this.parametro.valor;
}
},
methods: {
...mapActions("comisiones", ["cambiarParametro"]),
modificar() {
this.cambiarParametro({
parametro_id: this.parametro.id,
valor: this.control,
});
}
}
}
</script>
<template>
<tr>
<td>{{ parametro.nombre }}</td>
<td>
<div class="field">
<input :type="parametro.tipo"
:id="'input-' + parametro.id"
v-model="control"
class="has-text-right">
</div>
</td>
<td class="has-text-centered">
<div class="control">
<button class="button is-small is-success"
@click="modificar"
:disabled="!hayCambios">
<span class="icon">
<i class="fas fa-check"></i>
</span>
</button>
</div>
</td>
<td class="has-text-centered">
<div class="control">
<button class="button is-small is-danger"
@click="control = parametro.valor"
:disabled="!hayCambios">
<span class="icon">
<i class="fa fa-undo" aria-hidden="true"></i>
</span>
</button>
</div>
</td>
</tr>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,41 @@
<script>
import FilaCaracteristica from "../../admin/FilaCaracteristica.vue";
import FilaParametro from "./FilaParametro.vue";
import { mapActions, mapState } from "vuex";
export default {
name:"ParametrosSeccion",
components: { FilaParametro, FilaCaracteristica },
methods: {
...mapActions("comisiones", ["getParametros"]),
},
computed: {
...mapState("comisiones", ["parametros"]),
},
async mounted() {
await this.getParametros();
},
}
</script>
<template>
<table class="table is-striped is-bordered">
<thead>
<tr>
<th> Parámetro </th>
<th> Valor $ </th>
<th> Cambiar </th>
<th> Deshacer </th>
</tr>
</thead>
<tbody>
<fila-parametro v-for="(p,i) in parametros"
:key="i"
:parametro="p"/>
</tbody>
</table>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,30 @@
<script>
import { mapMutations, mapState } from "vuex";
export default {
name: "Burger",
computed: {
...mapState('ui', ["burger_activa"])
},
methods: {
...mapMutations('ui', ["toggleBurger"]),
}
}
</script>
<template>
<a role="button"
class="navbar-burger"
:class="{'is-active': burger_activa}"
aria-label="menu"
aria-expanded="false"
data-target="nav-bar"
@click="toggleBurger">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</template>
<style scoped>
</style>

View file

@ -1,43 +1,33 @@
<template>
<nav id="nav-bar" class="navbar is-danger is-fixed-top" role="navigation" aria-label="main navigation">
<nav id="nav-bar"
class="navbar is-danger is-fixed-top"
role="navigation"
aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://mps.org.uy">
<img src="/assets/logoMPS.png" height="28">
</a>
<div class="navbar-item hide-below-1024" v-if="pedidoDefinido">
<p>{{ `Barrio: ${grupo_de_compra.nombre} - Núcleo: ${nombre}` }}</p>
</div>
<chismosa-dropdown
v-if="pedidoDefinido"
class="hide-above-1023"
ariaControls="mobile"
/>
<a role="button" class="navbar-burger" :class="{'is-active':burgerActiva}" aria-label="menu"
aria-expanded="false" data-target="nav-bar" @click="toggleBurger">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<img src="/assets/logoMPS.png" height="28" alt="Logo del MPS">
</a>
<pedidos-nav-bar-brand v-if="rol === 'barrio'"/>
<ollas-nav-bar-brand v-else-if="rol === 'ollas'"/>
<admin-nav-bar-brand v-else-if="rol === 'admin_barrio'"/>
<comisiones-nav-bar-brand v-else/>
<burger/>
</div>
<div class="navbar-menu" :class="{'is-active':burgerActiva}">
<div class="navbar-end">
<div v-if="pedidoDefinido" class="navbar-item field has-addons mt-1 mr-3 mb-1">
<a class="button is-small has-text-dark-grey" @click.capture="buscar">
<span class="icon">
<i class="fas fa-search"></i>
</span>
</a>
<input class="input is-small" type="text" placeholder="Harina" v-model="searchString"
@keyup.enter="buscar">
<div class="navbar-menu" :class="{'is-active': burger_activa}">
<div class="navbar-start is-flex is-justify-content-center is-flex-grow-1">
<div v-if="mostrarAviso" class="is-absolute-center is-flex is-align-items-center navbar-item">
<span class="tag is-warning is-size-6">
Monto superado
</span>
</div>
<chismosa-dropdown
v-if="pedidoDefinido"
class="hide-below-1024"
ariaControls="wide">
</chismosa-dropdown>
</div>
<div class="navbar-end">
<buscador v-if="pedidoDefinido"/>
<chismosa-dropdown v-if="pedidoDefinido"
class="hide-below-1024"
ariaControls="wide"/>
<div class="block navbar-item">
<a onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
class="text-a">
<a @click="logOut" class="text-a">
Cerrar sesión
</a>
</div>
@ -48,63 +38,38 @@
<script>
import ChismosaDropdown from '../pedidos/ChismosaDropdown.vue';
import { mapActions, mapGetters, mapMutations, mapState } from "vuex";
import { mapGetters, mapState } from "vuex";
import PedidosNavBarBrand from "../pedidos/PedidosNavBarBrand.vue";
import ComisionesNavBarBrand from "../comisiones/ComisionesNavBarBrand.vue";
import AdminNavBarBrand from "../admin/AdminNavBarBrand.vue";
import OllasNavBarBrand from "../ollas/OllasNavBarBrand.vue";
import Buscador from "../pedidos/Buscador.vue";
import Burger from "./Burger.vue";
export default {
components: { ChismosaDropdown },
data() {
return {
burgerActiva: false,
searchString: "",
nombreCanasta: "",
fechaCanasta: "",
}
},
components: { Burger, Buscador, OllasNavBarBrand, AdminNavBarBrand, ComisionesNavBarBrand, PedidosNavBarBrand, ChismosaDropdown },
computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('pedido', ["nombre"]),
...mapState('pedido', ["grupo_de_compra"]),
},
methods: {
...mapActions('productos', ["filtrarProductos"]),
...mapMutations('ui', ["addMiga", "popUltimaBusqueda"]),
toggleBurger() {
this.burgerActiva = !this.burgerActiva
},
buscar() {
if (this.burgerActiva)
this.toggleBurger();
this.filtrarProductos({ filtro: "nombre", valor: this.searchString });
this.popUltimaBusqueda();
this.addMiga({ nombre: this.searchString });
...mapGetters('ollas', ["montoSuperado"]),
...mapState('login', ["rol"]),
...mapState('ui', ["burger_activa"]),
mostrarAviso() {
return this.pedidoDefinido && this.rol === 'ollas' && this.montoSuperado;
}
},
methods: {
logOut() {
event.preventDefault();
document.getElementById('logout-form').submit();
},
}
};
</script>
<style>
p.navbar-item:empty {
display: none;
}
#nav-bar {
z-index: 10;
}
.text-a {
color: inherit;
}
@media (max-width: 1023px) {
.hide-below-1024 {
display: none !important;
}
}
@media (min-width: 1024px) {
.hide-above-1023 {
display: none !important;
}
.is-absolute-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
</style>

View file

@ -1,12 +1,17 @@
<script>
import { mapGetters } from "vuex";
export default {
name: "UserInput",
computed: {
...mapGetters("login", ["estilos"]),
}
}
</script>
<template>
<div class="field">
<label class="label">Usuario</label>
<label class="label" :class="estilos.texto">Usuario</label>
<div class="field has-addons">
<div class="control">
<input required class="input" type="text" name="name" placeholder="Usuario">

View file

@ -0,0 +1,138 @@
<template>
<div class="ollas-box mt-0 py-0">
<div class="header-row">
<span v-if="visible" class="label">Cantidad de ollas:</span>
<span v-else class="label">Cantidad de ollas: {{ control }}</span>
</div>
<transition name="slide-fade">
<div v-if="visible" class="field my-1 has-addons is-justify-content-center">
<div class="control">
<button class="button is-small" :disabled="control < 1" @click="decrementar">
<i class="fas fa-minus" />
</button>
</div>
<div class="control">
<input
type="number"
min="0"
v-model.number="control"
@input="actualizarDebounced"
class="input is-small input-centered"
/>
</div>
<div class="control">
<button class="button is-small" @click="incrementar">
<i class="fas fa-plus" />
</button>
</div>
</div>
</transition>
<span class="icon-toggle mt-2 mb-0 pb-0" @click="visible = !visible">
<i :class="iconoToggle"/>
</span>
</div>
</template>
<script>
import { mapActions, mapMutations, mapState } from "vuex";
export default {
name: "CantidadOllas",
data() {
return {
control: 0,
visible: true,
debounceTimer: null,
};
},
computed: {
...mapState("pedido", ["cantidad_de_ollas"]),
iconoToggle() {
return this.visible? 'fas fa-angle-up' : 'fas fa-angle-down'
}
},
watch: {
cantidad_de_ollas(newVal) {
this.control = newVal;
},
},
methods: {
...mapActions("ollas", ["actualizarCantidadOllas"]),
...mapMutations("pedido", ["setCantidadOllas"]),
incrementar() {
this.control++;
this.actualizarDebounced();
},
decrementar() {
if (this.control > 0) {
this.control--;
this.actualizarDebounced();
}
},
actualizarDebounced() {
clearTimeout(this.debounceTimer);
const params = { cantidad: this.control };
this.debounceTimer = setTimeout(() => {
this.setCantidadOllas(params);
this.actualizarCantidadOllas(params);
}, 500);
},
},
mounted() {
this.control = this.cantidad_de_ollas;
},
};
</script>
<style scoped>
.ollas-box {
position: relative;
left: 0;
right: 0;
top: -1.5rem;
z-index: 2;
padding: 1rem 1.5rem;
background: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
border-radius: 10px;
width: fit-content;
margin: 1rem auto;
text-align: center;
}
.header-row {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
.label {
font-weight: 600;
}
.input-centered {
width: 60px;
text-align: center;
}
.icon-toggle {
cursor: pointer;
color: #888;
transition: color 0.2s;
}
.icon-toggle:hover {
color: #000;
}
/* Animation */
.slide-fade-enter-active,
.slide-fade-leave-active {}
.slide-fade-enter-from,
.slide-fade-leave-to {
opacity: 0;
transform: translateY(-10px);
max-height: 0;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,27 @@
<script>
import PedidosMain from "../pedidos/PedidosMain.vue";
import { mapActions, mapMutations } from "vuex";
export default {
name: "OllasBody",
components: { PedidosMain },
methods: {
...mapActions('pedido', ["getPedidoDeOllas", "getGrupoDeCompra"]),
...mapMutations('ui', ["migasOllas"])
},
async mounted() {
await this.getGrupoDeCompra();
await this.getPedidoDeOllas();
this.migasOllas();
},
}
</script>
<template>
<div id="ollas-body" class="mt-0 mb-6 pb-0 pb-6">
<pedidos-main/>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,35 @@
<script>
import { mapActions, mapGetters, mapMutations, mapState } from "vuex";
import ChismosaDropdown from "../pedidos/ChismosaDropdown.vue";
export default {
name: "OllasNavBarBrand",
components: { ChismosaDropdown },
computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapGetters('ollas', ["montoTotal"]),
...mapState('ui', ["burger_activa"]),
...mapState('pedido', ["nombre"]),
},
methods: {
...mapActions('ollas', ["getMontoPorOlla"]),
...mapMutations('ui', ["toggleBurger"]),
},
async mounted() {
await this.getMontoPorOlla();
}
}
</script>
<template>
<div class="navbar-item hide-below-1024">
<p v-if="pedidoDefinido">{{ `${nombre}` }}</p>
<chismosa-dropdown
v-if="pedidoDefinido"
class="hide-above-1023"
ariaControls="mobile"/>
</div>
</template>
<style scoped>
</style>

View file

@ -1,41 +1,25 @@
<template>
<div id="pedidos-body" class="pb-6 mb-6">
<pedido-select v-if="!pedidoDefinido"/>
<div v-else>
<nav-migas/>
<div class="columns">
<div class="column" :class="{ 'is-two-thirds-desktop is-hidden-touch': show_chismosa }">
<cartel-pedido-aprobado/>
<canasta/>
</div>
<div class="column is-full-touch" v-if="show_chismosa">
<chismosa/>
</div>
</div>
</div>
<pedidos-main v-else>
<template v-slot:cartel>
<cartel-pedido-aprobado/>
</template>
</pedidos-main>
</div>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
import CartelPedidoAprobado from "./CartelPedidoAprobado.vue";
import { mapGetters } from "vuex";
import PedidoSelect from "./PedidoSelect.vue";
import Canasta from "./Canasta.vue";
import NavMigas from "./NavMigas.vue";
import Chismosa from "./Chismosa.vue";
import PedidosMain from "./PedidosMain.vue";
import CartelPedidoAprobado from "./CartelPedidoAprobado.vue";
export default {
name: "PedidosBody",
components: { Chismosa, NavMigas, CartelPedidoAprobado, PedidoSelect, Canasta },
components: { CartelPedidoAprobado, PedidosMain, PedidoSelect },
computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('ui', ["show_chismosa"]),
},
methods: {
...mapActions('productos', ["init"]),
},
async mounted() {
await this.init();
}
}
</script>

View file

@ -0,0 +1,42 @@
<script>
import { mapActions, mapMutations } from "vuex";
export default {
name: "Buscador",
methods: {
...mapActions('productos', ["filtrarProductos"]),
...mapMutations('ui', ["addMiga", "popUltimaBusqueda", "toggleBurger"]),
buscar() {
if (this.burger_activa)
this.toggleBurger();
this.filtrarProductos({ filtro: "nombre", valor: this.searchString });
this.popUltimaBusqueda();
this.addMiga({ nombre: this.searchString });
}
},
data() {
return {
searchString: "",
}
},
}
</script>
<template>
<div class="navbar-item field has-addons mt-1 mr-3 mb-1">
<a class="button is-small has-text-dark-grey" @click.capture="buscar">
<span class="icon">
<i class="fas fa-search"></i>
</span>
</a>
<input class="input is-small"
type="text"
placeholder="Harina"
v-model="searchString"
@keyup.enter="buscar">
</div>
</template>
<style scoped>
</style>

View file

@ -2,10 +2,10 @@
<div class="dropdown is-right navbar-item" :class="{'is-active': show_chismosa}">
<div class="dropdown-trigger">
<a class="text-a" aria-haspopup="true" :aria-controls="ariaControls" @click.capture="toggleChismosa">
<span class="icon is-small mr-1">
<img src="/assets/chismosa.png">
</span>
<span v-text="'$' + total"></span>
<span class="icon is-small mr-1">
<img src="/assets/chismosa.png">
</span>
<span v-text="textoChismosa"/>
</a>
</div>
</div>
@ -13,7 +13,7 @@
<script>
import Chismosa from './Chismosa.vue'
import { mapMutations, mapState } from "vuex";
import { mapGetters, mapMutations, mapState } from "vuex";
export default {
components: {
Chismosa
@ -27,6 +27,13 @@ export default {
computed: {
...mapState('pedido',["total"]),
...mapState('ui',["show_chismosa"]),
...mapState('login', ["rol"]),
...mapGetters('ollas', ["montoTotal"]),
textoChismosa() {
if (this.rol === 'ollas')
return `$${this.total} / $${this.montoTotal}`;
return `$${this.total}`;
},
},
methods: {
...mapMutations('ui',["toggleChismosa"]),

View file

@ -1,6 +1,6 @@
<template>
<nav class="breadcrumb is-centered has-background-danger-light is-fixed-top"
aria-label="breadcrumbs" v-show="visible">
<nav class="breadcrumb is-centered has-background-danger-light is-fixed-top mb-0"
aria-label="breadcrumbs">
<ul class="mt-4">
<li v-for="(miga, i) in migas" :key="i" :class="{'is-active': i === migaActiva}">
<a @click="clickMiga({ miga: miga })"
@ -17,15 +17,11 @@ import { mapActions, mapMutations, mapState } from "vuex";
export default {
methods: {
...mapActions('productos', ["getProductos"]),
...mapActions('ui', ["clickMiga"]),
...mapMutations('ui', ["addMiga"]),
},
computed: {
...mapState('ui', ["migas"]),
visible() {
return this.migas.length > 0;
},
migaActiva() {
return this.migas.length - 1;
},

View file

@ -88,7 +88,8 @@ export default {
const response = await axios.get('/api/subpedidos/',{
params: {
nombre: nombre,
grupo_de_compra: this.grupo_de_compra.id
grupo_de_compra: this.grupo_de_compra.id,
tipo_pedido: 1,
}
});
this.pedidos = response.data;
@ -105,7 +106,8 @@ export default {
else
await this.crearPedido({
nombre: this.searchString,
grupo_de_compra_id: this.grupo_de_compra.id
grupo_de_compra_id: this.grupo_de_compra.id,
tipo_id: 1,
});
},
}

View file

@ -0,0 +1,43 @@
<script>
import { defineComponent } from "vue";
import Canasta from "./Canasta.vue";
import Chismosa from "./Chismosa.vue";
import NavMigas from "./NavMigas.vue";
import { mapActions, mapState } from "vuex";
import CantidadOllas from "../ollas/CantidadOllas.vue";
export default defineComponent({
name: "PedidosMain",
components: { CantidadOllas, NavMigas, Chismosa, Canasta },
computed: {
...mapState('ui', ["show_chismosa"]),
...mapState('login', ["rol"])
},
methods: {
...mapActions('productos', ["init"]),
},
async mounted() {
await this.init();
}
})
</script>
<template>
<div>
<nav-migas/>
<cantidad-ollas v-if="rol === 'ollas'"/>
<div class="columns">
<div class="column" :class="{ 'is-two-thirds-desktop is-hidden-touch': show_chismosa }">
<slot name="cartel"></slot>
<canasta/>
</div>
<div class="column is-full-touch" v-if="show_chismosa">
<chismosa/>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,32 @@
<script>
import { mapGetters, mapMutations, mapState } from "vuex";
import ChismosaDropdown from "./ChismosaDropdown.vue";
export default {
name: "PedidosNavBarBrand",
components: { ChismosaDropdown },
computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('pedido', ["nombre", "grupo_de_compra"]),
...mapState('ui', ["burger_activa"])
},
methods: {
...mapMutations('ui', ["toggleBurger"]),
}
}
</script>
<template>
<div class="navbar-item hide-below-1024">
<p v-if="pedidoDefinido">
{{ `Barrio: ${grupo_de_compra.nombre} - Núcleo: ${nombre}` }}
</p>
<chismosa-dropdown
v-if="pedidoDefinido"
class="hide-above-1023"
ariaControls="mobile"/>
</div>
</template>
<style scoped>
</style>

View file

@ -117,6 +117,7 @@ export default {
this.notas_warning_visible = true;
return;
}
this.notas_warning_visible = false;
await this.modificarChismosa({
producto_id: this.producto_id,
cantidad: this.cantidadControl,

View file

@ -6,6 +6,7 @@ import login from "./modules/login";
import pedido from "./modules/pedido";
import productos from "./modules/productos";
import ui from "./modules/ui";
import ollas from "./modules/ollas";
Vue.use(Vuex);
@ -17,5 +18,6 @@ export default new Vuex.Store({
pedido,
productos,
ui,
ollas,
},
});

View file

@ -61,6 +61,9 @@ const getters = {
grupoDeCompraDefinido() {
return state.lastFetch !== null;
},
barrio() {
return state.nombre?.replace('_admin','') ?? '';
},
hayPedidos() {
return state.pedidos?.length > 0;
},

View file

@ -3,6 +3,7 @@ import axios from "axios";
const state = {
lastFetch: undefined,
grupos_de_compra: [],
parametros: [],
};
const mutations = {
@ -10,6 +11,15 @@ const mutations = {
state.grupos_de_compra = data;
state.lastFetch = new Date();
},
setParametros(state, parametros) {
state.parametros = parametros;
state.parametros.forEach(
p => p.valor = Number.parseInt(p.valor)
);
},
actualizarParametro(state, { parametro_id, valor }) {
state.parametros.find(p => p.id === parametro_id).valor = valor;
},
setSaldo(state, { gdc_id, saldo }) {
const barrio = state.grupos_de_compra.find(gdc => gdc.id === gdc_id);
const i = state.grupos_de_compra.indexOf(barrio);
@ -22,6 +32,22 @@ const actions = {
const response = await axios.get('/api/grupos-de-compra/saldos');
commit('setGruposDeCompra', response.data);
},
async getParametros({ commit }) {
const response = await axios.get('/api/parametros');
commit('setParametros', response.data);
},
async cambiarParametro({ commit, dispatch }, { parametro_id, valor }) {
try {
await axios.put(
`/comisiones/parametros/${parametro_id}`,
{ valor: valor }
);
commit('actualizarParametro', { parametro_id, valor });
dispatch("ui/toast", { mensaje: 'Parámetro modificado con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: error }, { root: true });
}
},
async setSaldo({ commit, dispatch }, { gdc_id, saldo }) {
try {
await axios.post(

View file

@ -50,6 +50,14 @@ const getters = {
textos() {
let rol = getters.urlRol();
switch (rol) {
case 'pedido':
return {
titulo: "Pedidos MPS",
subtitlo: "aplicación de pedidos",
password: "Contraseña",
ayuda: "Si no la sabés, consultá a la comisión informática",
label: "Seleccioná tu región"
};
case 'admin':
return {
titulo: "Administración de Pedidos MPS",
@ -66,11 +74,11 @@ const getters = {
ayuda: "Si no la sabés, consultá a la comisión informática",
label: "Usuario"
};
case 'pedido':
case 'ollas':
return {
titulo: "Pedidos MPS",
subtitlo: "aplicación de pedidos",
password: "Contraseña",
titulo: "Ollas MPS",
subtitlo: "aplicación de pedidos de ollas",
password: "Contraseña de ollas del barrio",
ayuda: "Si no la sabés, consultá a la comisión informática",
label: "Seleccioná tu región"
};
@ -81,23 +89,29 @@ const getters = {
estilos() {
let rol = getters.urlRol();
switch (rol) {
case 'admin':
return {
fondo: "has-background-danger",
texto: "has-text-white",
botones: "is-warning",
};
case 'comisiones':
return {
fondo: "has-background-warning",
texto: "",
botones: "is-dark"
};
case 'pedido':
return {
fondo: "",
texto: "",
botones: "is-danger"
botones: "danger-dark-button"
};
case 'admin':
return {
fondo: "has-background-danger-dark",
texto: "has-text-white",
botones: "is-dark",
};
case 'comisiones':
return {
fondo: "has-background-grey",
texto: "has-text-white",
botones: "danger-dark-button",
};
case 'ollas':
return {
fondo: "has-background-dark",
texto: "has-text-white",
botones: "danger-dark-button",
};
default:
throw new Error("Url inválida");
@ -106,27 +120,48 @@ const getters = {
opcionesLogin() {
let rol = getters.urlRol();
switch (rol) {
case 'pedido':
return [
{ nombre: "Administración", href: "/admin" },
{ nombre: "Ollas", href: "/ollas" },
{ nombre: "Comisiones", href: "/comisiones" },
];
case 'admin':
return [
{ nombre: "Pedidos", href: "/" },
{ nombre: "Comisiones", href: "/comisiones" }
{ nombre: "Ollas", href: "/ollas" },
{ nombre: "Comisiones", href: "/comisiones" },
];
case 'comisiones':
return [
{ nombre: "Pedidos", href: "/" },
{ nombre: "Administración", href: "/admin" }
];
case 'pedido':
return [
{ nombre: "Ollas", href: "/ollas" },
{ nombre: "Administración", href: "/admin" },
{ nombre: "Comisiones", href: "/comisiones" }
];
case 'ollas':
return [
{ nombre: "Pedidos", href: "/" },
{ nombre: "Administración", href: "/admin" },
{ nombre: "Comisiones", href: "/comisiones" },
];
default:
throw new Error("Url inválida");
}
},
nombre() {
return `${state.grupo_de_compra_elegido}${ getters.urlRol() === 'admin' ? '_admin' : ''}`;
let rol = getters.urlRol();
switch (rol) {
case 'pedido':
return state.grupo_de_compra_elegido;
case 'admin':
return `${state.grupo_de_compra_elegido}_admin`;
case 'ollas':
return `${state.grupo_de_compra_elegido}_ollas`;
case 'comisiones':
return "";
default:
throw new Error("Url inválida");
}
}
};

45
resources/js/store/modules/ollas.js vendored Normal file
View file

@ -0,0 +1,45 @@
import axios from "axios";
const state = {
monto_por_olla: undefined,
};
const mutations = {
setMontoPorOlla(state, parametros) {
state.monto_por_olla = Number.parseInt(parametros.find(p => p.id === 'monto-olla').valor);
},
};
const actions = {
async getMontoPorOlla({ commit }) {
const response = await axios.get('/api/parametros');
commit('setMontoPorOlla', response.data);
},
async actualizarCantidadOllas({ rootState, dispatch }, { cantidad: cantidad}) {
try {
const barrio = rootState.pedido.grupo_de_compra.id;
const params = { cantidad : cantidad };
await axios.put(`/ollas/${barrio}/cantidad`, params);
dispatch("ui/toast", { mensaje: 'Cantidad modificada con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: { message: "Cantidad inválida" } }, { root: true });
}
}
};
const getters = {
montoTotal(state, _, rootState) {
return state.monto_por_olla * rootState.pedido.cantidad_de_ollas;
},
montoSuperado: (_, getters, rootState) => {
return rootState.pedido.total > getters.montoTotal;
}
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

View file

@ -3,16 +3,17 @@ import axios from "axios";
const state = {
lastFetch: null,
grupo_de_compra: null,
pedido_id: null,
nombre: null,
productos: null,
aprobado: null,
total: null,
total_transporte: null,
cantidad_transporte: null,
total_sin_devoluciones: null,
devoluciones_total: null,
devoluciones_notas: null,
pedido_id: 0,
nombre: "",
productos: [],
aprobado: false,
total: 0,
total_transporte: 0,
cantidad_transporte: 0,
total_sin_devoluciones: 0,
devoluciones_total: 0,
devoluciones_notas: "",
cantidad_de_ollas: 0,
};
const mutations = {
@ -25,13 +26,27 @@ const mutations = {
state.nombre = pedido.nombre;
state.productos = pedido.productos;
state.aprobado = pedido.aprobado;
state.total = pedido.total;
state.total_transporte = pedido.total_transporte;
state.cantidad_transporte = pedido.cantidad_transporte;
state.total_sin_devoluciones = pedido.total_sin_devoluciones;
state.devoluciones_total = pedido.devoluciones_total;
state.total = Number.parseFloat(pedido.total.replace(',',''));
state.total_transporte = Number.parseInt(pedido.total_transporte?.replace(',',''));
state.cantidad_transporte = Number.parseInt(pedido.cantidad_transporte?.replace(',',''));
state.total_sin_devoluciones = Number.parseFloat(pedido.total_sin_devoluciones?.replace(',',''));
state.devoluciones_total = Number.parseFloat(pedido.devoluciones_total?.replace(',',''));
state.devoluciones_notas = pedido.devoluciones_notas;
},
setPedidoDeOllas(state, pedido) {
state.lastFetch = new Date();
state.pedido_id = pedido.id;
state.nombre = pedido.nombre;
state.productos = pedido.productos;
state.total = Number.parseFloat(pedido.total.replace(',',''));
state.cantidad_de_ollas = Number.parseInt(pedido.cantidad_de_ollas);
delete state.aprobado;
delete state.total_transporte;
delete state.cantidad_transporte;
delete state.total_sin_devoluciones;
delete state.devoluciones_total;
delete state.devoluciones_notas;
},
reset(state) {
state.lastFetch = null;
state.pedido_id = null;
@ -44,6 +59,10 @@ const mutations = {
state.total_sin_devoluciones = null;
state.devoluciones_total = null;
state.devoluciones_notas = null;
},
setCantidadOllas(state, { cantidad }) {
if (cantidad >= 0)
state.cantidad_de_ollas = cantidad;
}
};
@ -53,28 +72,25 @@ const actions = {
commit('setGrupoDeCompra', response.data.grupo_de_compra);
},
async guardarSesion(_, { pedido_id }) {
await axios.post("/pedido/sesion", { id: pedido_id });
const body = { id: pedido_id };
await axios.post("/pedido/sesion", body);
},
async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id }) {
const response = await axios.post("/api/subpedidos", {
nombre: nombre,
grupo_de_compra_id: grupo_de_compra_id
});
async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id, tipo_id }) {
const body = { nombre, grupo_de_compra_id, tipo_id };
const response = await axios.post("/api/subpedidos", body);
dispatch("guardarSesion", { pedido_id: response.data.data.id});
commit('setPedido', response.data.data);
},
async elegirPedido({ commit, dispatch }, { pedido_id }) {
const response = await axios.get(`/api/subpedidos/${pedido_id}`);
dispatch("guardarSesion", { pedido_id: pedido_id})
const body = { pedido_id: pedido_id};
dispatch("guardarSesion", body)
commit('setPedido', response.data.data);
},
async modificarChismosa({ commit, dispatch }, { producto_id, cantidad, notas }) {
const body = { cantidad: cantidad, producto_id: producto_id, notas: notas };
try {
const response = await axios.post("/api/subpedidos/" + state.pedido_id + "/sync", {
cantidad: cantidad,
producto_id: producto_id,
notas: notas,
});
const response = await axios.post("/api/subpedidos/" + state.pedido_id + "/sync", body);
commit('setPedido', response.data.data);
dispatch("ui/toast", { mensaje: 'Pedido modificado con éxito' }, { root: true });
} catch (error) {
@ -82,11 +98,9 @@ const actions = {
}
},
async modificarDevoluciones({ commit, dispatch }, { monto, notas }) {
const body = { total: monto, notas: notas };
try {
const response = await axios.post("api/subpedidos/" + state.pedido_id + "/sync_devoluciones", {
total: monto,
notas: notas,
});
const response = await axios.post("api/subpedidos/" + state.pedido_id + "/sync_devoluciones", body);
commit('setPedido', response.data.data);
dispatch("ui/toast", { mensaje: 'Devoluciones modificadas con éxito' }, { root: true });
} catch (error) {
@ -99,6 +113,10 @@ const actions = {
dispatch("ui/resetear", null, { root: true });
commit('reset');
},
async getPedidoDeOllas({ commit }) {
const response = await axios.get(`/ollas/${state.grupo_de_compra.id}`);
commit('setPedidoDeOllas', response.data);
},
};
const getters = {

View file

@ -2,6 +2,7 @@ const state = {
show_chismosa: false,
show_devoluciones: false,
show_tags: true,
burger_activa: false,
tags_interactuada: false,
migas: [{ nombre: 'Pedidos', action: 'pedido/resetear' }],
canasta_actual: null,
@ -18,10 +19,12 @@ const mutations = {
state.show_devoluciones = !state.show_devoluciones;
},
toggleTags(state, manual) {
if (manual)
state.tags_interactuada = true;
state.tags_interactuada = manual;
state.show_tags = !state.show_tags;
},
toggleBurger(state) {
state.burger_activa = !state.burger_activa;
},
addMiga(state, miga) {
state.migas.push(miga);
},
@ -32,7 +35,10 @@ const mutations = {
reset(state) {
state.show_chismosa = false;
state.show_devoluciones = false;
}
},
migasOllas(state) {
state.migas.reverse().pop();
},
};
const actions = {
@ -50,7 +56,7 @@ const actions = {
}
dispatch(miga.action, miga.arguments ?? null, { root: true });
state.migas = dropWhile(state.migas.reverse(),(m => m.nombre !== miga.nombre)).reverse();
state.migas = dropWhile(state.migas.reverse(), (m => m.nombre !== miga.nombre)).reverse();
},
toast(_, { mensaje }) {
return window.bulmaToast.toast({
@ -69,6 +75,9 @@ const actions = {
resetear({ commit }) {
commit("reset");
},
migasOllas({ commit }) {
commit("migasOllas");
},
};
export default {

View file

@ -13,12 +13,12 @@ html, body {
height: 100%;
}
form, #root, #login-form {
form, #root, #login-form, #main {
height: 100%;
}
main.has-top-padding {
padding-top: 4.5rem !important;
padding-top: 4rem !important;
}
table.table td {
@ -35,14 +35,39 @@ table.table td {
z-index: 30;
}
#main {
height: 100%;
}
.container {
max-height: 100% !important;
}
.danger-dark-button {
background-color: $danger-dark;
border-color: transparent;
color: #fff;
}
p.navbar-item:empty {
display: none;
}
#nav-bar {
z-index: 10;
}
.text-a {
color: inherit;
}
@media (max-width: 1023px) {
.hide-below-1024 {
display: none !important;
}
}
@media (min-width: 1024px) {
.hide-above-1023 {
display: none !important;
}
}
/*
Author: Aseem Lalfakawma <alalfakawma.github.io>
This SCSS mixin will allow sizing of table columns in Bulma CSS Framework.

View file

@ -48,4 +48,6 @@ Route::middleware('api')->group(function() {
Route::prefix('productos')->group(function () {
Route::get('/','Api\ProductoController@index');
});
Route::get('/parametros', 'ComisionesController@obtenerParametros');
});

View file

@ -24,6 +24,7 @@ Auth::routes(['register' => false]);
Route::get('/', 'RouteController@home')->name('home');
Route::middleware(['auth'])->group(function () {
Route::get('/user', 'UserController@user')->name('user');
Route::get('/user/rol', 'UserController@rol')->name('user.rol');
Route::get('/user/grupo_de_compra', 'UserController@grupoDeCompra');
});
@ -52,7 +53,17 @@ Route::middleware(['auth', 'role:comision'])->group( function() {
Route::get('/comisiones/pedidos/descargar', 'ComisionesController@descargarPedidos')->name('comisiones.pedidos.descargar');
Route::get('/comisiones/pedidos/notas', 'ComisionesController@descargarNotas')->name('comisiones.pedidos.descargar');
Route::get('/comisiones/pedidos/pdf', 'ComisionesController@pdf')->name('comisiones.pedidos.pdf');
Route::get('/comisiones/pedidos/ollas', 'ComisionesController@descargarPedidosDeOllas')->name('comisiones.pedidos.ollas');
Route::get('/comisiones/canasta/ejemplo', 'ComisionesController@descargarCanastaEjemplo')->name('comisiones.canasta.ejemplo');
Route::post('/comisiones/canasta', 'ComisionesController@cargarCanasta')->name('comisiones.canasta');
Route::post('/comisiones/saldos', 'ComisionesController@cargarSaldos')->name('comisiones.saldos');
Route::put('/comisiones/parametros/{parametro_id}', 'ComisionesController@modificarParametros');
});
Route::get('/ollas/login', 'OllasController@show')->name('ollas.login');
Route::middleware(['auth', 'role:ollas'])->prefix('ollas')->group( function() {
Route::get('/', 'RouteController@main')->name('ollas');
Route::get('/{gdc}','OllasController@pedido');
Route::put('/{gdc}/cantidad','OllasController@actualizarCantidadOllas');
});