Compare commits

..

33 commits

Author SHA1 Message Date
ale
7e49633b2c Merge branch 'master' into funcion/manejo-de-barrios 2025-08-13 17:28:02 -03:00
ale
fdfba78d21 Moviendo logica 2025-08-13 01:49:07 -03:00
ale
544f54e95d Cambiando de titulo segun si se crea o modifica un barrio 2025-08-13 01:45:38 -03:00
ale
6fe4295577 Arreglada logica para crear y para modificar barrios 2025-08-13 01:45:23 -03:00
ale
cdbd1504b6 Agregada logica para crear barrios 2025-08-13 00:58:50 -03:00
ale
e4e4fe2fff Cambio de método según si se está modificando o creando un barrio 2025-08-13 00:58:24 -03:00
ale
6200ee234a Agregada lógica para crear barrios 2025-08-13 00:58:05 -03:00
ale
bf97f60e32 Agregada ruta para crear barrio 2025-08-13 00:57:35 -03:00
ale
408629a78d Agregado método para crear barrio 2025-08-13 00:57:25 -03:00
ale
105767dab7 Agregado boton para agregar barrio 2025-08-13 00:31:41 -03:00
ale
54fe1ff601 Cambio a dropdown generico 2025-08-13 00:30:57 -03:00
ale
228b1b63bc Agregada opcion de rutas 2025-08-13 00:02:22 -03:00
ale
d4a2735af5 Agregada ruta para notas y arreglados nomrbes 2025-08-13 00:02:11 -03:00
ale
1aeb917ecf Arreglado regex y agregado metodo para notas de barrio 2025-08-13 00:01:58 -03:00
ale
092e98a456 Agregado metodo para notas de barrio 2025-08-13 00:01:53 -03:00
ale
56e1a94ee7 Ruta para descargar pedidos barriales simplificada 2025-08-12 23:47:13 -03:00
ale
9d049200cb Cambio a pedido de ollas 2025-08-12 23:44:21 -03:00
ale
4a01b21307 Agregadas rutas para descargar pedidos barriales y de ollas 2025-08-12 23:43:53 -03:00
ale
2ef4d77e4f Agregado metodo para exportar pedido de ollas a csv 2025-08-12 23:42:43 -03:00
ale
59cefc8233 Agregada logica para modificar grupo de compra 2025-08-12 23:31:39 -03:00
ale
284cef2d7d Agregados métodos para modificar grupo de compra 2025-08-12 23:30:05 -03:00
ale
af4a697388 Cambio ruta a ComisionesController 2025-08-12 23:29:52 -03:00
ale
a9d26cc146 Agregada logica de modificar barrio a comisiones controller 2025-08-12 23:29:48 -03:00
ale
bdbf5939a1 Cambio estilo boton de modificar 2025-08-12 23:29:05 -03:00
ale
cc341462e5 Pedidos y saldos fusionadas en barrios 2025-08-12 22:29:20 -03:00
ale
70f5756988 Agregada barrios seccion 2025-08-12 22:29:05 -03:00
ale
207b6d91c3 Agregada tabla de barrios 2025-08-12 22:28:34 -03:00
ale
5b824ae7f8 Agregado modal para modificar barrio, faltan rutas 2025-08-12 22:28:19 -03:00
ale
2dba5ef3b4 Agregado componente para dropdown generico 2025-08-12 22:26:48 -03:00
ale
bdd44d72fb Agregada region a resource de comisiones 2025-08-12 22:03:20 -03:00
ale
708c153fe6 Cambio en z-index 2025-08-12 22:03:01 -03:00
ale
42445e216e Agregada ruta para cambiar contraseña desde comisiones, y agregados nombres faltantes a rutas 2025-08-12 16:02:57 -03:00
ale
12c187da13 Método para cambiar contraseña 2025-08-12 16:02:28 -03:00
62 changed files with 27675 additions and 4565 deletions

View file

@ -4,11 +4,9 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
VITE_DEV_SERVER_URL=http://vite:5173
LOG_CHANNEL=stack LOG_CHANNEL=stack
USERID=1000 USERID=
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=db DB_HOST=db

View file

@ -1,4 +1,4 @@
FROM php:8.3-fpm FROM php:7.4-fpm
# Arguments defined in docker-compose.yml # Arguments defined in docker-compose.yml
ARG user ARG user
@ -12,8 +12,7 @@ RUN apt-get update && apt-get install -y \
libonig-dev \ libonig-dev \
libxml2-dev \ libxml2-dev \
zip \ zip \
unzip \ unzip
libzip-dev
# Install node # Install node
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
@ -23,7 +22,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
RUN apt-get clean && rm -rf /var/lib/apt/lists/* RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions # Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Get latest Composer # Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

View file

@ -2,14 +2,13 @@
Aplicación de pedidos del Mercado Popular de Subsistencia. Aplicación de pedidos del Mercado Popular de Subsistencia.
Pedi2 está hecha en Laravel 12 y Vue 2 con Vite y Vuex. Pedi2 está hecha en Laravel 7 y utiliza laravel7-docker de dyarleniber.
Se utilizan los siguientes servicios, separadamente: Se utilizan los siguientes servicios, separadamente:
- `app`, un servicio que corre PHP8.3-FPM. - `app`, un servicio que corre PHP7.4-FPM.
- `db`, un servicio que corre MySQL 5.7. - `db`, un servicio que corre MySQL 5.7.
- `nginx`, un servicio que usa el servicio app para parsear código PHP antes de servir la aplicación de Laravel al usuario final. - `nginx`, un servicio que usa el servicio app para parsear código PHP antes de servir la aplicación de Laravel al usuario final.
- `vite`, un servicio que corre el frontend de la aplicación.
## Pre-requisitos ## Pre-requisitos
- docker - docker
@ -18,28 +17,28 @@ Se utilizan los siguientes servicios, separadamente:
## Instalación ## Instalación
1. Una vez descargado el proyecto, hacé una copia del archivo `.env.example` que se encuentra en la raíz del proyecto y nombrala `.env`. Seteá los valores correctos - específicamente, para las variables, `APP_URL`, `DB_USERNAME` y `DB_PASSWORD`. Prestá atención a que `DB_HOST` sea el nombre del servicio que corre MySQL (por defecto `DB_HOST=db`). 1. Una vez descargado el proyecto, hacé una copia del archivo `.env.example` que se encuentra en la raíz del proyecto y nombrala `.env`. Seteá los valores correctos - específicamente, para las variables, `APP_URL`, `DB_USERNAME` y `DB_PASSWORD`. Prestá atención a que `DB_HOST` sea el nombre del servicio que corre MySQL (por defecto `DB_HOST=db`).
2. Levantá los contenedores, construyendo la imagen de la app primero: 2. Levantá los contenedores, construyendo la imagen de la app primero
```bash ```bash
docker-compose up --build docker-compose up -d --build
``` ```
El ambiente ahora está andando, deberías ver los logs de cada servicio en la terminal. Falta ejecutar un par de comandos para terminar la instalación de Laravel. Podemos usar `docker-compose exec [nombre-del-servicio]` previo a un comando para ejecutarlo dentro del contenedor. El ambiente ahora está andando, pero necesitamos ejecutar un par de comandos para terminar la instalación de Laravel. Podemos usar `docker-compose exec [nombre-del-servicio]` previo a un comando para ejecutarlo dentro del contenedor.
3. Abrí una nueva terminal, y terminá de instalar las dependencias de la app, según fueron definidas en `composer.json`: 3. Terminá de instalar las dependencias de la app, según fueron definidas en `composer.json`.
```bash ```bash
docker-compose exec app composer install docker-compose exec app composer install
``` ```
4. Generá una clave de aplicación. Esta clave se usa para encriptar datos sensibles: 4. Generá una clave de aplicación. Esta clave se usa para encriptar datos sensibles.
```bash ```bash
docker-compose exec app php artisan key:generate docker-compose exec app php artisan key:generate
``` ```
5. Corré las migraciones y seeders de Laravel: 5. Corré las migraciones y seeders de Laravel
```bash ```bash
docker-compose exec app php artisan migrate:fresh --seed docker-compose exec app php artisan migrate:fresh --seed
@ -47,7 +46,7 @@ docker-compose exec app php artisan migrate:fresh --seed
6. Copia el token que se imprime al correr los seeders. Lo necesitamos para autenticar las llamadas que hagamos desde nuestro cliente web 6. Copia el token que se imprime al correr los seeders. Lo necesitamos para autenticar las llamadas que hagamos desde nuestro cliente web
7. Instala las dependencias de npm: 7. Instala las dependencias de npm
```bash ```bash
docker-compose exec app npm install docker-compose exec app npm install
``` ```
@ -55,13 +54,73 @@ docker-compose exec app npm install
Ahora la aplicación está corriendo y la podés ver en el puerto 8000 de tu dominio o IP. En caso de que estés en tu máquina local, la vas a ver accediendo a `http://localhost:8000` desde tu navegador. Ahora la aplicación está corriendo y la podés ver en el puerto 8000 de tu dominio o IP. En caso de que estés en tu máquina local, la vas a ver accediendo a `http://localhost:8000` desde tu navegador.
Podés usar el comando `logs` para ver los logs generados por tus servicios: Podés usar el comando `logs` para ver los logs generados por tus servicios:
```bash ```bash
docker-compose logs nginx docker-compose logs nginx
``` ```
---
Si estás actualizando o no te anda, probá limpiar los caches: 8. Ejecuta npm para compilar el js y css
```bash
docker-compose exec app npm run prod
``` ```
docker-compose exec app php artisan optimize:clear
docker-compose exec app composer dump-autoload ## Services description
### Dockerfile
Although both `db` service and `nginx` service, will be based on default images obtained from the Docker Hub, the `app` service will be based on a custom image created by the `Dockerfile`.
The `Dockerfile` starts by defining the base image `php:7.4-fpm`.
After installing system packages and PHP extensions, the Composer will be installed by copying the composer executable from its latest official image.
A new system user is then created and set up using the `user` and `uid` arguments that were declared at the beginning of the `Dockerfile`. These values will be injected by Docker Compose at build time.
> This new system user is necessary to execute Laravel Artisan and Composer commands while developing the application. The `uid` setting ensures that the user inside the container has the same `uid` as your system user on your host machine. This way, any files created by these commands are replicated in the host with the correct permissions. This also means that youll be able to use your code editor of choice in the host machine to develop the application that is running inside containers.
Finally, the default working dir as `/var/www` and the newly created user are set. This will make sure youre connecting as a regular user, and that youre on the right directory, when running Laravel Artisan and Composer commands on the application container.
### PHP service
The `app` service will build an image called `laravel-image`, based on the `Dockerfile` previously created. The container defined by this service will run a php-fpm server to parse PHP code and send the results back to the nginx service, which will be running on a separate container. The mysql service defines a container running a MySQL 5.7 server. All these services will share a bridge network named `app-network`.
The application files will be synchronized on both the `app` and the `nginx` services via bind mounts. Bind mounts are useful in development environments because they allow for a performant two-way sync between host machine and containers.
Inside the `app` container you will be able to execute command line tasks with the Laravel Artisan and Composer.
The `app` service will set up a container named `laravel-app`. It builds a new Docker image based on a `Dockerfile` located in the same path as the `docker-compose.yml` file. The new image will be saved locally under the name `laravel-image`.
The `volumes` setting creates a shared volume that will synchronize contents from the current directory to `/var/www` inside the container. Notice that this is not your document root, since that will live in the nginx container.
Another file which will be synchronized is the `local.ini` file from the directory `./php/local.ini` to `/usr/local/etc/php/conf.d/local.ini` inside the container.
The `local.ini` is the configuration file (php.ini) that is read when PHP starts up.
### Nginx service
The `nginx` service uses a pre-built Nginx image on top of Alpine, a lightweight Linux distribution. It creates a container named `laravel-nginx`, and it uses the ports definition to create a redirection from port `8000` on the host system to port `80` inside the container.
The `volumes` setting creates two shared volumes. The first one will synchronize contents from the current directory to `/var/www` inside the container. This way, when you make local changes to the application files, they will be quickly reflected in the application being served by Nginx inside the container. The second volume will make sure the Nginx configuration file, located at `./nginx/conf.d/app.conf`, is copied to the containers Nginx configuration folder. This configuration file will configure Nginx to listen on port `80` and use `index.php` as default index page. It will set the document root to `/var/www/public`, and then configure Nginx to use the `app` service on port `9000` to process all the php files.
### MySQL service
The `db` service uses a pre-built MySQL 5.7 image from Docker Hub. Because Docker Compose automatically loads `.env` variable files located in the same directory as the `docker-compose.yml` file, you can obtain the database settings from the Laravel `.env` file.
The `volumes` setting creates two shared volumes. The first one will make sure the MySQL configuration file, located at `./mysql/my.cnf`, is copied to the containers MySQL configuration folder. The second volume will share a `.sql` database dump that will be used to initialize the application database. The MySQL image will automatically import `.sql` files placed in the `/docker-entrypoint-initdb.d` directory inside the container.
The `environment` setting defines environment variables in the new container. You can use values obtained from the Laravel `.env` file to set up the MySQL service, which will automatically create a new database and user based on the provided environment variables:
```bash
DB_HOST=db
DB_DATABASE=laravelapp
DB_USERNAME=laravelapp_user
DB_PASSWORD=password
``` ```
## References
- https://www.digitalocean.com/community/tutorials/how-to-install-and-set-up-laravel-with-docker-compose-on-ubuntu-20-04
- https://docs.docker.com/
- https://docs.docker.com/compose/
- https://github.com/dyarleniber/laravel7-docker
- https://laravel.com/docs/7.x/installation

View file

@ -12,6 +12,7 @@ use TypeError;
class Filtro extends Model class Filtro extends Model
{ {
protected Request $request; protected Request $request;
protected $builder;
protected array $MENSAJES_ERROR = [ protected array $MENSAJES_ERROR = [
'ARGUMENTO' => 'Argumento inválido para el parámetro %s. Revise la documentación.' 'ARGUMENTO' => 'Argumento inválido para el parámetro %s. Revise la documentación.'
]; ];

View file

@ -128,6 +128,31 @@ class PedidosExportHelper
); );
} }
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
static public function pedidoOllasDeBarrio(GrupoDeCompra $grupo)
{
$filePath = "csv/exports/" . $grupo->nombre . "-ollas-" . now()->format('Y-m-d') . ".csv";
$tipo_olla = self::getTipoId('olla');
$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.tipo_pedido_id = $tipo_olla"
),
);
}
/** /**
* @throws InvalidArgument * @throws InvalidArgument
* @throws CannotInsertRecord * @throws CannotInsertRecord

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\GrupoDeCompra; use App\GrupoDeCompra;
use App\Helpers\PedidosExportHelper; use App\Helpers\PedidosExportHelper;
use App\Producto;
use League\Csv\Exception; use League\Csv\Exception;
use Mpdf\MpdfException; use Mpdf\MpdfException;
@ -60,4 +61,38 @@ class AdminController extends Controller
return response()->download($files[0]); return response()->download($files[0]);
} }
public function exportarPedidoOllasACSV(GrupoDeCompra $gdc)
{
try {
PedidosExportHelper::pedidoOllasDeBarrio($gdc);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()]);
}
$pattern = storage_path('csv/exports/'. $gdc->nombre . '-ollas-*.csv');
$files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
}
public function exportarNotasACSV(GrupoDeCompra $gdc)
{
try {
Producto::planillaNotasBarrio($gdc);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()]);
}
$pattern = storage_path('csv/exports/notas-de-'. $gdc->nombre . '-*.csv');
$files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
}
} }

View file

@ -7,16 +7,21 @@ use App\Helpers\CanastaHelper;
use App\Helpers\CsvHelper; use App\Helpers\CsvHelper;
use App\Helpers\PedidosExportHelper; use App\Helpers\PedidosExportHelper;
use App\Helpers\TransporteHelper; use App\Helpers\TransporteHelper;
use App\Http\Resources\GrupoDeCompraComisionesResource;
use App\Http\Resources\GrupoDeCompraResource; use App\Http\Resources\GrupoDeCompraResource;
use App\Producto; use App\Producto;
use App\User;
use App\UserRole;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use League\Csv\CannotInsertRecord; use League\Csv\CannotInsertRecord;
use League\Csv\Exception; use League\Csv\Exception;
use League\Csv\InvalidArgument; use League\Csv\InvalidArgument;
use Mpdf\MpdfException; use Mpdf\MpdfException;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ComisionesController class ComisionesController
{ {
@ -183,4 +188,105 @@ class ComisionesController
$records = CsvHelper::getRecords(resource_path(self::PARAMETROS_PATH), "No se pudo leer el archivo."); $records = CsvHelper::getRecords(resource_path(self::PARAMETROS_PATH), "No se pudo leer el archivo.");
return iterator_to_array($records); return iterator_to_array($records);
} }
public static function modificarGrupoDeCompra(Request $request, $grupo_de_compra_id) {
$valid = $request->validate([
'nombre' => ['nullable','string','regex:/^([a-z]| )+$/i'],
'region' => ['nullable','string','regex:/^([a-z]| |\d)+$/i'],
'passBarrio' => ['nullable','string','alpha_num','min:3'],
'passAdmin' => ['nullable','string','alpha_num','min:3'],
'passOllas' => ['nullable','string','alpha_num','min:3'],
]);
$grupoDeCompra = GrupoDeCompra::find($grupo_de_compra_id);
foreach (array_keys($valid) as $key) {
switch ($key) {
case 'nombre':
$users = User::where([
'grupo_de_compra_id' => $grupoDeCompra->id,
])->first();
foreach ($users as $user) {
$user->name = str_replace($grupoDeCompra->nombre, $valid['nombre'], $user->name);
$user->save();
}
$grupoDeCompra->nombre = $valid['nombre'];
$grupoDeCompra->save();
break;
case 'region':
$grupoDeCompra->region = $valid['region'];
$grupoDeCompra->save();
break;
case 'passBarrio':
$user = User::where([
'grupo_de_compra_id' => $grupoDeCompra->id,
'role_id' => UserRole::where(['nombre' => 'barrio'])->first()->id,
])->first();
$user->password = Hash::make($valid['passBarrio']);
$user->save();
break;
case 'passAdmin':
$user = User::where([
'grupo_de_compra_id' => $grupoDeCompra->id,
'role_id' => UserRole::where(['nombre' => 'admin_barrio'])->first()->id,
])->first();
$user->password = Hash::make($valid['passAdmin']);
$user->save();
break;
case 'passOllas':
$user = User::where([
'grupo_de_compra_id' => $grupoDeCompra->id,
'role_id' => UserRole::where(['nombre' => 'ollas'])->first()->id,
])->first();
$user->password = Hash::make($valid['passOllas']);
$user->save();
break;
default:
break;
}
}
return response()->noContent();
}
public static function crearGrupoDeCompra(Request $request) {
$valid = $request->validate([
'nombre' => ['required','string','regex:/^([a-z]| )+$/i'],
'region' => ['required','string','regex:/^([a-z]| |\d)+$/i'],
'passBarrio' => ['required','string','alpha_num','min:3'],
'passAdmin' => ['required','string','alpha_num','min:3'],
'passOllas' => ['required','string','alpha_num','min:3'],
]);
if (GrupoDeCompra::where(["nombre" => $valid["nombre"]])->get()->count())
throw new HttpException(400, "Ya existe un barrio con este nombre.");
$gdc = GrupoDeCompra::create([
'nombre' => $valid['nombre'],
'region' => $valid['region'],
'saldo' => 0,
]);
User::create([
'grupo_de_compra_id' => $gdc->id,
'name' => $valid['nombre'],
'password' => Hash::make($valid['passBarrio']),
'role_id' => UserRole::where('nombre','barrio')->first()->id,
]);
User::create([
'grupo_de_compra_id' => $gdc->id,
'name' => $valid['nombre'] . '_admin',
'password' => Hash::make($valid['passAdmin']),
'role_id' => UserRole::where('nombre','admin_barrio')->first()->id,
]);
User::create([
'grupo_de_compra_id' => $gdc->id,
'name' => $valid['nombre'] . '_ollas',
'password' => Hash::make($valid['passOllas']),
'role_id' => UserRole::where('nombre','ollas')->first()->id,
]);
return new GrupoDeCompraComisionesResource($gdc);
}
} }

View file

@ -5,9 +5,11 @@ namespace App\Http\Controllers;
use App\GrupoDeCompra; use App\GrupoDeCompra;
use App\Http\Resources\GrupoDeCompraPedidoResource; use App\Http\Resources\GrupoDeCompraPedidoResource;
use App\Http\Resources\GrupoDeCompraResource; use App\Http\Resources\GrupoDeCompraResource;
use App\User;
use App\UserRole; use App\UserRole;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class UserController extends Controller class UserController extends Controller
{ {

View file

@ -18,7 +18,7 @@ class Kernel extends HttpKernel
protected $middleware = [ protected $middleware = [
// \App\Http\Middleware\TrustHosts::class, // \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class, \Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\CheckForMaintenanceMode::class, \App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\TrimStrings::class,

View file

@ -2,8 +2,8 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware; use Fideloper\Proxy\TrustProxies as Middleware;
use Symfony\Component\HttpFoundation\Request; use Illuminate\Http\Request;
class TrustProxies extends Middleware class TrustProxies extends Middleware
{ {
@ -19,8 +19,5 @@ class TrustProxies extends Middleware
* *
* @var int * @var int
*/ */
protected $headers = Request::HEADER_X_FORWARDED_FOR | protected $headers = Request::HEADER_X_FORWARDED_ALL;
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO;
} }

View file

@ -16,6 +16,7 @@ class GrupoDeCompraComisionesResource extends JsonResource
return [ return [
'id' => $this->id, 'id' => $this->id,
'nombre' => $this->nombre, 'nombre' => $this->nombre,
'region' => $this->region,
'saldo' => $this->saldo, 'saldo' => $this->saldo,
]; ];
} }

View file

@ -103,4 +103,32 @@ class Producto extends Model
$filePath = 'csv/exports/notas-por-barrio-' . $fecha . '.csv'; $filePath = 'csv/exports/notas-por-barrio-' . $fecha . '.csv';
CsvHelper::generarCsv($filePath, $planilla, $headers); CsvHelper::generarCsv($filePath, $planilla, $headers);
} }
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
*/
static public function planillaNotasBarrio($gdc) {
$headers = ['Producto'];
$barrios = [$gdc->nombre];
$headers = array_merge($headers, $barrios);
$notasPorBarrio = self::notasPorBarrio();
$planilla = [];
foreach ($notasPorBarrio as $producto => $notasGrupo) {
$fila = [$producto];
foreach ($barrios as $barrio) {
$notas = $notasGrupo->where('barrio', $barrio)
->pluck('notas')
->implode('; ');
$fila[] = $notas ?: '';
}
$planilla[] = $fila;
}
$fecha = now()->format('Y-m-d');
$filePath = 'csv/exports/notas-de-' . $gdc->nombre . '-' . $fecha . '.csv';
CsvHelper::generarCsv($filePath, $planilla, $headers);
}
} }

View file

@ -16,7 +16,7 @@ class User extends Authenticatable
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'email', 'password', 'role_id', 'grupo_de_compra_id', 'name', 'email', 'password', 'role_id',
]; ];
/** /**

View file

@ -8,20 +8,23 @@
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.3", "php": "^7.4",
"doctrine/dbal": "^3.0", "doctrine/dbal": "^2.2.0",
"guzzlehttp/guzzle": "^7.0.1", "fideloper/proxy": "^4.4",
"laravel/framework": "^12.0", "fruitcake/laravel-cors": "^2.0",
"laravel/sanctum": "^4.0", "guzzlehttp/guzzle": "^6.3.1|^7.0.1",
"laravel/tinker": "^2.8", "laravel/framework": "^7.29",
"laravel/ui": "^4.3", "laravel/sanctum": "^2.13",
"laravel/tinker": "^2.5",
"laravel/ui": "*",
"league/csv": "^9.8", "league/csv": "^9.8",
"mpdf/mpdf": "^8.2", "mpdf/mpdf": "^8.1",
"prexview/prexview": "^1.1" "prexview/prexview": "^1.1"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^2.0",
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",
"nunomaduro/collision": "^8.0" "nunomaduro/collision": "^4.3"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,
@ -52,13 +55,13 @@
"scripts": { "scripts": {
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi" "@php7.4 artisan package:discover --ansi"
], ],
"post-root-package-install": [ "post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" "@php7.4 -r \"file_exists('.env') || copy('.env.example', '.env');\""
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi" "@php7.4 artisan key:generate --ansi"
] ]
} }
} }

3413
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ class UserSeeder extends Seeder
$usersToInsert = []; $usersToInsert = [];
$usersToInsert[] = DatabaseSeeder::addTimestamps([ $usersToInsert[] = DatabaseSeeder::addTimestamps([
'name' => 'comision', 'name' => 'comi',
'password' => Hash::make("123"), 'password' => Hash::make("123"),
'role_id' => UserRole::where('nombre', 'comision')->first()->id, 'role_id' => UserRole::where('nombre', 'comision')->first()->id,
]); ]);

View file

@ -18,22 +18,6 @@ services:
networks: networks:
- app-network - app-network
vite:
build:
args:
user: www
uid: ${USERID}
context: ./
container_name: vite
working_dir: /var/www
command: npm run dev
volumes:
- ./:/var/www
ports:
- "5173:5173"
networks:
- app-network
db: db:
image: mysql:5.7 image: mysql:5.7
container_name: pedi2-db container_name: pedi2-db

View file

@ -13,22 +13,6 @@ server {
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_INFO $fastcgi_path_info;
} }
location ^~ /@vite/ {
proxy_pass http://vite:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location ^~ /resources/ {
proxy_pass http://vite:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location / { location / {
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;
gzip_static on; gzip_static on;

27234
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,7 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "npm run development",
"build": "vite build",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js", "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch", "watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll", "watch-poll": "npm run watch -- --watch-poll",
@ -11,24 +10,22 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js" "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
}, },
"devDependencies": { "devDependencies": {
"@types/axios": "^0.9.36", "axios": "^0.19.2",
"@vitejs/plugin-vue2": "^2.3.3",
"axios": "^0.27.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"laravel-vite-plugin": "^1.3.0", "laravel-mix": "^5.0.1",
"resolve-url-loader": "^2.3.1",
"sass": "^1.20.1", "sass": "^1.20.1",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"typescript": "^5.4.5", "vue": "^2.5.17",
"vite": "^5.2.8" "vue-template-compiler": "^2.6.10",
"webpack": "^4.47.0",
"webpack-cli": "^3.3.12"
}, },
"dependencies": { "dependencies": {
"vue": "^2.7.16",
"vue-template-compiler": "^2.7.16",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
"bulma-toast": "^2.4.1", "bulma-toast": "^2.4.1",
"vue-router": "^3.5.4",
"vuex": "^3.6.2" "vuex": "^3.6.2"
} }
} }

45
resources/js/app.js vendored Normal file
View file

@ -0,0 +1,45 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
import axios from 'axios';
import Vue from 'vue';
window.Vue = require('vue');
window.Event = new Vue();
window.axios = axios;
window.bulmaToast = require('bulma-toast');
/**
* The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue
* components and automatically register them with their "basename".
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
import './components';
import store from "./store";
/**
* Global methods
*/
Vue.prototype.$toast = function (mensaje, duration = 2000) {
return window.bulmaToast.toast({
message: mensaje,
duration: duration,
type: 'is-danger',
position: 'bottom-center',
});
}
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
new Vue({
el: '#root',
store,
});

View file

@ -1,27 +0,0 @@
import * as bulmaToast from 'bulma-toast';
import Vue from '../../node_modules/vue/dist/vue.esm.js';
import axios from 'axios';
import store from "./store";
import './shims-vue.d.ts';
// Registro de components
const components = import.meta.glob('./components/**/*.vue', { eager: true });
Object.entries(components).forEach(([path, module]) => {
let name = path
.replace(/^\.\/components\//, '') // Remove leading folder
.replace(/\.vue$/, '') // Remove file extension
.replace(/\//g, '-') // Replace subfolders with hyphens
.replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase to kebab-case
.toLowerCase(); // Enforce kebab-case for HTML
Vue.component(name, (module as any).default);
});
window.Vue = Vue;
window.Event = new Vue();
window.axios = axios;
window.bulmaToast = bulmaToast;
new Vue({
el: '#root',
store,
});

25
resources/js/components.js vendored Normal file
View file

@ -0,0 +1,25 @@
import Vue from 'vue';
const requireComponent = require.context('./components', true, /\.vue$/);
// Registro automático de componentes:
// e.g. components/foo/bar/UnComponente.vue
// se registra como 'foo-bar-un-componente'
requireComponent.keys().forEach(fileName => {
// Get the component config
const componentConfig = requireComponent(fileName);
// Get the PascalCase name of the component
const componentName = fileName
.replace(/^\.\/(.*)\.\w+$/, '$1') // Remove "./" from the beginning and the file extension from the end
.replace(/\//g, '-') // Replace directories with hyphens
.replace(/([a-z])([A-Z])/g, '$1-$2') // Insert hyphen between camelCase words
.toLowerCase() // Convert to lowercase
// Globally register the component
Vue.component(
componentName,
// Look for the component options on `.default`, which will
// exist if the component was exported with `export default`,
// otherwise fall back to module's root.
componentConfig.default || componentConfig
);
});

View file

@ -1,12 +1,12 @@
<script> <script>
import LoginInput from "./login/LoginInput.vue"; import LoginInput from "./login/LoginInput.vue";
import LoginTitulos from "./login/LoginTitulos.vue"; import LoginLoginTitulos from "./login/LoginTitulos.vue";
import { mapGetters } from "vuex"; import { mapGetters } from "vuex";
import LoginDropdown from "./login/LoginDropdown.vue"; import LoginDropdown from "./login/LoginDropdown.vue";
export default { export default {
name: 'AppLogin', name: 'LoginForm',
components: { LoginDropdown, LoginTitulos, LoginInput }, components: { LoginDropdown, LoginTitulos: LoginLoginTitulos, LoginInput },
computed: { computed: {
...mapGetters("login", ["estilos"]) ...mapGetters("login", ["estilos"])
} }

View file

@ -14,14 +14,14 @@
</div> </div>
<div class="dropdown-menu" id="dropdown-menu" role="menu"> <div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<a :href="'/admin/exportar-pedido-a-csv/' + grupo_de_compra_id" class="dropdown-item has-background-primary">
Planilla para central (CSV)
</a>
<a :href="'/admin/exportar-planillas-a-pdf/' + grupo_de_compra_id" class="dropdown-item"> <a :href="'/admin/exportar-planillas-a-pdf/' + grupo_de_compra_id" class="dropdown-item">
Planillas para armado (PDF) Planillas para armado (PDF)
</a> </a>
<a :href="'/admin/exportar-pedido-con-nucleos-a-csv/' + grupo_de_compra_id" class="dropdown-item"> <a :href="'/admin/exportar-pedido-con-nucleos-a-csv/' + grupo_de_compra_id" class="dropdown-item">
Pedidos por núcleo (CSV) Planilla completa de la canasta (CSV)
</a>
<a :href="'/admin/exportar-pedido-a-csv/' + grupo_de_compra_id" class="dropdown-item">
Pedido barrial (CSV)
</a> </a>
</div> </div>
</div> </div>

View file

@ -2,22 +2,15 @@
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop"> <div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<tabs-secciones :tabs="tabs" :tabInicial="tabActiva"/> <tabs-secciones :tabs="tabs" :tabInicial="tabActiva"/>
<div class="block pb-6" <div class="block pb-6"
id="pedidos-comisiones-seccion" id="barrios-comisiones-seccion"
:class="seccionActiva === 'pedidos-comisiones-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'barrios-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="pedidos-comisiones-tabla-y-dropdown"> <barrios-seccion/>
<dropdown-descargar/>
</div>
</div> </div>
<div class="block pb-6" <div class="block pb-6"
id="canasta-comisiones-seccion" id="canasta-comisiones-seccion"
:class="seccionActiva === 'canasta-comisiones-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'canasta-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<canasta-seccion/> <canasta-seccion/>
</div> </div>
<div class="block pb-6"
id="saldos-comisiones-seccion"
:class="seccionActiva === 'saldos-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<saldos-seccion/>
</div>
<div class="block pb-6" <div class="block pb-6"
id="parametros-comisiones-seccion" id="parametros-comisiones-seccion"
:class="seccionActiva === 'parametros-comisiones-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'parametros-comisiones-seccion' ? 'is-active' : 'is-hidden'">
@ -33,13 +26,15 @@ import InputFileButton from "../comunes/InputFileButton.vue";
import CanastaSeccion from "./canasta/CanastaSeccion.vue"; import CanastaSeccion from "./canasta/CanastaSeccion.vue";
import SaldosSeccion from "./saldos/SaldosSeccion.vue"; import SaldosSeccion from "./saldos/SaldosSeccion.vue";
import ParametrosSeccion from "./parametros/ParametrosSeccion.vue"; import ParametrosSeccion from "./parametros/ParametrosSeccion.vue";
import BarriosSeccion from "./barrios/BarriosSeccion.vue";
export default { export default {
name: "ComisionesBody", name: "ComisionesBody",
components: { components: {
ParametrosSeccion,
SaldosSeccion, SaldosSeccion,
BarriosSeccion,
CanastaSeccion, CanastaSeccion,
ParametrosSeccion,
TabsSecciones, TabsSecciones,
DropdownDescargar, DropdownDescargar,
InputFileButton, InputFileButton,
@ -47,13 +42,12 @@ export default {
data() { data() {
return { return {
tabs: [ tabs: [
{ id: "pedidos-comisiones", nombre: "Pedidos" }, { id: "barrios-comisiones", nombre: "Barrios" },
{ id: "canasta-comisiones", nombre: "Canasta" }, { id: "canasta-comisiones", nombre: "Canasta" },
{ id: "saldos-comisiones", nombre: "Saldos" },
{ id: "parametros-comisiones", nombre: "Parámetros" }, { id: "parametros-comisiones", nombre: "Parámetros" },
], ],
tabActiva: "pedidos-comisiones", tabActiva: "barrios-comisiones",
seccionActiva: "pedidos-comisiones-seccion", seccionActiva: "barrios-comisiones-seccion",
} }
}, },
methods: { methods: {

View file

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

View file

@ -0,0 +1,113 @@
<script>
import { mapActions, mapGetters, mapMutations, mapState } from "vuex";
import Dropdown from "../../comunes/Dropdown.vue";
export default {
name: "BarrioRow",
components: { Dropdown },
props: {
grupo_de_compra: {
type: Object,
required: true,
}
},
data() {
return {
saldoControl: 0,
inputSaldoInteractuado: false,
opcionesDescarga: [
{
nombre: "Pedido barrial en csv",
href: `/comisiones/pedidos/${this.grupo_de_compra.id}/`
},
{
nombre: "Notas en csv",
href: `/comisiones/pedidos/${this.grupo_de_compra.id}/notas`
},
{
nombre: "Pedido de ollas en csv",
href: `/comisiones/pedidos/${this.grupo_de_compra.id}/ollas`
}
],
};
},
mounted() {
this.saldoControl = this.grupo_de_compra.saldo;
},
watch: {
lastFetch() {
this.saldoControl = this.grupo_de_compra.saldo;
},
},
methods: {
...mapMutations("ui", ["toggleModalBarrio"]),
...mapMutations("comisiones", ["seleccionarGrupoDeCompra"]),
...mapActions("ui", ["toast"]),
...mapActions("comisiones", ["setSaldo"]),
async confirmarSaldo() {
await this.setSaldo({
gdc_id: this.grupo_de_compra.id,
saldo: this.saldoControl,
});
this.inputSaldoInteractuado = false;
},
abrirModalBarrio() {
this.seleccionarGrupoDeCompra({ grupo_de_compra: this.grupo_de_compra });
this.toggleModalBarrio();
},
descargasBarrio() {
this.toast({
mensaje: `Esto debería ser un dropdown para descargar el pedido de ${this.grupo_de_compra.nombre}`
});
}
},
computed: {
...mapState("comisiones", ["lastFetch"]),
...mapGetters("comisiones", ["saldo"]),
saldoModificado() {
return Number.parseFloat(this.saldo(this.grupo_de_compra.id)) !== Number.parseFloat(this.saldoControl);
},
}
}
</script>
<template>
<tr>
<th>{{ grupo_de_compra.nombre }}</th>
<th>{{ grupo_de_compra.region }}</th>
<td>
<div class="field has-addons">
<div class="control is-expanded">
<input :id="`barrio-input-${grupo_de_compra.id}`"
v-model="saldoControl"
class="input is-small"
type="number"
style="text-align: center"
@input="inputSaldoInteractuado = true">
</div>
<div class="control">
<button :disabled="!(inputSaldoInteractuado && saldoModificado)"
class="button is-small is-success ml-1"
@click="confirmarSaldo">
<span class="icon"><i class="fas fa-check"/></span>
</button>
</div>
</div>
</td>
<td>
<dropdown :opciones="opcionesDescarga"
:placeholder="`Pedido de ${grupo_de_compra.nombre}`"
:is-right="false"/>
</td>
<td>
<button class="button"
@click="abrirModalBarrio">
<span>Modificar barrio</span>
<span class="icon"><i class="fas fa-edit"/></span>
</button>
</td>
</tr>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,63 @@
<script>
import TablaBarrios from "./TablaBarrios.vue";
import DropdownDescargar from "../DropdownDescargar.vue";
import ModalBarrio from "./ModalBarrio.vue";
import { mapActions, mapMutations, mapState } from "vuex";
import Dropdown from "../../comunes/Dropdown.vue";
import comisiones from "../../../store/modules/comisiones";
export default {
name: "BarriosSeccion",
components: { Dropdown, DropdownDescargar, TablaBarrios, ModalBarrio },
data() {
return {
opcionesDescarga: [
{
nombre: "Pedidos por barrio en csv",
href: "/comisiones/pedidos"
},
{
nombre: "Notas por barrio en csv",
href: "/comisiones/pedidos/notas"
},
{
nombre: "Pedidos por barrio en pdf",
href: "/comisiones/pedidos/pdf"
},
{
nombre: "Pedidos de ollas en csv",
href: "/comisiones/pedidos/ollas"
},
],
};
},
computed: {
...mapState("comisiones", ["grupo_de_compra_actual", "grupo_de_compra_nuevo"])
},
methods: {
...mapMutations("ui", ["toggleModalBarrio"]),
...mapMutations("comisiones", ["grupoDeCompraNuevo"]),
modalNuevoBarrio() {
this.grupoDeCompraNuevo();
this.toggleModalBarrio();
}
}
}
</script>
<template>
<div>
<modal-barrio v-if="grupo_de_compra_actual || grupo_de_compra_nuevo"/>
<div class="is-flex is-justify-content-space-between mb-3">
<button class="button" @click="modalNuevoBarrio">
<span class="icon"><i class="fa fa-plus-circle"/></span>
<span>Agregar barrio</span>
</button>
<dropdown :opciones="opcionesDescarga" placeholder="Descargar planillas" :is-right="false"/>
</div>
<tabla-barrios/>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,113 @@
<template>
<div :class="show_modal_barrio ? 'is-active modal' : 'modal'">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ titulo }}</p>
<button class="delete" aria-label="close" @click.capture="cerrar"></button>
</header>
<section class="modal-card-body">
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Nombre:
<input id="notasControl" class="input" type="text" v-model="nombreControl">
</p>
</div>
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Region:
<input id="notasControl" class="input" type="text" v-model="regionControl">
</p>
</div>
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Contraseña pedidos:
<input id="notasControl" class="input" type="text" v-model="passBarrio">
</p>
</div>
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Contraseña admin:
<input id="notasControl" class="input" type="text" v-model="passAdmin">
</p>
</div>
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Contraseña ollas:
<input id="notasControl" class="input" type="text" v-model="passOllas">
</p>
</div>
</section>
<footer class="modal-card-foot is-justify-content-right">
<button class="button" @click="cerrar">Cancelar</button>
<button class="button is-success" @click="confirmar">Aceptar</button>
</footer>
</div>
</div>
</template>
<script>
import { mapActions, mapMutations, mapState } from "vuex";
export default {
name: 'ModalBarrio',
data() {
return {
nombreControl: "",
regionControl: "",
passBarrio: "",
passAdmin: "",
passOllas: "",
}
},
computed: {
...mapState('ui', ["show_modal_barrio"]),
...mapState('comisiones', ["grupo_de_compra_actual", "grupo_de_compra_nuevo"]),
titulo() {
return this.grupo_de_compra_nuevo ? "Agregar barrio" : "Modificar barrio";
}
},
methods: {
...mapMutations("comisiones", ["seleccionarGrupoDeCompra"]),
...mapMutations('ui', ["toggleModalBarrio"]),
...mapActions("comisiones", ["modificarBarrio", "crearBarrio"]),
...mapActions('ui', ["toast", "error"]),
async confirmar() {
if (this.grupo_de_compra_nuevo)
await this.crear();
else
await this.modificar();
this.cerrar();
},
async crear() {
await this.crearBarrio({
nombre: this.nombreControl,
region: this.regionControl,
passBarrio: this.passBarrio,
passAdmin: this.passAdmin,
passOllas: this.passOllas
});
},
async modificar() {
const nombre = this.nombreControl !== this.grupo_de_compra_actual.nombre ? this.nombreControl : undefined;
const region = this.regionControl !== this.grupo_de_compra_actual.region ? this.regionControl : undefined;
await this.modificarBarrio({
gdc_id: this.grupo_de_compra_actual.id,
nombre: nombre,
region: region,
passBarrio: this.passBarrio,
passAdmin: this.passAdmin,
passOllas: this.passOllas,
})
},
cerrar() {
this.toggleModalBarrio();
this.seleccionarGrupoDeCompra({ grupoDeCompra: false });
},
},
mounted() {
this.nombreControl = this.grupo_de_compra_actual.nombre;
this.regionControl = this.grupo_de_compra_actual.region;
}
}
</script>

View file

@ -0,0 +1,41 @@
<script>
import { mapActions, mapState } from "vuex";
import BarrioRow from "./BarrioRow.vue";
export default {
name: "TablaBarrios",
components: { BarrioRow },
async mounted() {
await this.getGruposDeCompra();
},
methods: {
...mapActions("comisiones", ["getGruposDeCompra"]),
},
computed: {
...mapState("comisiones", ["grupos_de_compra"]),
}
}
</script>
<template>
<table class="table is-fullwidth is-striped is-bordered">
<thead>
<tr>
<th>Barrio</th>
<th>Region</th>
<th>Saldo</th>
<th>Pedido</th>
<th>Modificar</th>
</tr>
</thead>
<tbody>
<barrio-row v-for="(gdc,index) in grupos_de_compra"
:grupo_de_compra="gdc"
:key="index"/>
</tbody>
</table>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,52 @@
<template>
<div class="buttons" :class="{'is-right': isRight}">
<div class="dropdown" :class="{'is-active': dropdownActivo}" @mouseleave="dropdownActivo = false">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu" @click="dropdownActivo = !dropdownActivo">
<span class="icon is-small"><i class="fas fa-download"/></span>
<span>{{ placeholder }}</span>
<span class="icon is-small"><i class="fas fa-angle-down" aria-hidden="true"/></span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item"
v-for="(opcion,i) in opciones"
:key="i"
:href="opcion.href">
{{ opcion.nombre }}
</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Dropdown",
props: {
placeholder: {
type: String,
required: true,
},
opciones: {
type: Array,
required: true,
},
isRight: {
type: Boolean,
required: false,
default: true,
}
},
data() {
return {
dropdownActivo: false
}
},
}
</script>
<style>
</style>

View file

@ -59,7 +59,7 @@ export default {
position: fixed; position: fixed;
bottom: 1rem; bottom: 1rem;
right: 1rem; right: 1rem;
z-index: 50; z-index: 25;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
max-width: 90vw; max-width: 90vw;

View file

@ -38,6 +38,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../../node_modules/bulma';
hr { hr {
border: none; border: none;
height: 1px; height: 1px;

View file

@ -1,6 +1,6 @@
<script > <script >
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapGetters, mapMutations, mapState } from "vuex"; import { mapMutations, mapState } from "vuex";
import CategoriasContainer from "./CategoriasContainer.vue"; import CategoriasContainer from "./CategoriasContainer.vue";
import ProductosContainer from "./ProductosContainer.vue"; import ProductosContainer from "./ProductosContainer.vue";
import Chismosa from "./Chismosa.vue"; import Chismosa from "./Chismosa.vue";
@ -9,8 +9,7 @@ import DevolucionesModal from "./DevolucionesModal.vue";
export default defineComponent({ export default defineComponent({
components: { DevolucionesModal, CategoriasContainer, ProductosContainer, Chismosa }, components: { DevolucionesModal, CategoriasContainer, ProductosContainer, Chismosa },
computed: { computed: {
...mapState('ui', ["show_chismosa", "show_devoluciones", "tags_interactuada"]), ...mapState('ui', ["show_chismosa", "show_devoluciones", "tags_interactuada"])
...mapGetters('productos', ["mostrarProductos"]),
}, },
methods: { methods: {
...mapMutations("ui", ["toggleTags"]), ...mapMutations("ui", ["toggleTags"]),
@ -24,8 +23,8 @@ export default defineComponent({
<template> <template>
<div class="columns ml-3 mr-3" v-else> <div class="columns ml-3 mr-3" v-else>
<productos-container v-if="mostrarProductos" :class="show_chismosa ? 'hide-below-1024' : ''"/> <categorias-container :class="show_chismosa ? 'hide-below-1024' : ''"/>
<categorias-container v-else :class="show_chismosa ? 'hide-below-1024' : ''"/> <productos-container :class="show_chismosa ? 'hide-below-1024' : ''"/>
<devoluciones-modal v-show="show_devoluciones"/> <devoluciones-modal v-show="show_devoluciones"/>
</div> </div>
</template> </template>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="column"> <div v-show="visible" class="column">
<div ref="categorias" <div ref="categorias"
class="columns is-multiline is-mobile" class="columns is-multiline is-mobile"
:class="{ 'align-last-left': isLastRowIncomplete }"> :class="{ 'align-last-left': isLastRowIncomplete }">
@ -28,6 +28,9 @@ export default {
computed: { computed: {
...mapState('productos', ["categorias", "filtro"]), ...mapState('productos', ["categorias", "filtro"]),
...mapState('ui', ["show_chismosa"]), ...mapState('ui', ["show_chismosa"]),
visible() {
return this.filtro === null;
}
}, },
methods: { methods: {
...mapActions('productos', ["seleccionarCategoria"]), ...mapActions('productos', ["seleccionarCategoria"]),

View file

@ -85,7 +85,7 @@ export default {
...mapActions('pedido', ["getGrupoDeCompra", "crearPedido", "elegirPedido"]), ...mapActions('pedido', ["getGrupoDeCompra", "crearPedido", "elegirPedido"]),
...mapMutations("ui", ["toggleTags"]), ...mapMutations("ui", ["toggleTags"]),
async getPedidos(nombre) { async getPedidos(nombre) {
const response = await axios.get('/api/subpedidos',{ const response = await axios.get('/api/subpedidos/',{
params: { params: {
nombre: nombre, nombre: nombre,
grupo_de_compra: this.grupo_de_compra.id, grupo_de_compra: this.grupo_de_compra.id,

View file

@ -7,7 +7,7 @@
:requiere_notas="producto.requiere_notas" :requiere_notas="producto.requiere_notas"
/> />
</td> </td>
<td class="has-text-right">{{ `${producto.total}` }}</td> <td class="has-text-right">{{ `${cantidad(producto.id) * producto.precio}` }}</td>
</tr> </tr>
</template> </template>
<script> <script>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="column"> <div v-show="visible" class="column">
<div ref="productos" <div ref="productos"
class="columns is-multiline is-mobile" class="columns is-multiline is-mobile"
:class="{ 'align-last-left': isLastRowIncomplete }"> :class="{ 'align-last-left': isLastRowIncomplete }">
@ -21,6 +21,9 @@ export default {
computed: { computed: {
...mapState('productos', ["productos", "filtro"]), ...mapState('productos', ["productos", "filtro"]),
...mapState('ui', ["show_chismosa"]), ...mapState('ui', ["show_chismosa"]),
visible() {
return this.filtro !== null;
},
miga: function () { miga: function () {
return { return {
nombre: this.filtro.valor, nombre: this.filtro.valor,

View file

@ -1,10 +0,0 @@
declare global {
interface Window {
Vue: typeof Vue;
Event: InstanceType<typeof Vue>;
axios: typeof axios;
bulmaToast: {
toast: (options: any) => void;
};
}
}

View file

@ -1,5 +0,0 @@
// resources/js/shims-vue.d.ts
declare module '*.vue' {
import Vue from '../../node_modules/vue/dist/vue.esm.js';
export default Vue;
}

View file

@ -1,53 +0,0 @@
export interface ProductoResponse {
id: number,
fila: number,
nombre: string,
precio: number,
categoria: string,
bono: boolean,
created_at: Date,
updated_at: Date,
requiere_notas: boolean,
es_solidario: boolean,
pivot: {
subpedido_id: number,
producto_id: number,
notas: string,
cantidad: number,
total: number,
}
}
export interface Producto {
id: number,
fila: number,
nombre: string,
precio: number,
categoria: string,
bono: boolean,
created_at: Date,
updated_at: Date,
requiere_notas: boolean,
es_solidario: boolean,
notas: string,
cantidad: number,
total: number,
}
export function aplanarProducto(producto: ProductoResponse): Producto {
return {
id: producto.id,
fila: producto.fila,
nombre: producto.nombre,
precio: producto.precio,
categoria: producto.categoria,
bono: producto.bono,
created_at: producto.created_at,
updated_at: producto.updated_at,
requiere_notas: producto.requiere_notas,
es_solidario: producto.es_solidario,
notas: producto.pivot.notas,
cantidad: producto.pivot.cantidad,
total: producto.pivot.total,
};
}

View file

@ -1,8 +1,8 @@
import Vue from '../../../node_modules/vue/dist/vue.esm.js'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import login from "./modules/login";
import admin from "./modules/admin"; import admin from "./modules/admin";
import comisiones from "./modules/comisiones"; import comisiones from "./modules/comisiones";
import login from "./modules/login";
import pedido from "./modules/pedido"; import pedido from "./modules/pedido";
import productos from "./modules/productos"; import productos from "./modules/productos";
import ui from "./modules/ui"; import ui from "./modules/ui";
@ -12,9 +12,9 @@ Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
login,
admin, admin,
comisiones, comisiones,
login,
pedido, pedido,
productos, productos,
ui, ui,

View file

@ -1,36 +1,29 @@
import axios from "axios"; import axios from "axios";
import { AdminState } from "./types";
import { aplanarProducto } from "../../comunes";
const state: AdminState = { const state = {
lastFetch: undefined, lastFetch: null,
grupo_de_compra_id: undefined, grupo_de_compra_id: null,
nombre: undefined, nombre: null,
devoluciones_habilitadas: undefined, devoluciones_habilitadas: null,
pedidos: [], pedidos: null,
total_a_recaudar: undefined, total_a_recaudar: null,
total_sin_devoluciones: undefined, total_sin_devoluciones: null,
total_barrial: undefined, total_barrial: null,
total_devoluciones: undefined, total_devoluciones: null,
total_de_pedido: undefined, total_de_pedido: null,
total_a_transferir: undefined, total_a_transferir: null,
total_transporte: undefined, total_transporte: null,
cantidad_transporte: undefined, cantidad_transporte: null,
saldo: undefined, saldo: null,
}; };
const mutations = { const mutations = {
setState(state: AdminState, { grupo_de_compra }) { setState(state, { grupo_de_compra }) {
const aplanarProductos = (pedido) => {
pedido.productos = pedido.productos.map(p => aplanarProducto(p));
return pedido;
};
state.lastFetch = new Date(); state.lastFetch = new Date();
state.grupo_de_compra_id = grupo_de_compra.id; state.grupo_de_compra_id = grupo_de_compra.id;
state.nombre = grupo_de_compra.nombre; state.nombre = grupo_de_compra.nombre;
state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas; state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas;
state.pedidos = grupo_de_compra.pedidos.map(p => aplanarProductos(p)); state.pedidos = grupo_de_compra.pedidos;
state.total_a_recaudar = grupo_de_compra.total_a_recaudar; state.total_a_recaudar = grupo_de_compra.total_a_recaudar;
state.total_sin_devoluciones = grupo_de_compra.total_sin_devoluciones; state.total_sin_devoluciones = grupo_de_compra.total_sin_devoluciones;
state.total_barrial = grupo_de_compra.total_barrial; state.total_barrial = grupo_de_compra.total_barrial;
@ -48,8 +41,8 @@ const mutations = {
const actions = { const actions = {
async getGrupoDeCompra({ commit }) { async getGrupoDeCompra({ commit }) {
const { data } = await axios.get('/user/grupo_de_compra'); const response = await axios.get('/user/grupo_de_compra');
commit('setState', data); commit('setState', response.data);
}, },
async setAprobacionPedido({ commit, dispatch }, { pedido_id, aprobacion }){ async setAprobacionPedido({ commit, dispatch }, { pedido_id, aprobacion }){
await axios.post("/api/admin/subpedidos/" + pedido_id + "/aprobacion", { aprobacion: aprobacion }); await axios.post("/api/admin/subpedidos/" + pedido_id + "/aprobacion", { aprobacion: aprobacion });
@ -66,7 +59,7 @@ const actions = {
const getters = { const getters = {
grupoDeCompraDefinido() { grupoDeCompraDefinido() {
return state.lastFetch !== undefined; return state.lastFetch !== null;
}, },
barrio() { barrio() {
return state.nombre?.replace('_admin','') ?? ''; return state.nombre?.replace('_admin','') ?? '';

View file

@ -1,33 +0,0 @@
import { Producto } from "../../comunes";
export interface AdminState extends Barrio {
lastFetch?: Date
}
export interface Barrio {
grupo_de_compra_id?: number,
nombre?: string,
devoluciones_habilitadas?: boolean,
pedidos: Pedido[],
total_a_recaudar?: number,
total_sin_devoluciones?: number,
total_barrial?: number,
total_devoluciones?: number,
total_de_pedido?: number,
total_a_transferir?: number,
total_transporte?: number,
cantidad_transporte?: number,
saldo?: number,
}
export interface Pedido {
id: number,
nombre: string,
productos: Producto[],
aprobado: boolean,
total: number,
total_transporte: number,
cantidad_transporte: number,
total_sin_devoluciones: number,
devoluciones_total: number,
devoluciones_notas: string
}

View file

@ -1,14 +1,15 @@
import axios from "axios"; import axios from "axios";
import { ComisionesState } from "./types";
const state: ComisionesState = { const state = {
lastFetch: undefined, lastFetch: undefined,
grupos_de_compra: [], grupos_de_compra: [],
parametros: [], parametros: [],
grupo_de_compra_actual: undefined,
grupo_de_compra_nuevo: false,
}; };
const mutations = { const mutations = {
setGruposDeCompra(state: ComisionesState, { data }) { setGruposDeCompra(state, { data }) {
state.grupos_de_compra = data; state.grupos_de_compra = data;
state.lastFetch = new Date(); state.lastFetch = new Date();
}, },
@ -21,17 +22,36 @@ const mutations = {
actualizarParametro(state, { parametro_id, valor }) { actualizarParametro(state, { parametro_id, valor }) {
state.parametros.find(p => p.id === parametro_id).valor = valor; state.parametros.find(p => p.id === parametro_id).valor = valor;
}, },
setSaldo(state: ComisionesState, { gdc_id, saldo }) { setSaldo(state, { gdc_id, saldo }) {
const barrio = state.grupos_de_compra.find(gdc => gdc.id === gdc_id); const barrio = state.grupos_de_compra.find(gdc => gdc.id === gdc_id);
const i = state.grupos_de_compra.indexOf(barrio!); const i = state.grupos_de_compra.indexOf(barrio);
state.grupos_de_compra[i].saldo = saldo; state.grupos_de_compra[i].saldo = saldo;
}, },
seleccionarGrupoDeCompra(state, { grupo_de_compra }) {
state.grupo_de_compra_actual = grupo_de_compra;
},
actualizarGrupoDeCompra(state, { gdc_id, nombre, region }) {
const barrio = state.grupos_de_compra.find(gdc => gdc.id === gdc_id);
const i = state.grupos_de_compra.indexOf(barrio);
if (nombre)
state.grupos_de_compra[i].nombre = nombre;
if (region)
state.grupos_de_compra[i].region = region;
},
grupoDeCompraNuevo(state) {
state.grupo_de_compra_actual = {};
state.grupo_de_compra_nuevo = true;
},
agregarGrupoDeCompra(state, grupo_de_compra) {
state.grupos_de_compra.push(grupo_de_compra);
state.grupo_de_compra_nuevo = false;
},
}; };
const actions = { const actions = {
async getGruposDeCompra({ commit }) { async getGruposDeCompra({ commit }) {
const { data } = await axios.get('/api/grupos-de-compra/saldos'); const response = await axios.get('/api/grupos-de-compra/saldos');
commit('setGruposDeCompra', data); commit('setGruposDeCompra', response.data);
}, },
async getParametros({ commit }) { async getParametros({ commit }) {
const response = await axios.get('/api/parametros'); const response = await axios.get('/api/parametros');
@ -75,6 +95,33 @@ const actions = {
dispatch("ui/error", { error: error }, { root: true }); dispatch("ui/error", { error: error }, { root: true });
} }
}, },
async modificarBarrio({ commit, dispatch }, { gdc_id, nombre, region, passBarrio, passAdmin, passOllas }) {
try {
const data = {
nombre: nombre,
region: region,
passBarrio: passBarrio,
passAdmin: passAdmin,
passOllas: passOllas
};
await axios.put(`/comisiones/grupos-de-compra/${gdc_id}`, data);
commit('actualizarGrupoDeCompra', { gdc_id: gdc_id, nombre: nombre, region: region });
dispatch("ui/toast", { mensaje: 'Barrio modificado con éxito'}, { root: true });
} catch (error) {
console.log(error);
dispatch("ui/error", { error: error }, { root: true });
}
},
async crearBarrio({ commit, dispatch }, data) {
try {
const response = await axios.post(`/comisiones/grupos-de-compra/`, data);
commit('agregarGrupoDeCompra', response.data.data);
dispatch("ui/toast", { mensaje: 'Barrio agregado con éxito'}, { root: true });
} catch (error) {
console.log(error);
dispatch("ui/error", { error: error }, { root: true });
}
}
}; };
const getters = { const getters = {

View file

@ -1,10 +0,0 @@
export interface ComisionesState {
lastFetch?: Date,
grupos_de_compra: Barrio[],
}
export interface Barrio {
id: number,
nombre: string,
saldo: number,
}

View file

@ -1,23 +1,22 @@
import axios from "axios"; import axios from "axios";
import { Estilos, LoginState, OpcionLogin, Textos, UrlRol, UserRol } from "./types";
const state: LoginState = { const state = {
regiones: [], regiones: null,
grupos_de_compra: [], grupos_de_compra: null,
region_elegida: undefined, region_elegida: null,
grupo_de_compra_elegido: undefined, grupo_de_compra_elegido: null,
rol: undefined, rol: null,
}; };
const mutations = { const mutations = {
setRegiones(state, { regiones }): void { setRegiones(state, { regiones }) {
state.regiones = regiones; state.regiones = regiones;
}, },
setRegionYBarrios(state, { region, grupos_de_compra }): void { setRegionYBarrios(state, { region, grupos_de_compra }) {
state.region_elegida = region; state.region_elegida = region;
state.grupos_de_compra = grupos_de_compra; state.grupos_de_compra = grupos_de_compra;
}, },
selectGrupoDeCompra(state, { grupo_de_compra }): void { selectGrupoDeCompra(state, { grupo_de_compra }) {
state.grupo_de_compra_elegido = grupo_de_compra; state.grupo_de_compra_elegido = grupo_de_compra;
}, },
setRol(state, { rol }) { setRol(state, { rol }) {
@ -26,29 +25,29 @@ const mutations = {
}; };
const actions = { const actions = {
async getRegiones({ commit }): Promise<void> { async getRegiones({ commit }) {
const { data } = await axios.get("/api/regiones"); const response = await axios.get("/api/regiones");
commit('setRegiones', { regiones: data }); commit('setRegiones', { regiones: response.data });
}, },
async selectRegion({ commit }, { region }): Promise<void> { async selectRegion({ commit }, { region }) {
const { data } = await axios.get(`/api/regiones/${region}`); const response = await axios.get(`/api/regiones/${region}`);
commit('setRegionYBarrios', { region: region, grupos_de_compra: data }); commit('setRegionYBarrios', { region: region, grupos_de_compra: response.data });
}, },
async getRol({ commit }): Promise<void> { async getRol({ commit }) {
const { data }: UserRol = await axios.get("/user/rol"); const response = await axios.get("/user/rol");
commit('setRol', { rol: data.rol }); commit('setRol', { rol: response.data.rol });
} }
}; };
const getters = { const getters = {
urlRol(): UrlRol { urlRol() {
let split = window.location.pathname let split = window.location.pathname
.replace('login', '') .replace('login', '')
.split('/') .split('/')
.filter(x => x.length); .filter(x => x.length);
return split[0] as UrlRol ?? 'pedido'; return split[0] ?? 'pedido';
}, },
textos(): Textos { textos() {
let rol = getters.urlRol(); let rol = getters.urlRol();
switch (rol) { switch (rol) {
case 'pedido': case 'pedido':
@ -87,7 +86,7 @@ const getters = {
throw new Error("Url inválida"); throw new Error("Url inválida");
} }
}, },
estilos(): Estilos { estilos() {
let rol = getters.urlRol(); let rol = getters.urlRol();
switch (rol) { switch (rol) {
case 'pedido': case 'pedido':
@ -104,8 +103,8 @@ const getters = {
}; };
case 'comisiones': case 'comisiones':
return { return {
fondo: "has-background-dark", fondo: "has-background-grey",
texto: "has-text-danger-light", texto: "has-text-white",
botones: "danger-dark-button", botones: "danger-dark-button",
}; };
case 'ollas': case 'ollas':
@ -118,7 +117,7 @@ const getters = {
throw new Error("Url inválida"); throw new Error("Url inválida");
} }
}, },
opcionesLogin(): OpcionLogin[] { opcionesLogin() {
let rol = getters.urlRol(); let rol = getters.urlRol();
switch (rol) { switch (rol) {
case 'pedido': case 'pedido':

View file

@ -1,43 +0,0 @@
export interface LoginState {
regiones: string[],
grupos_de_compra: Barrio[],
region_elegida?: string,
grupo_de_compra_elegido?: Barrio,
rol?: Rol,
}
export interface Textos {
titulo: string,
subtitlo: string,
password: string,
ayuda: string,
label: string,
}
export interface Estilos {
fondo: string,
texto: string,
botones: string,
}
export interface OpcionLogin {
nombre: string,
href: string,
}
export interface Barrio {
id: number,
nombre: string,
region: string,
created_at: Date,
updated_at: Date,
devoluciones_habilitadas: boolean,
saldo: number,
}
export interface UserRol {
rol: Rol
}
export type UrlRol = 'pedido' | 'admin' | 'comisiones' | 'ollas';
export type Rol = 'barrio' | 'admin_barrio' | 'comision' | 'ollas';

View file

@ -1,32 +1,30 @@
import axios from "axios"; import axios from "axios";
import { PedidoState } from "./types";
import { aplanarProducto } from "../../comunes";
const state: PedidoState = { const state = {
lastFetch: undefined, lastFetch: null,
grupo_de_compra: undefined, grupo_de_compra: null,
pedido_id: undefined, pedido_id: 0,
nombre: undefined, nombre: "",
productos: [], productos: [],
aprobado: undefined, aprobado: false,
total: undefined, total: 0,
total_transporte: undefined, total_transporte: 0,
cantidad_transporte: undefined, cantidad_transporte: 0,
total_sin_devoluciones: undefined, total_sin_devoluciones: 0,
devoluciones_total: undefined, devoluciones_total: 0,
devoluciones_notas: undefined, devoluciones_notas: "",
cantidad_de_ollas: undefined, cantidad_de_ollas: 0,
}; };
const mutations = { const mutations = {
setGrupoDeCompra(state: PedidoState, grupo_de_compra) { setGrupoDeCompra(state, grupo_de_compra) {
state.grupo_de_compra = grupo_de_compra; state.grupo_de_compra = grupo_de_compra;
}, },
setPedido(state: PedidoState, pedido) { setPedido(state, pedido) {
state.lastFetch = new Date(); state.lastFetch = new Date();
state.pedido_id = pedido.id; state.pedido_id = pedido.id;
state.nombre = pedido.nombre; state.nombre = pedido.nombre;
state.productos = pedido.productos.map(p => aplanarProducto(p)); state.productos = pedido.productos;
state.aprobado = pedido.aprobado; state.aprobado = pedido.aprobado;
state.total = Number.parseFloat(pedido.total.replace(',','')); state.total = Number.parseFloat(pedido.total.replace(',',''));
state.total_transporte = Number.parseInt(pedido.total_transporte?.replace(',','')); state.total_transporte = Number.parseInt(pedido.total_transporte?.replace(',',''));
@ -49,18 +47,18 @@ const mutations = {
delete state.devoluciones_total; delete state.devoluciones_total;
delete state.devoluciones_notas; delete state.devoluciones_notas;
}, },
reset(state: PedidoState) { reset(state) {
state.lastFetch = undefined; state.lastFetch = null;
state.pedido_id = undefined; state.pedido_id = null;
state.nombre = undefined; state.nombre = null;
state.productos = []; state.productos = null;
state.aprobado = undefined; state.aprobado = null;
state.total = undefined; state.total = null;
state.total_transporte = undefined; state.total_transporte = null;
state.cantidad_transporte = undefined; state.cantidad_transporte = null;
state.total_sin_devoluciones = undefined; state.total_sin_devoluciones = null;
state.devoluciones_total = undefined; state.devoluciones_total = null;
state.devoluciones_notas = undefined; state.devoluciones_notas = null;
}, },
setCantidadOllas(state, { cantidad }) { setCantidadOllas(state, { cantidad }) {
if (cantidad >= 0) if (cantidad >= 0)
@ -70,8 +68,8 @@ const mutations = {
const actions = { const actions = {
async getGrupoDeCompra({ commit }) { async getGrupoDeCompra({ commit }) {
const { data } = await axios.get('/user/grupo_de_compra'); const response = await axios.get('/user/grupo_de_compra');
commit('setGrupoDeCompra', data.grupo_de_compra); commit('setGrupoDeCompra', response.data.grupo_de_compra);
}, },
async guardarSesion(_, { pedido_id }) { async guardarSesion(_, { pedido_id }) {
const body = { id: pedido_id }; const body = { id: pedido_id };
@ -79,35 +77,31 @@ const actions = {
}, },
async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id, tipo_id }) { async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id, tipo_id }) {
const body = { nombre, grupo_de_compra_id, tipo_id }; const body = { nombre, grupo_de_compra_id, tipo_id };
const { data } = await axios.post("/api/subpedidos", body); const response = await axios.post("/api/subpedidos", body);
dispatch("guardarSesion", { pedido_id: data.data.id}); dispatch("guardarSesion", { pedido_id: response.data.data.id});
commit('setPedido', data.data); commit('setPedido', response.data.data);
}, },
async elegirPedido({ commit, dispatch }, { pedido_id }) { async elegirPedido({ commit, dispatch }, { pedido_id }) {
const { data } = await axios.get(`/api/subpedidos/${pedido_id}`); const response = await axios.get(`/api/subpedidos/${pedido_id}`);
dispatch("guardarSesion", { pedido_id: pedido_id}) const body = { pedido_id: pedido_id};
commit('setPedido', data.data); dispatch("guardarSesion", body)
commit('setPedido', response.data.data);
}, },
async modificarChismosa({ commit, dispatch }, { producto_id, cantidad, notas }) { async modificarChismosa({ commit, dispatch }, { producto_id, cantidad, notas }) {
const body = { cantidad: cantidad, producto_id: producto_id, notas: notas };
try { try {
const { data } = await axios.post("/api/subpedidos/" + state.pedido_id + "/sync", { const response = await axios.post("/api/subpedidos/" + state.pedido_id + "/sync", body);
cantidad: cantidad, commit('setPedido', response.data.data);
producto_id: producto_id,
notas: notas,
});
commit('setPedido', data.data);
dispatch("ui/toast", { mensaje: 'Pedido modificado con éxito' }, { root: true }); dispatch("ui/toast", { mensaje: 'Pedido modificado con éxito' }, { root: true });
} catch (error) { } catch (error) {
dispatch("ui/error", { error: error }, { root: true }); dispatch("ui/error", { error: error }, { root: true });
} }
}, },
async modificarDevoluciones({ commit, dispatch }, { monto, notas }) { async modificarDevoluciones({ commit, dispatch }, { monto, notas }) {
const body = { total: monto, notas: notas };
try { try {
const { data } = await axios.post("api/subpedidos/" + state.pedido_id + "/sync_devoluciones", { const response = await axios.post("api/subpedidos/" + state.pedido_id + "/sync_devoluciones", body);
total: monto, commit('setPedido', response.data.data);
notas: notas,
});
commit('setPedido', data.data);
dispatch("ui/toast", { mensaje: 'Devoluciones modificadas con éxito' }, { root: true }); dispatch("ui/toast", { mensaje: 'Devoluciones modificadas con éxito' }, { root: true });
} catch (error) { } catch (error) {
dispatch("ui/error", { error: error }, { root: true }); dispatch("ui/error", { error: error }, { root: true });
@ -127,16 +121,16 @@ const actions = {
const getters = { const getters = {
pedidoDefinido() { pedidoDefinido() {
return state.lastFetch !== undefined; return state.lastFetch !== null;
}, },
enChismosa() { enChismosa() {
return ((producto_id) => state.productos.some(p => p.id === producto_id)); return ((producto_id) => state.productos.some(p => p.id === producto_id));
}, },
cantidad() { cantidad() {
return ((producto_id) => state.productos.find(p => p.id === producto_id)?.cantidad ?? 0); return ((producto_id) => state.productos.find(p => p.id === producto_id)?.pivot.cantidad ?? 0);
}, },
notas() { notas() {
return ((producto_id) => state.productos.find(p => p.id === producto_id)?.notas ?? ""); return ((producto_id) => state.productos.find(p => p.id === producto_id)?.pivot.notas ?? "");
} }
} }

View file

@ -1,22 +0,0 @@
import { Producto } from "../../comunes";
export interface PedidoState {
lastFetch?: Date,
grupo_de_compra?: Barrio,
pedido_id?: number,
nombre?: string,
productos: Producto[],
aprobado?: boolean,
total?: number,
total_transporte?: number,
cantidad_transporte?: number,
total_sin_devoluciones?: number,
devoluciones_total?: number,
devoluciones_notas?: string,
}
export interface Barrio {
id: number,
nombre: string,
devoluciones_habilitadas: boolean,
}

View file

@ -1,23 +1,22 @@
import axios from "axios"; import axios from "axios";
import { ProductosState } from "./types";
const state: ProductosState = { const state = {
lastFetch: undefined, lastFetch: null,
categorias: [], categorias: [],
productos: [], productos: [],
filtro: undefined, filtro: null,
}; };
const mutations = { const mutations = {
setCategorias(state: ProductosState, categorias) { setCategorias(state, categorias) {
state.lastFetch = new Date(); state.lastFetch = new Date();
state.categorias = categorias; state.categorias = categorias;
}, },
setProductos(state: ProductosState, productos) { setProductos(state, productos) {
state.lastFetch = new Date(); state.lastFetch = new Date();
state.productos = productos; state.productos = productos;
}, },
setFiltro(state: ProductosState, filtro) { setFiltro(state, filtro) {
state.lastFetch = new Date(); state.lastFetch = new Date();
state.filtro = filtro; state.filtro = filtro;
}, },
@ -29,29 +28,23 @@ const actions = {
dispatch('getProductos'); dispatch('getProductos');
}, },
async getCategorias({ commit }) { async getCategorias({ commit }) {
const { data } = await axios.get('api/categorias'); const response = await axios.get('api/categorias');
commit('setCategorias', data); commit('setCategorias', response.data);
}, },
async getProductos({ commit }) { async getProductos({ commit }) {
const { data } = await axios.get("/api/productos"); const response = await axios.get("/api/productos");
commit('setFiltro', undefined); commit('setFiltro', null);
commit('setProductos', data.data); commit('setProductos', response.data.data);
}, },
async seleccionarCategoria({ dispatch }, { categoria }) { async seleccionarCategoria({ dispatch }, { categoria }) {
dispatch('filtrarProductos', { filtro: "categoria", valor: categoria }); dispatch('filtrarProductos', { filtro: "categoria", valor: categoria });
}, },
async filtrarProductos({ commit }, { filtro, valor }) { async filtrarProductos({ commit }, { filtro, valor }) {
const { data } = await axios.get("/api/productos", { const response = await axios.get("/api/productos", {
params: { [filtro]: valor } params: { [filtro]: valor }
}); });
commit('setFiltro', { clave: filtro, valor: valor }); commit('setFiltro', { clave: filtro, valor: valor });
commit('setProductos', data.data); commit('setProductos', response.data.data);
}
};
const getters = {
mostrarProductos() {
return state.filtro !== undefined;
} }
}; };
@ -60,5 +53,4 @@ export default {
state, state,
mutations, mutations,
actions, actions,
getters,
}; };

View file

@ -1,21 +0,0 @@
export interface ProductosState {
lastFetch?: Date,
categorias: string[],
productos: Producto[],
filtro?: Filtro,
}
export interface Producto {
id: number,
nombre: string,
precio: number,
categoria: string,
economia_solidaria: boolean,
nacional: boolean,
requiere_notas: boolean,
}
export interface Filtro {
clave: string,
valor: string,
}

View file

@ -1,13 +1,12 @@
import { UiState } from "./types"; const state = {
const state: UiState = {
show_chismosa: false, show_chismosa: false,
show_devoluciones: false, show_devoluciones: false,
show_tags: true, show_tags: true,
show_modal_barrio: false,
burger_activa: false, burger_activa: false,
tags_interactuada: false, tags_interactuada: false,
migas: [{ nombre: 'Pedidos', action: 'pedido/resetear' }], migas: [{ nombre: 'Pedidos', action: 'pedido/resetear' }],
canasta_actual: undefined, canasta_actual: null,
}; };
const mutations = { const mutations = {
@ -20,6 +19,9 @@ const mutations = {
toggleDevoluciones(state) { toggleDevoluciones(state) {
state.show_devoluciones = !state.show_devoluciones; state.show_devoluciones = !state.show_devoluciones;
}, },
toggleModalBarrio(state) {
state.show_modal_barrio = !state.show_modal_barrio;
},
toggleTags(state, manual) { toggleTags(state, manual) {
state.tags_interactuada = manual; state.tags_interactuada = manual;
state.show_tags = !state.show_tags; state.show_tags = !state.show_tags;
@ -31,7 +33,7 @@ const mutations = {
state.migas.push(miga); state.migas.push(miga);
}, },
popUltimaBusqueda(state) { popUltimaBusqueda(state) {
if (!state.migas.at(-1)?.action) if (!state.migas.at(-1).action)
state.migas.pop(); state.migas.pop();
}, },
reset(state) { reset(state) {
@ -45,8 +47,8 @@ const mutations = {
const actions = { const actions = {
async getCanastaActual({ commit }) { async getCanastaActual({ commit }) {
const { data } = await axios.get('/api/canasta-actual'); const response = await axios.get('/api/canasta-actual');
commit("setCanastaActual", { canasta: data }); commit("setCanastaActual", { canasta: response.data });
}, },
clickMiga({ dispatch }, { miga }) { clickMiga({ dispatch }, { miga }) {
let dropWhile = (array, pred) => { let dropWhile = (array, pred) => {

View file

@ -1,20 +0,0 @@
export interface UiState {
show_chismosa: boolean,
show_devoluciones: boolean,
show_tags: boolean,
tags_interactuada: boolean,
migas: Miga[],
canasta_actual?: DatosCanasta,
}
export interface Miga {
nombre: string,
action: string,
arguments?: { [key: string]: string },
}
export interface DatosCanasta {
nombre: string,
fecha: Date,
}

View file

@ -4,8 +4,8 @@
// Variables // Variables
@import 'variables'; @import 'variables';
@import '../../node_modules/bulma/bulma.sass'; @import 'bulma';
@import '../../node_modules/bulma-switch/src/sass/index.sass'; @import '~bulma-switch';
html, body { html, body {
overflow-x: hidden; overflow-x: hidden;

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Pedidos del MPS') }}</title> <title>{{ config('app.name', 'Pedidos del MPS') }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
@vite(['resources/sass/app.scss', 'resources/js/app.ts']) <link rel="stylesheet" href="{{ mix('css/app.css') }}">
<script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script>
</head> </head>
<body> <body>
@ -15,5 +15,6 @@
<app-login></app-login> <app-login></app-login>
</form> </form>
</div> </div>
<script src="{{ mix('js/app.js') }}" defer></script>
</body> </body>
</html> </html>

View file

@ -12,7 +12,7 @@
<!-- Fonts --> <!-- Fonts -->
<script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script>
@vite(['resources/sass/app.scss', 'resources/js/app.ts']) <link rel="stylesheet" href="{{ mix('css/app.css') }}">
@yield('stylesheets') @yield('stylesheets')
</head> </head>
<body class="has-navbar-fixed-top"> <body class="has-navbar-fixed-top">
@ -28,6 +28,8 @@
@yield('content') @yield('content')
</main> </main>
</div> </div>
<!-- Scripts -->
<script src="{{ mix('js/app.js') }}" defer></script>
@yield('scripts') @yield('scripts')
</body> </body>
</html> </html>

View file

@ -26,31 +26,30 @@ Route::get('/', 'RouteController@home')->name('home');
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/user', 'UserController@user')->name('user'); Route::get('/user', 'UserController@user')->name('user');
Route::get('/user/rol', 'UserController@rol')->name('user.rol'); Route::get('/user/rol', 'UserController@rol')->name('user.rol');
Route::get('/user/grupo_de_compra', 'UserController@grupoDeCompra'); Route::get('/user/grupo_de_compra', 'UserController@grupoDeCompra')->name('user.grupoDeCompra');
}); });
Route::middleware(['auth', 'role:barrio'])->group(function() { Route::middleware(['auth', 'role:barrio'])->group(function() {
Route::get('/pedido', 'RouteController@main')->name('pedido'); Route::get('/pedido', 'RouteController@main')->name('pedido');
Route::get('/pedido/sesion', 'SessionController@fetch'); Route::get('/pedido/sesion', 'SessionController@fetch')->name('pedido.sesion');
Route::post('/pedido/sesion', 'SessionController@store'); Route::post('/pedido/sesion', 'SessionController@store')->name('pedido.sesion.store');
Route::delete('/pedido/sesion', 'SessionController@destroy'); Route::delete('/pedido/sesion', 'SessionController@destroy')->name('pedido.sesion.destroy');
}); });
Route::get('/admin/login', 'AdminController@show')->name('admin.login'); Route::get('/admin/login', 'AdminController@show')->name('admin.login');
Route::middleware(['auth', 'role:admin_barrio'])->group(function () { Route::middleware(['auth', 'role:admin_barrio'])->group(function () {
Route::get('/admin', 'RouteController@main')->name('admin'); Route::get('/admin', 'RouteController@main')->name('admin');
Route::get('/admin/exportar-planillas-a-pdf/{gdc}', 'AdminController@exportarPedidosAPdf')->name('admin.pedidos.pdf');
Route::get('/admin/exportar-planillas-a-pdf/{gdc}', 'AdminController@exportarPedidosAPdf'); Route::get('/admin/exportar-pedido-a-csv/{gdc}', 'AdminController@exportarPedidoACSV')->name('admin.pedidoBarrial.csv');
Route::get('/admin/exportar-pedido-a-csv/{gdc}', 'AdminController@exportarPedidoACSV'); Route::get('/admin/exportar-pedido-con-nucleos-a-csv/{gdc}', 'AdminController@exportarPedidoConNucleosACSV')->name('admin.pedidoPorNucleos.csv');
Route::get('/admin/exportar-pedido-con-nucleos-a-csv/{gdc}', 'AdminController@exportarPedidoConNucleosACSV');
}); });
Route::get('/comisiones/login', 'ComisionesController@show')->name('comisiones.login'); Route::get('/comisiones/login', 'ComisionesController@show')->name('comisiones.login');
Route::middleware(['auth', 'role:comision'])->group( function() { Route::middleware(['auth', 'role:comision'])->group( function() {
Route::get('/comisiones', 'RouteController@main')->name('comisiones'); Route::get('/comisiones', 'RouteController@main')->name('comisiones');
Route::get('/comisiones/pedidos/descargar', 'ComisionesController@descargarPedidos')->name('comisiones.pedidos.descargar'); Route::get('/comisiones/pedidos', 'ComisionesController@descargarPedidos')->name('comisiones.pedidos');
Route::get('/comisiones/pedidos/notas', 'ComisionesController@descargarNotas')->name('comisiones.pedidos.notas'); Route::get('/comisiones/pedidos/notas', 'ComisionesController@descargarNotas')->name('comisiones.pedidos.notas');
Route::get('/comisiones/pedidos/pdf', 'ComisionesController@pdf')->name('comisiones.pedidos.pdf'); 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/pedidos/ollas', 'ComisionesController@descargarPedidosDeOllas')->name('comisiones.pedidos.ollas');
@ -58,13 +57,18 @@ Route::middleware(['auth', 'role:comision'])->group( function() {
Route::post('/comisiones/canasta', 'ComisionesController@cargarCanasta')->name('comisiones.canasta'); Route::post('/comisiones/canasta', 'ComisionesController@cargarCanasta')->name('comisiones.canasta');
Route::get('/comisiones/saldos/ejemplo', 'ComisionesController@descargarSaldosEjemplo')->name('comisiones.saldos.ejemplo'); Route::get('/comisiones/saldos/ejemplo', 'ComisionesController@descargarSaldosEjemplo')->name('comisiones.saldos.ejemplo');
Route::post('/comisiones/saldos', 'ComisionesController@cargarSaldos')->name('comisiones.saldos'); Route::post('/comisiones/saldos', 'ComisionesController@cargarSaldos')->name('comisiones.saldos');
Route::put('/comisiones/parametros/{parametro_id}', 'ComisionesController@modificarParametros'); Route::put('/comisiones/parametros/{parametro_id}', 'ComisionesController@modificarParametros')->name('comisiones.parametros.modificar');
Route::post('/comisiones/grupos-de-compra/', 'ComisionesController@crearGrupoDeCompra')->name('comisiones.gruposDeComrpa.crear');
Route::put('/comisiones/grupos-de-compra/{grupo_de_compra_id}', 'ComisionesController@modificarGrupoDeCompra')->name('comisiones.gruposDeComrpa.modificar');
Route::get('/comisiones/pedidos/{gdc}', 'AdminController@exportarPedidoACSV')->name('comisiones.pedidos.descargar.grupoDeCompra');
Route::get('/comisiones/pedidos/{gdc}/ollas', 'AdminController@exportarPedidoOllasACSV')->name('comisiones.pedidos.ollas.grupoDeCompra');
Route::get('/comisiones/pedidos/{gdc}/notas', 'AdminController@exportarNotasACSV')->name('comisiones.pedidos.notas.grupoDeCompra');
}); });
Route::get('/ollas/login', 'OllasController@show')->name('ollas.login'); Route::get('/ollas/login', 'OllasController@show')->name('ollas.login');
Route::middleware(['auth', 'role:ollas'])->prefix('ollas')->group( function() { Route::middleware(['auth', 'role:ollas'])->prefix('ollas')->group( function() {
Route::get('/', 'RouteController@main')->name('ollas'); Route::get('/', 'RouteController@main')->name('ollas');
Route::get('/{gdc}','OllasController@pedido'); Route::get('/{gdc}','OllasController@pedido')->name('ollas.pedido');
Route::put('/{gdc}/cantidad','OllasController@actualizarCantidadOllas'); Route::put('/{gdc}/cantidad','OllasController@actualizarCantidadOllas')->name('ollas.actualizarCantidadOllas');
}); });

View file

@ -1,17 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": ["esnext", "dom"],
"jsx": "preserve",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"skipLibCheck": true,
"types": ["vite/client"]
},
"include": ["resources/js/**/*", "resources/**/*.vue"]
}

View file

@ -1,23 +0,0 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue2';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
server: {
host: '0.0.0.0',
port: 5173,
},
plugins: [
laravel({
input: ['resources/js/app.ts', 'resources/sass/app.scss'],
refresh: true,
}),
vue(),
],
resolve: {
alias: {
'@': '/resources/js',
'vue$': 'vue/dist/vue.esm.js',
},
},
});

16
webpack.mix.js vendored Normal file
View file

@ -0,0 +1,16 @@
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.version();