Compare commits

...

58 commits

Author SHA1 Message Date
ale
210c91f3a8 Mejoras estéticas y cambios cuando el pedido está aprobado 2025-06-10 19:09:21 -03:00
ale
2ee7fca584 Cambio por null check 2025-06-10 18:06:43 -03:00
ale
258bccf59b Reseteando ui 2025-05-29 16:51:08 -03:00
ale
ca42526a62 Hover dedito 2025-05-29 16:44:25 -03:00
ale
f484bfff79 Mejoras de estilo de chismosa para tablet y celular 2025-05-29 15:26:55 -03:00
ale
9384d09ff6 Arreglado scroll horizontal en celular y tablet 2025-05-29 15:24:39 -03:00
ale
f460cdd6ce Mejoras de estilo 2025-05-27 23:54:01 -03:00
ale
4b4a284914 Cambio en layout de productos 2025-05-27 23:38:37 -03:00
ale
a641247748 Cambio dinámico de tamaño y alineación 2025-05-27 23:32:27 -03:00
ale
82a518fa1d Mejoras de estilo 2025-05-27 22:28:40 -03:00
ale
deaa65d857 Ya no usa lodash 2025-05-27 20:42:45 -03:00
ale
4af9e53a50 Quitadas dependencias inncesarias y eliminadio bootstrap.js que no se usaba para nada 2025-05-27 20:42:24 -03:00
ale
8d64d85b8b Impidiendo cantidades invalidas 2025-05-27 20:38:12 -03:00
ale
2c720faca7 Cambiado margen derecho 2025-05-27 19:27:19 -03:00
ale
85fa9f1e9b formato 2025-05-27 19:21:43 -03:00
ale
c8d1969352 Migas simplificadas por renderizarse solo despues de que el pedido se defina 2025-05-27 19:21:20 -03:00
ale
1eb77be1d0 Columna definida afuera 2025-05-27 19:19:53 -03:00
ale
3949cf3400 Quitada chismosa 2025-05-27 19:19:33 -03:00
ale
973d099bf1 Layout de columnas para que el cartel de pedido aprobado no desplace la chismosa 2025-05-27 19:19:23 -03:00
ale
802d4d0c0b No se stackean las busquedas 2025-05-27 18:29:44 -03:00
ale
134ed0cd22 Agregadas acciones con argumentos a migas + accion para miga de categoria 2025-05-27 18:21:19 -03:00
ale
2bfcf59f3e Eliminada store de barrio, logica de pedidos existentes pasada a pedido select y datos de grupo de compra pasados a store de pedido 2025-05-27 18:14:00 -03:00
ale
4e197204a9 Agregado nombre 2025-05-27 15:49:24 -03:00
ale
2075bcab0f Usando titulo genérico para login 2025-05-27 15:25:56 -03:00
ale
c0d8392f6e Agregado metodo para titulo genérico de login 2025-05-27 15:25:47 -03:00
ale
ef9a296f5c Simplificado v-if 2025-05-26 18:50:18 -03:00
ale
bc55b4c34f Mejores imports 2025-05-26 18:05:48 -03:00
ale
e779111856 Formato 2025-05-26 18:05:38 -03:00
ale
d6990f8c88 Eliminada tabla bonos 2025-05-26 18:05:17 -03:00
ale
8eb385c67e Formato 2025-05-26 18:00:01 -03:00
ale
2970982c77 SubpedidoSelect mergeado con PedidoSelectSection en PedidoSelect y Pedido renombrado a Canasta 2025-05-26 17:59:57 -03:00
ale
9e63c83126 Quitados comentarios 2025-05-26 17:58:25 -03:00
ale
439f69a30c Agregado total sin devoluciones a resource de grupo de compra 2025-05-26 17:25:35 -03:00
ale
d0ce8e8e23 Navmigas movida a pedidos-body 2025-05-26 17:03:49 -03:00
ale
3b858f5b2b Quitada navbar de app blade, logout form escondida 2025-05-26 17:02:34 -03:00
ale
0512ea9ab2 Agregada miga para volver al select de pedidos 2025-05-26 16:46:39 -03:00
ale
8488d9d6c5 Movida logica de obtener sesion a subpedido select + simplificado elegir pedido + mejores responses de session controller 2025-05-26 16:37:19 -03:00
ale
354045c5df Borrada linea no usada 2025-05-24 14:47:19 -03:00
ale
ae1f8673e7 Metodo para resetear pedido 2025-05-24 14:46:46 -03:00
ale
4f74bf38f9 Metodo y ruta para borrar sesion 2025-05-24 14:46:15 -03:00
ale
8a2539f207 Quitada import no usado 2025-05-24 14:18:05 -03:00
ale
04673b1754 Quitada ruta y metodo no usados 2025-05-24 13:52:19 -03:00
ale
88af33d998 Agregado metodo y ruta para obtener categorias 2025-05-24 13:47:56 -03:00
ale
23af6ccd61 Borrada ruta no usada 2025-05-23 18:30:54 -03:00
ale
777f442118 Metodo toggleDevoluciones en controller 2025-05-23 18:28:09 -03:00
ale
7cae00e613 Quitado import no usado 2025-05-23 18:27:51 -03:00
ale
5a61ca46c5 Quitado password de grupo de compra 2025-05-23 18:27:33 -03:00
ale
e379825fd9 Usando ruta nueva 2025-05-23 18:14:51 -03:00
ale
6b2da42160 v-if simplificado 2025-05-23 18:14:11 -03:00
ale
48cf57a6d8 Agregada ruta y metodo para obtener barrios de una region 2025-05-23 17:56:46 -03:00
ale
aa545ff82a Eliminadas views no usadas 2025-05-23 17:40:05 -03:00
ale
b29d63ed4d Quitado import no usado 2025-05-23 17:32:38 -03:00
ale
1e91f443a7 Rutas de sesion movidas a /pedido 2025-05-23 17:16:19 -03:00
ale
6782b7f675 Limpiadas rutas no usadas 2025-05-23 17:12:48 -03:00
ale
85b3f1dd0f Agregados fillable faltantes + productos no barriales en metodos para armar planillas 2025-05-23 17:05:13 -03:00
ale
a006fc15fa Excluyendo productos barriales de productosPedidos 2025-05-23 17:04:29 -03:00
ale
37fd0fb4d3 Fila nullable 2025-05-23 16:53:54 -03:00
ale
d794dbd2b0 Usando vuex 2025-05-23 16:53:37 -03:00
55 changed files with 1254 additions and 926 deletions

View file

@ -43,6 +43,14 @@ class GrupoDeCompra extends Model
return $total;
}
public function totalSinDevoluciones() {
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->totalSinDevoluciones();
}
return $total;
}
public function totalBarrial()
{
$total = 0;
@ -108,7 +116,7 @@ class GrupoDeCompra extends Model
function pedidoParaPdf(): array
{
$productos = $this->productosPedidos(true, true, 'producto_id');
$productos = $this->productosPedidos(true, 'producto_id');
$pedido = [];
$pedido['productos'] = [];
@ -258,13 +266,12 @@ class GrupoDeCompra extends Model
return $result;
}
public function productosPedidos($excluirBarriales = false, $excluirBonos = false, $orderBy = 'producto_nombre'): Collection
public function productosPedidos($excluirBonos = false, $orderBy = 'producto_nombre'): Collection
{
$query = DB::table('pedidos_aprobados')
->where('grupo_de_compra_id', $this->id);
->where('grupo_de_compra_id', $this->id)
->where('producto_nombre','NOT LIKE','%barrial%');
if ($excluirBarriales)
$query = $query->where('producto_nombre','NOT LIKE','%barrial%');
if ($excluirBonos)
$query = $query->where('producto_es_bono',false);

View file

@ -101,7 +101,6 @@ class CanastaHelper
});
Producto::create([
'fila' => 420,
'nombre' => "Bono barrial",
'precio' => 20,
'categoria' => $categoria,

View file

@ -6,6 +6,7 @@ use App\GrupoDeCompra;
use App\Http\Controllers\Controller;
use App\Http\Resources\GrupoDeCompraReducido;
use App\Http\Resources\GrupoDeCompraResource;
use http\Env\Request;
class GrupoDeCompraController extends Controller
{
@ -17,8 +18,18 @@ class GrupoDeCompraController extends Controller
{
return new GrupoDeCompraResource($grupoDeCompra);
}
public function reducido(GrupoDeCompra $grupoDeCompra)
public function regiones()
{
return new GrupoDeCompraReducido($grupoDeCompra);
return GrupoDeCompra::all()->pluck('region')->unique()->flatten();
}
public function region(string $region)
{
return GrupoDeCompra::where('region', $region)->get();
}
public function toggleDevoluciones(int $gdc) {
GrupoDeCompra::find($gdc)->toggleDevoluciones();
return response()->noContent();
}
}

View file

@ -2,8 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Filtros\FiltroDeProducto;
use App\Http\Resources\ProductoResource;
use App\Producto;
@ -15,9 +15,8 @@ class ProductoController extends Controller
return ProductoResource::collection(Producto::filtrar($filtros)->paginate(Producto::getPaginar($request)));
}
public function show(Producto $producto)
public function categorias()
{
return new ProductoResource($producto);
return Producto::all()->pluck('categoria')->unique()->flatten();
}
}

View file

@ -3,13 +3,16 @@
namespace App\Http\Controllers;
use App\Subpedido;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class SessionController extends Controller
{
public function store(Request $request) {
public function store(Request $request): Response
{
$grupo_de_compra_id = Auth::user()->grupo_de_compra_id;
$validated = $request->validate([
'id' => 'required',
@ -19,7 +22,14 @@ class SessionController extends Controller
return response()->noContent();
}
public function fetch() {
return session('pedido_id');
public function fetch(): JsonResponse
{
return response()->json(['id' => session('pedido_id')]);
}
public function destroy(): Response
{
session()->forget('pedido_id');
return response()->noContent();
}
}

View file

@ -21,6 +21,7 @@ class GrupoDeCompraResource extends JsonResource
'devoluciones_habilitadas' => $this->devoluciones_habilitadas,
'pedidos' => SubpedidoResource::collection($this->subpedidos),
'total_a_recaudar' => number_format($this->totalARecaudar(),2),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),
'total_barrial' => number_format($this->totalBarrial(),2),
'total_devoluciones' => number_format($this->totalDevoluciones(),2),
'total_a_transferir' => number_format($this->totalATransferir(),2),

View file

@ -14,13 +14,18 @@ use Illuminate\Support\Facades\DB;
class Producto extends Model
{
protected $fillable = ["nombre", "precio", "categoria"];
protected $fillable = ["nombre", "precio", "categoria", "bono", "es_solidario", "requiere_notas"];
public function subpedidos(): BelongsToMany
{
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
{
@ -34,17 +39,17 @@ class Producto extends Model
public static function productosFilaID()
{
return Producto::pluck('id', 'fila')->all();
return self::noBarriales()->pluck('id', 'fila')->all();
}
public static function productosIDFila()
{
return Producto::pluck('fila', 'id')->all();
return self::noBarriales()->pluck('fila', 'id')->all();
}
public static function productosIDNombre()
{
return Producto::pluck('nombre', 'id')->all();
return self::noBarriales()->pluck('nombre', 'id')->all();
}
static public function cantidadesPorBarrio(): Collection

View file

@ -9,6 +9,7 @@
"license": "MIT",
"require": {
"php": "^7.4",
"doctrine/dbal": "^2.2.0",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3.1|^7.0.1",

809
composer.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

60
package-lock.json generated
View file

@ -13,12 +13,8 @@
},
"devDependencies": {
"axios": "^0.19.2",
"bootstrap": "^4.0.0",
"cross-env": "^7.0.3",
"jquery": "^3.2",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"resolve-url-loader": "^2.3.1",
"sass": "^1.20.1",
"sass-loader": "^8.0.0",
@ -3090,26 +3086,6 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
},
"node_modules/bootstrap": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.1"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -7939,12 +7915,6 @@
"node": ">=8"
}
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"dev": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -9605,17 +9575,6 @@
"node": ">=8"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/portfinder": {
"version": "1.0.37",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz",
@ -17156,13 +17115,6 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
},
"bootstrap": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
"dev": true,
"requires": {}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -20894,12 +20846,6 @@
}
}
},
"jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -22234,12 +22180,6 @@
"find-up": "^4.0.0"
}
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"dev": true
},
"portfinder": {
"version": "1.0.37",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz",

View file

@ -11,12 +11,8 @@
},
"devDependencies": {
"axios": "^0.19.2",
"bootstrap": "^4.0.0",
"cross-env": "^7.0.3",
"jquery": "^3.2",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"resolve-url-loader": "^2.3.1",
"sass": "^1.20.1",
"sass-loader": "^8.0.0",

View file

@ -1,41 +0,0 @@
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });

View file

@ -23,7 +23,6 @@ import CaracteristicasOpcionales from "./CaracteristicasOpcionales.vue";
import TabsSecciones from "../comunes/TabsSecciones.vue";
import DropdownDescargar from "./DropdownDescargar.vue";
import TablaPedidos from "./TablaPedidos.vue";
import TablaBonos from "./TablaBonos.vue";
import { mapActions, mapGetters } from "vuex";
export default {
components: {
@ -31,7 +30,6 @@ export default {
TabsSecciones,
DropdownDescargar,
TablaPedidos,
TablaBonos,
},
data() {
return {

View file

@ -1,5 +1,4 @@
<script>
import axios from "axios";
import {mapActions, mapGetters, mapState} from "vuex";
export default {

View file

@ -12,7 +12,9 @@
<td class="has-text-right" >
{{ devoluciones_habilitadas ? pedido.total : pedido.total_sin_devoluciones }}
</td>
<td><switch-aprobacion :pedido_id="pedido_id"></switch-aprobacion></td>
<td>
<switch-aprobacion :pedido_id="pedido_id"/>
</td>
</tr>
</template>

View file

@ -1,97 +0,0 @@
<template>
<div class="block">
<div class="block" v-show="!hayBonos">
<p class="has-text-centered">
Todavía no hay bonos pedidos.
</p>
</div>
<div class="block" v-show="hayBonos">
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th><abbr title="Núcleo">Núcleo</abbr></th>
<td v-for="(bono,i) in bonos" :key="i" class="is-1">
{{ bono.nombre }}
</td>
<th><abbr title="Total a Pagar">Total $</abbr></th>
</tr>
</thead>
<tbody>
<tr v-for="(bp, i) in bonosPorPedido" :key="i">
<td> {{ bp.nucleo }} </td>
<td v-for="(bono,j) in bp.bonos" :key="j" class="has-text-right">
{{ bono.cantidad }}
</td>
<td class="has-text-right"> {{ bp.total }} </td>
</tr>
</tbody>
<tfoot>
<tr>
<th></th>
<th :colspan="bonos.length">Total bonos</th>
<th class="has-text-right">$ {{ totalBonos }}</th>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script>
export default {
props: {
pedidos: {
type: Array,
required: true
}
},
data() {
return {
bonos: []
}
},
computed: {
pedidosAprobados: function() {
return this.pedidos.filter(p => p.aprobado)
},
hayBonos: function() {
return this.pedidosAprobados.filter(p => p.subtotal_bonos != 0).length !== 0
},
bonosPorPedido: function() {
let bonosPorPedido = this.pedidosAprobados.map(p => p = {"nucleo":p.nombre, "bonos":p.productos.filter(x => x.bono), "total":p.subtotal_bonos});
bonosPorPedido.forEach(bp => {
bp.bonos = bp.bonos.map(b => b = {"bono":b.nombre, "cantidad":b.pivot.cantidad, "total":b.pivot.total, "id":b.id})
})
return bonosPorPedido.map(bp => this.completarBonos(bp));
},
totalBonos: function() {
let total = 0
this.bonosPorPedido.map(bp => total += parseInt(bp.total))
return total
},
},
methods: {
completarBonos(bonosPedido) {
this.bonos.map(b => {
if (!bonosPedido.bonos.map(x => x.id).includes(b.id))
bonosPedido.bonos.push({"bono":b.nombre, "cantidad":0, "total":0, "id":b.id})
})
bonosPedido.bonos = bonosPedido.bonos.sort((b1,b2) => b1.id - b2.id)
return bonosPedido
}
},
beforeMount() {
axios.get("../api/productos", {
params: {
categoria:'TRANSPORTE, BONOS Y FINANCIAMIENTO SORORO',
}
}).then(response => {
this.bonos = response.data.data;
});
},
}
</script>
<style>
</style>

View file

@ -24,7 +24,7 @@
</tr>
<tr>
<th>Total a recaudar:</th>
<td class="has-text-right">$ {{ total_a_recaudar }}</td>
<td class="has-text-right">$ {{ devoluciones_habilitadas ? total_a_recaudar : total_sin_devoluciones }}</td>
</tr>
<tr>
<th>Total bonos barriales:</th>
@ -62,6 +62,7 @@ export default {
"devoluciones_habilitadas",
"pedidos",
"total_a_recaudar",
"total_sin_devoluciones",
"total_barrial",
"total_devoluciones",
"cantidad_transporte",

View file

@ -4,8 +4,8 @@
<div class="block pb-6" id="pedidos-compras-seccion"
:class="seccionActiva === 'pedidos-compras-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="pedidos-compras-tabla-y-dropdown">
<compras-dropdown-descargar>
</compras-dropdown-descargar>
<dropdown-descargar>
</dropdown-descargar>
</div>
</div>
<div class="block pb-6" id="canasta-compras-seccion"
@ -37,7 +37,7 @@
</div>
</article>
<div class="buttons is-right">
<compras-canasta-input></compras-canasta-input>
<canasta-input></canasta-input>
</div>
</div>
</div>

View file

@ -14,7 +14,7 @@
</span>
<span class="file-label">Subir canasta</span>
</span>
<span class="file-name" v-if="archivo">
<span class="file-name" v-if="cargando">
{{ 'Cargando ' + archivo.nombre }}
</span>
</label>
@ -24,6 +24,7 @@
<script>
import axios from "axios";
import { mapActions } from "vuex";
export default {
name: "CanastaInput",
@ -34,10 +35,11 @@ export default {
};
},
methods: {
...mapActions('ui',["toast"]),
async archivoSubido(event) {
const archivo = event.target.files[0];
if (archivo && archivo.type === "text/csv") {
this.archivo = {data: archivo, nombre: archivo.name};
this.archivo = { data: archivo, nombre: archivo.name };
const formData = new FormData();
formData.append("data", this.archivo.data);
@ -48,15 +50,15 @@ export default {
"Content-Type": "multipart/form-data",
},
});
this.$root.$toast(response.data.message || "Canasta cargada exitosamente");
this.toast({ mensaje: (response.data.message || "Canasta cargada exitosamente") });
} catch (error) {
this.$root.$toast(error.response?.data?.message || "Hubo errores.");
this.toast({ mensaje: (error.response?.data?.message || "Hubo errores.") });
} finally {
this.cargando = false;
this.archivo = null;
}
} else {
this.$root.$toast("La canasta debe ser .CSV")
this.toast("La canasta debe ser .CSV")
this.archivo = null;
}
},

View file

@ -1,5 +1,5 @@
<template>
<div v-if="region_elegida !== null" class="block">
<div v-if="region_elegida" class="block">
<div class="field">
<label class="label" :class="adminUrl ? 'has-text-white' : ''">
Seleccioná tu barrio o grupo de compra

View file

@ -1,5 +1,5 @@
<template>
<div v-if="grupo_de_compra_elegido !== null" class="block">
<div v-if="grupo_de_compra_elegido" class="block">
<div class="field">
<label class="label"
:class="adminUrl ? 'has-text-white' : ''">{{ mensajes.mensaje }}</label>

View file

@ -6,7 +6,7 @@
</a>
<div class="navbar-item" id="datos-pedido" v-if="pedidoDefinido">
<p class="hide-below-1024">
{{ `Núcleo: ${nombre} - Barrio: ${grupo_de_compra}` }}
{{ `Núcleo: ${nombre} - Barrio: ${grupo_de_compra.nombre}` }}
</p>
</div>
<chismosa-dropdown
@ -42,7 +42,6 @@
class="text-a">
Cerrar sesión
</a>
<slot name="logout-form"></slot>
</div>
</div>
</div>
@ -63,12 +62,12 @@ export default {
},
computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('pedido',["nombre"]),
...mapState('barrio',["grupo_de_compra"]),
...mapState('pedido', ["nombre"]),
...mapState('pedido', ["grupo_de_compra"]),
},
methods: {
...mapActions('productos', ["filtrarProductos"]),
...mapMutations('ui',["addMiga"]),
...mapMutations('ui', ["addMiga", "popUltimaBusqueda"]),
toggleBurger() {
this.burgerActiva = !this.burgerActiva
},
@ -76,6 +75,7 @@ export default {
if (this.burgerActiva)
this.toggleBurger();
this.filtrarProductos({ filtro: "nombre", valor: this.searchString });
this.popUltimaBusqueda();
this.addMiga({ nombre: this.searchString });
}
},

View file

@ -7,7 +7,7 @@
<div class="control">
<div class="select">
<select @change="selectRegion({ region })" v-model="region">
<option :disabled="region_elegida !== null" value=null>
<option :disabled="region_elegida" value=null>
Seleccionar
</option>
<option v-for="(region, index) in regiones"
@ -25,6 +25,7 @@
<script>
import {mapActions, mapGetters, mapState} from "vuex";
export default {
name: 'RegionSelect',
async mounted() {
await this.getRegiones();
},

View file

@ -0,0 +1,27 @@
<script>
import { mapGetters } from "vuex";
export default {
name:'LoginTitulos',
computed: {
...mapGetters('login',["titulos", "urlRol"]),
whiteText() {
console.log(this.urlRol);
return this.urlRol === 'admin';
}
}
};
</script>
<template>
<div class="block">
<h1 class="title" :class="{'has-text-white': whiteText}">{{ titulos.titulo }}</h1>
<p class="subtitle" :class="{'has-text-white': whiteText}">
{{ `Bienvenidx a la ${titulos.subtitlo} del ` }}<strong :class="{'has-text-white': whiteText}">Mercado Popular de Subistencia</strong>
</p>
</div>
</template>
<style scoped>
</style>

View file

@ -1,29 +1,40 @@
<template>
<div id="pedidos-body">
<cartel-pedido-aprobado></cartel-pedido-aprobado>
<pedido-select-section v-if="!pedidoDefinido"></pedido-select-section>
<pedido v-else></pedido>
<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>
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import PedidoSelectSection from "./PedidoSelectSection.vue";
import Pedido from "./Pedido.vue";
import { mapActions, mapGetters, mapState } from "vuex";
import CartelPedidoAprobado from "./CartelPedidoAprobado.vue";
import PedidoSelect from "./PedidoSelect.vue";
import Canasta from "./Canasta.vue";
import NavMigas from "./NavMigas.vue";
import Chismosa from "./Chismosa.vue";
export default {
components: { CartelPedidoAprobado, Pedido, Productos: Pedido, PedidoSelectSection },
components: { Chismosa, NavMigas, CartelPedidoAprobado, PedidoSelect, Canasta },
computed: {
...mapGetters('pedido',["pedidoDefinido"]),
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('ui', ["show_chismosa"]),
},
methods: {
...mapActions('productos',["init"]),
...mapActions('pedido',["getSesion"]),
...mapActions('productos', ["init"]),
},
async mounted() {
await this.init();
await this.getSesion();
}
}
</script>

View file

@ -1,18 +1,16 @@
<script >
import { defineComponent } from "vue";
import { mapActions, mapState } from "vuex";
import SubpedidoSelect from "./SubpedidoSelect.vue";
import { mapState } from "vuex";
import CategoriasContainer from "./CategoriasContainer.vue";
import ProductosContainer from "./ProductosContainer.vue";
import Chismosa from "./Chismosa.vue";
import DevolucionesModal from "./DevolucionesModal.vue";
import CartelPedidoAprobado from "./CartelPedidoAprobado.vue";
export default defineComponent({
components: { CartelPedidoAprobado, DevolucionesModal, SubpedidoSelect, CategoriasContainer, ProductosContainer, Chismosa },
components: { DevolucionesModal, CategoriasContainer, ProductosContainer, Chismosa },
computed: {
...mapState('ui',["show_chismosa","show_devoluciones"])
},
...mapState('ui', ["show_chismosa", "show_devoluciones"])
}
})
</script>
@ -20,7 +18,6 @@ export default defineComponent({
<div class="columns ml-3 mr-3" v-else>
<categorias-container :class="show_chismosa ? 'hide-below-1024' : ''"></categorias-container>
<productos-container :class="show_chismosa ? 'hide-below-1024' : ''"></productos-container>
<chismosa v-show="show_chismosa"></chismosa>
<devoluciones-modal v-show="show_devoluciones"></devoluciones-modal>
</div>
</template>

View file

@ -1,39 +1,92 @@
<template>
<div v-show="visible" class="column">
<div class="columns is-multiline is-mobile">
<div ref="categorias"
class="columns is-multiline is-mobile"
:class="{ 'align-last-left': isLastRowIncomplete }">
<div v-for="(categoria,i) in categorias" :key="i"
class="block column is-one-quarter-desktop is-one-third-tablet is-half-mobile">
<div @click.capture="seleccionar(categoria)" class="card" style="height:100%">
:class="{ 'is-3-desktop is-2-fullhd': !show_chismosa }"
class="column is-4-tablet is-6-mobile hover-dedito">
<div @click.capture="seleccionar(categoria)" class="card" style="height: 100%">
<div class="card-content">
<div class="media">
<div class="media-content" style="overflow:hidden">
<p class="title is-6" v-text="categoria"></p>
<div class="media-content" style="overflow: hidden">
<p class="title is-size-7-mobile is-size-6-tablet has-text-centered" v-text="categoria"></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- END CARD -->
</div><!-- END BLOCK COLUMN -->
</div><!-- END COLUMNS -->
</div><!-- END CONTAINER -->
</template>
<script>
import { mapActions, mapMutations, mapState } from "vuex";
export default {
name: 'CategoriasContainer',
computed: {
...mapState('productos',["categorias", "filtro"]),
...mapState('productos', ["categorias", "filtro"]),
...mapState('ui', ["show_chismosa"]),
visible() {
return this.filtro === null;
}
},
methods: {
...mapActions('productos',["seleccionarCategoria"]),
...mapMutations('ui',["addMiga"]),
...mapActions('productos', ["seleccionarCategoria"]),
...mapMutations('ui', ["addMiga"]),
seleccionar(categoria) {
this.seleccionarCategoria({ categoria: categoria });
this.addMiga({ nombre: categoria });
this.addMiga({ nombre: categoria, action: "productos/seleccionarCategoria", arguments: { categoria: categoria }});
},
checkIfLastRowIncomplete() {
this.$nextTick(() => {
const wrapper = this.$refs.categorias;
if (!wrapper) return;
const columns = wrapper.querySelectorAll('.column');
if (columns.length === 0) {
this.isLastRowIncomplete = false;
return;
}
const firstRowTop = columns[0].offsetTop;
let firstRowCount = 0;
columns.forEach(col => {
if (col.offsetTop === firstRowTop) firstRowCount++;
});
this.isLastRowIncomplete = this.categorias.length % firstRowCount !== 0;
});
}
},
data() {
return {
isLastRowIncomplete: false
}
},
mounted() {
this.checkIfLastRowIncomplete();
window.addEventListener('resize', this.checkIfLastRowIncomplete);
},
beforeDestroy() {
window.removeEventListener('resize', this.checkIfLastRowIncomplete);
},
}
</script>
<style>
.hover-dedito {
cursor: pointer;
}
.columns.align-last-left {
justify-content: flex-start !important;
}
.columns.align-last-left > .column:last-child:nth-child(3n),
.columns.align-last-left > .column:last-child:nth-child(2n),
.columns.align-last-left > .column:last-child {
margin-right: auto;
}
.title {
word-break: keep-all;
}
</style>

View file

@ -1,6 +1,5 @@
<template>
<div class="column is-one-third">
<div class="fixed-right">
<div class="fixed-right mr-3 ml-3">
<table v-show="mostrar_tabla" class="table is-striped is-bordered tabla-chismosa is-narrow">
<thead>
<tr>
@ -15,7 +14,7 @@
<th class="has-text-right">{{ cantidad_transporte }}</th>
<th class="has-text-right">{{ total_transporte }}</th>
</tr>
<tr v-if="devoluciones_habilitadas">
<tr v-if="grupo_de_compra.devoluciones_habilitadas">
<th><p>Devoluciones</p></th>
<td>
<abbr :title="devoluciones_notas">{{ notas_abreviadas }}</abbr>
@ -41,7 +40,6 @@
Compa, todavía no agregaste nada a la chismosa.
</p>
</div>
</div>
</template>
<script>
@ -51,8 +49,8 @@ import { mapMutations, mapState } from "vuex";
export default {
components: { ProductoRow },
computed: {
...mapState('barrio',["devoluciones_habilitadas"]),
...mapState('pedido',[
"grupo_de_compra",
"productos",
"total",
"total_transporte",
@ -82,6 +80,5 @@ export default {
position: fixed;
overflow-y: auto;
max-height: 81vh;
margin-right: 20px;
}
</style>

View file

@ -1,5 +1,5 @@
<template>
<nav v-if="pedidoDefinido" class="breadcrumb is-centered has-background-danger-light is-fixed-top"
<nav class="breadcrumb is-centered has-background-danger-light is-fixed-top"
aria-label="breadcrumbs" v-show="visible">
<ul class="mt-4">
<li v-for="(miga, i) in migas" :key="i" :class="{'is-active': i === migaActiva}">
@ -11,22 +11,25 @@
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
import { mapActions, mapMutations, mapState } from "vuex";
export default {
methods: {
...mapActions('productos',["getProductos"]),
...mapActions('ui',["clickMiga"]),
...mapActions('productos', ["getProductos"]),
...mapActions('ui', ["clickMiga"]),
...mapMutations('ui', ["addMiga"]),
},
computed: {
...mapState('ui',["migas"]),
...mapGetters('pedido',["pedidoDefinido"]),
...mapState('ui', ["migas"]),
visible() {
return this.migas.length > 0
return this.migas.length > 0;
},
migaActiva() {
return this.migas.length - 1
}
return this.migas.length - 1;
},
},
mounted() {
this.addMiga({ nombre: 'Categorias', action: 'productos/getProductos' });
},
}
</script>

View file

@ -0,0 +1,114 @@
<template>
<section class="section">
<div id="root" class="container">
<h1 class="title">
Pedidos MPS
</h1>
<p class="subtitle">
Bienvenidx a la aplicación de pedidos del <strong>Mercado Popular de Subsistencia</strong>
</p>
<div>
<label class="label">Escribí el nombre de tu familia o grupo de convivencia</label>
<div class="columns">
<div class="column is-two-thirds">
<div class="field">
<div class="control">
<input class="input" @input="onType" v-model="searchString"/>
</div>
<p class="help">Debe ser claro para que tus compas del barrio te identifiquen.</p>
</div>
</div>
<div class="column is-one-third buttons">
<button class="button is-danger" v-if="!deshabilitado" @click="submit(undefined)">
Crear nuevo pedido
</button>
</div>
</div>
<div v-if="pedidos.length" class="block">
<label class="label">
Si ya comenzaste a hacer tu pedido este mes, elegilo en esta lista:
</label>
<p class="help">
Podés seguir escribiendo en el campo de arriba para refinar la búsqueda.
</p>
<div class="columns is-mobile"
v-for="(subpedidoExistente, index) in pedidos"
:class="{'has-background-grey-lighter': index % 2}"
:key="index">
<div class="column is-half-mobile is-two-thirds-desktop is-two-thirds-tablet">
<p class="nombre">
{{ subpedidoExistente.nombre }}
</p>
</div>
<div class="buttons column is-half-mobile is-one-third-desktop is-one-third-tablet">
<button class="button is-danger" @click="submit(subpedidoExistente)">
Continuar pedido
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</template><script>
import { mapActions, mapMutations, mapState } from "vuex";
import axios from "axios";
export default {
name: 'PedidoSelect',
async mounted() {
await this.getGrupoDeCompra();
const sesion = await axios.get("/pedido/sesion");
if (sesion.data.id) {
await this.elegirPedido({ pedido_id: sesion.data.id });
}
},
data() {
return {
pedidos: [],
searchString: null,
}
},
computed: {
...mapState('pedido', ["grupo_de_compra"]),
deshabilitado: function () {
return !this.searchString?.trim()
|| this.pedidos.some(p => p.nombre.toLowerCase() === this.searchString.toLowerCase())
}
},
methods: {
...mapActions('pedido', ["getGrupoDeCompra", "crearPedido", "elegirPedido"]),
async getPedidos(nombre) {
const response = await axios.get('/api/subpedidos/',{
params: {
nombre: nombre,
grupo_de_compra: this.grupo_de_compra.id
}
});
this.pedidos = response.data;
},
onType() {
if (!this.searchString) {
this.setPedidos([]);
return;
}
this.getPedidos(this.searchString);
},
async submit(pedido) {
if (pedido)
await this.elegirPedido({ pedido_id: pedido.id });
else
await this.crearPedido({ nombre: this.searchString, grupo_de_compra_id: this.grupo_de_compra.id });
},
}
}
</script>
<style>
.nombre {
padding-top: calc(.5em - 1px);
margin-bottom: .5rem
}
</style>

View file

@ -1,26 +0,0 @@
<script>
import { defineComponent } from "vue";
import SubpedidoSelect from "./SubpedidoSelect.vue";
import { mapGetters } from "vuex";
export default defineComponent({
components: { SubpedidoSelect },
})
</script>
<template>
<section class="section">
<div id="root" class="container">
<h1 class="title">
Pedidos MPS
</h1>
<p class="subtitle">
Bienvenidx a la aplicación de pedidos del <strong>Mercado Popular de Subsistencia</strong>
</p>
<subpedido-select></subpedido-select>
</div>
</section>
</template>
<style scoped>
</style>

View file

@ -1,32 +1,37 @@
<template>
<div>
<div class="field has-addons contador">
<div class="is-justify-content-center">
<div class="field has-addons is-justify-content-center contador">
<div class="control">
<button class="button is-small" @click.capture="decrementar();">
<button class="button is-small" :disabled="cantidadControl < 1" v-if="!aprobado" @click.capture="decrementar();">
<i class="fa fa-solid fa-minus"></i>
</button>
</div>
<div class="control">
<input id="cantidad" v-model="cantidadControl" class="input is-small" type="number"
style="text-align: center">
<input id="cantidad"
v-model="cantidadControl"
class="input is-small"
type="number"
style="text-align: center"
:readonly="aprobado">
</div>
<div class="control" @click="incrementar();">
<button class="button is-small">
<div class="control">
<button class="button is-small" v-if="!aprobado" @click="incrementar();">
<i class="fa fa-solid fa-plus"></i>
</button>
</div>
<button :disabled="!hayCambios" class="button is-small is-success ml-1" @click="confirmar()">
<button :disabled="!hayCambios || cantidadControl < 0" v-if="!aprobado" class="button is-small is-success ml-1" @click="confirmar()">
<span class="icon">
<i class="fas fa-check"></i>
</span>
</button>
<button :disabled="!puedeBorrar" class="button is-small is-danger ml-1" @click="borrar()">
<button :disabled="!puedeBorrar" v-if="!aprobado" class="button is-small is-danger ml-1" @click="borrar()">
<span class="icon">
<i class="fas fa-trash-alt"></i>
</span>
</button>
</div>
<div v-if="requiere_notas" :class="{'has-icons-right': notas_warning_visible}"
<div v-if="!aprobado && requiere_notas"
:class="{'has-icons-right': notas_warning_visible}"
class="control is-full-width has-icons-left">
<span class="icon is-small is-left">
<i class="fas fa-sticky-note"></i>
@ -45,7 +50,7 @@
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import { mapActions, mapGetters, mapState } from "vuex";
export default {
props: {
@ -77,6 +82,7 @@ export default {
this.actualizar();
},
computed: {
...mapState('pedido', ["aprobado"]),
...mapGetters('pedido', ["enChismosa", "cantidad", "notas"]),
cantidadEnChismosa() {
return this.cantidad(this.producto_id);
@ -88,7 +94,7 @@ export default {
return this.cantidadControl !== this.cantidadEnChismosa || this.notasControl !== this.notasEnChismosa;
},
puedeBorrar() {
return this.enChismosa(this.producto_id);
return this.enChismosa(this.producto_id) && !this.aprobado;
},
faltaNotas() {
return this.requiere_notas && this.cantidadControl > 0 && !this.notasControl;
@ -140,7 +146,7 @@ input[type=number] {
}
.contador {
min-width: 178px;
min-width: 1.5rem;
}
.is-danger {

View file

@ -1,6 +1,6 @@
<script>
import ProductoCantidad from "./ProductoCantidad.vue";
import { mapGetters } from "vuex";
import { mapGetters, mapState } from "vuex";
export default {
name: "ProductoCard",
@ -12,50 +12,58 @@ export default {
}
},
computed: {
...mapGetters('pedido',["enChismosa", "cantidad"]),
...mapState('ui', ["show_chismosa"]),
...mapState('pedido', ["aprobado"]),
...mapGetters('pedido', ["enChismosa", "cantidad"]),
fuePedido() {
return this.enChismosa(this.producto.id);
},
cantidadEnChismosa() {
return this.cantidad(this.producto.id);
},
conIconos() {
return this.producto.economia_solidaria || this.producto.nacional;
}
},
}
</script>
<template>
<div class="block column is-one-quarter-desktop is-full-mobile is-half-tablet min-width-from-desktop">
<div class="box" style="height:100%">
<div class="columns">
<div class="columns is-mobile">
<div class="column">
<p class="title is-6">
{{ producto.nombre }}
</p>
<span class="subtitle is-7 hidden-from-tablet" v-if="fuePedido">{{ cantidadEnChismosa }}</span>
</div>
<div class="column is-one-quarter has-text-right">
<p class="has-text-weight-bold has-text-primary">
<span class="is-left-mobile">
<img v-show="producto.economia_solidaria" height="30px" width="30px" src="/assets/solidaria.png" alt="proveedor de economía solidaria">
<img v-show="producto.nacional" height="30px" width="30px" src="/assets/uruguay.png" alt="proveedor nacional"/>
</span>
$<span v-text="producto.precio"></span>
<p class="has-text-weight-bold has-text-primary block">
${{ producto.precio }}
</p>
</div>
</div>
<footer class="columns">
<div class="column is-three-quarters">
<footer>
<div class="columns is-justify-content-left is-mobile">
<div v-if="conIconos" class="column has-text-left">
<span>
<img v-show="producto.economia_solidaria" height="30px" width="30px" src="/assets/solidaria.png" alt="proveedor de economía solidaria">
<img v-show="producto.nacional" height="30px" width="30px" src="/assets/uruguay.png" alt="proveedor nacional"/>
</span>
</div>
<div class="column"
:class="conIconos ? 'is-three-quarters-mobile is-two-thirds-tablet' : 'is-full'">
<producto-cantidad
v-if="!aprobado"
:producto_id="producto.id"
:requiere_notas="producto.requiere_notas">
</producto-cantidad>
<div class="has-text-centered mt-2" v-if="fuePedido && aprobado">
<p class="subtitle is-7">{{ cantidadEnChismosa }} en chismosa</p>
</div>
</div>
<div class="column">
<p class="subtitle is-7 is-hidden-mobile" v-if="fuePedido">{{ cantidadEnChismosa }} en chismosa</p>
</div>
</footer>
</div>
</div>
</template>
<style lang="scss" scoped>

View file

@ -1,13 +1,17 @@
<template>
<div v-show="visible" class="column">
<div class="columns is-multiline is-mobile">
<producto-card
v-for="(producto,i) in this.productos"
:key="i"
<div ref="productos"
class="columns is-multiline is-mobile"
:class="{ 'align-last-left': isLastRowIncomplete }">
<div v-for="(producto,i) in this.productos"
class="block column is-full-mobile is-half-tablet is-one-quarter-fullhd"
:class="show_chismosa ? 'is-half-desktop' : 'is-one-third-desktop'">
<producto-card :key="i"
:producto="producto">
</producto-card>
</div>
</div>
</div>
</template>
<script>
@ -18,6 +22,7 @@ export default {
components: { ProductoCard },
computed: {
...mapState('productos', ["productos", "filtro"]),
...mapState('ui', ["show_chismosa"]),
visible() {
return this.filtro !== null;
},
@ -28,5 +33,49 @@ export default {
}
}
},
methods: {
checkIfLastRowIncomplete() {
this.$nextTick(() => {
const wrapper = this.$refs.productos;
if (!wrapper) return;
const columns = wrapper.querySelectorAll('.column');
if (columns.length === 0) {
this.isLastRowIncomplete = false;
return;
}
const firstRowTop = columns[0].offsetTop;
let firstRowCount = 0;
columns.forEach(col => {
if (col.offsetTop === firstRowTop) firstRowCount++;
});
this.isLastRowIncomplete = this.productos.length % firstRowCount !== 0;
});
}
},
data() {
return {
isLastRowIncomplete: false
}
},
mounted() {
this.checkIfLastRowIncomplete();
window.addEventListener('resize', this.checkIfLastRowIncomplete);
},
beforeDestroy() {
window.removeEventListener('resize', this.checkIfLastRowIncomplete);
},
}
</script>
<style>
.columns.align-last-left {
justify-content: flex-start !important;
}
.columns.align-last-left > .column:last-child:nth-child(3n),
.columns.align-last-left > .column:last-child:nth-child(2n),
.columns.align-last-left > .column:last-child {
margin-right: auto;
}
</style>

View file

@ -1,73 +0,0 @@
<template>
<div>
<label class="label">Escribí el nombre de tu familia o grupo de convivencia</label>
<div class="columns">
<div class="column is-two-thirds">
<div class="field">
<div class="control">
<input class="input" @input="onType" v-model="searchString"/>
</div>
<p class="help">Debe ser claro para que tus compas del barrio te identifiquen.</p>
</div>
</div>
<div class="column is-one-third buttons">
<button class="button is-danger" v-if="!deshabilitado" @click="submit">Crear nuevo pedido
</button>
</div>
</div>
<div v-if="pedidos.length" class="block">
<label class="label">Si ya comenzaste a hacer tu pedido este mes, elegilo en esta lista:</label>
<p class="help">Podés seguir escribiendo en el campo de arriba para refinar la búsqueda.</p>
<div class="columns is-mobile" v-for="(subpedidoExistente, index) in pedidos"
:class="{'has-background-grey-lighter': index % 2}" :key="index">
<div class="column is-half-mobile is-two-thirds-desktop is-two-thirds-tablet">
<p style="padding-top: calc(.5em - 1px); margin-bottom: .5rem"
v-text="subpedidoExistente.nombre"></p>
</div>
<div class="buttons column is-half-mobile is-one-third-desktop is-one-third-tablet">
<button class="button is-danger" @click="elegirPedido({ pedido: subpedidoExistente })">Continuar pedido
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapMutations, mapState } from "vuex";
export default {
name: 'SubpedidoSelect',
async mounted() {
await this.getGrupoDeCompra();
},
data() {
return {
searchString: null
}
},
computed: {
...mapState('barrio',["grupo_de_compra_id","pedidos"]),
...mapState('pedido',["nombre","pedido_id"]),
deshabilitado: function () {
return !this.searchString?.trim()
|| this.pedidos.some(p => p.nombre.toLowerCase() === this.searchString.toLowerCase())
}
},
methods: {
...mapActions('barrio',["getGrupoDeCompra","getPedidos"]),
...mapActions('pedido',["crearPedido","elegirPedido"]),
...mapMutations('barrio',["setPedidos"]),
onType() {
if (!this.searchString) {
this.setPedidos([]);
return;
}
this.getPedidos(this.searchString);
},
async submit() {
await this.crearPedido({ nombre: this.searchString, grupo_de_compra_id: this.grupo_de_compra_id });
},
}
}
</script>

View file

@ -3,7 +3,6 @@ import Vuex from 'vuex';
import admin from "./modules/admin";
import login from "./modules/login";
import pedido from "./modules/pedido";
import barrio from "./modules/barrio";
import productos from "./modules/productos";
import ui from "./modules/ui";
@ -14,7 +13,6 @@ export default new Vuex.Store({
admin,
login,
pedido,
barrio,
productos,
ui,
},

View file

@ -7,6 +7,7 @@ const state = {
devoluciones_habilitadas: null,
pedidos: null,
total_a_recaudar: null,
total_sin_devoluciones: null,
total_barrial: null,
total_devoluciones: null,
total_a_transferir: null,
@ -22,6 +23,7 @@ const mutations = {
state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas;
state.pedidos = grupo_de_compra.pedidos;
state.total_a_recaudar = grupo_de_compra.total_a_recaudar;
state.total_sin_devoluciones = grupo_de_compra.total_sin_devoluciones;
state.total_barrial = grupo_de_compra.total_barrial;
state.total_devoluciones = grupo_de_compra.total_devoluciones;
state.total_a_transferir = grupo_de_compra.total_a_transferir;
@ -56,16 +58,16 @@ const getters = {
return state.lastFetch !== null;
},
hayPedidos() {
return getters.grupoDeCompraDefinido() && state.pedidos.length > 0;
return state.pedidos?.length > 0;
},
pedidosAprobados() {
return getters.grupoDeCompraDefinido() ? state.pedidos.filter(p => p.aprobado) : [];
return state.pedidos?.filter(p => p.aprobado) ?? [];
},
hayAprobados() {
return getters.pedidosAprobados().length !== 0;
},
getPedido() {
return (pedido_id) => state.pedidos.find(p => p.id === pedido_id);
return (pedido_id) => state.pedidos?.find(p => p.id === pedido_id);
},
getCaracteristica() {
return (caracteristica) => state[`${caracteristica}_habilitadas`];

View file

@ -1,42 +0,0 @@
import axios from "axios";
const state = {
grupo_de_compra_id: null,
grupo_de_compra: null,
devoluciones_habilitadas: null,
pedidos: [],
};
const mutations = {
setGrupoDeCompra(state, { grupo_de_compra }) {
state.grupo_de_compra_id = grupo_de_compra.id;
state.grupo_de_compra = grupo_de_compra.nombre;
state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas;
},
setPedidos(state, pedidos) {
state.pedidos = pedidos;
},
};
const actions = {
async getGrupoDeCompra({ commit }) {
const response = await axios.get('/user/grupo_de_compra');
commit('setGrupoDeCompra', response.data);
},
async getPedidos({ commit }, nombre) {
const response = await axios.get('/api/subpedidos/',{
params: {
nombre: nombre,
grupo_de_compra: state.grupo_de_compra_id
}
});
commit('setPedidos', response.data);
}
};
export default {
namespaced: true,
state,
mutations,
actions,
};

View file

@ -30,8 +30,8 @@ const actions = {
commit('setRegiones', { regiones: response.data });
},
async selectRegion({ commit }, { region }) {
const response = await axios.get("/api/grupos-de-compra");
commit('setRegionYBarrios', { region: region, grupos_de_compra: response.data[region] });
const response = await axios.get(`/api/regiones/${region}`);
commit('setRegionYBarrios', { region: region, grupos_de_compra: response.data });
},
async getRol({ commit }) {
const response = await axios.get("/user/rol");
@ -49,6 +49,27 @@ const getters = {
ayuda: `Si no la sabés, consultá a ${getters.adminUrl() ? 'la comisión informática ' : 'tus compañerxs'}.`
};
},
urlRol() {
let split = window.location.pathname
.replace('login', '')
.split('/')
.filter(x => x.length);
return split[0] ?? 'pedido';
},
titulos() {
let rol = getters.urlRol();
switch (rol) {
case 'admin':
return { titulo: "Administración de Pedidos MPS", subtitlo: "administración de pedidos" };
case 'compras':
return { titulo: "Comisiones MPS", subtitlo: "página de comisiones" };
case 'pedido':
return { titulo: "Pedidos MPS", subtitlo: "aplicación de pedidos" };
default:
throw new Error("Url inválida");
}
},
};
export default {

View file

@ -2,6 +2,7 @@ import axios from "axios";
const state = {
lastFetch: null,
grupo_de_compra: null,
pedido_id: null,
nombre: null,
productos: null,
@ -15,7 +16,10 @@ const state = {
};
const mutations = {
setState(state, pedido) {
setGrupoDeCompra(state, grupo_de_compra) {
state.grupo_de_compra = grupo_de_compra;
},
setPedido(state, pedido) {
state.lastFetch = new Date();
state.pedido_id = pedido.id;
state.nombre = pedido.nombre;
@ -28,18 +32,28 @@ const mutations = {
state.devoluciones_total = pedido.devoluciones_total;
state.devoluciones_notas = pedido.devoluciones_notas;
},
reset(state) {
state.lastFetch = null;
state.pedido_id = null;
state.nombre = null;
state.productos = null;
state.aprobado = null;
state.total = null;
state.total_transporte = null;
state.cantidad_transporte = null;
state.total_sin_devoluciones = null;
state.devoluciones_total = null;
state.devoluciones_notas = null;
}
};
const actions = {
async guardarSesion(_, { pedido_id }) {
await axios.post("/subpedidos/sesion", { id: pedido_id });
async getGrupoDeCompra({ commit }) {
const response = await axios.get('/user/grupo_de_compra');
commit('setGrupoDeCompra', response.data.grupo_de_compra);
},
async getSesion({ commit }) {
const sesion = await axios.get("/subpedidos/sesion");
if (sesion.data) {
const response = await axios.get(`/api/subpedidos/${sesion.data}`);
commit('setState', response.data.data);
}
async guardarSesion(_, { pedido_id }) {
await axios.post("/pedido/sesion", { id: pedido_id });
},
async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id }) {
const response = await axios.post("/api/subpedidos", {
@ -47,12 +61,12 @@ const actions = {
grupo_de_compra_id: grupo_de_compra_id
});
dispatch("guardarSesion", { pedido_id: response.data.data.id});
commit('setState', response.data.data);
commit('setPedido', response.data.data);
},
async elegirPedido({ commit, dispatch }, { pedido }) {
const response = await axios.get(`/api/subpedidos/${pedido.id}`);
dispatch("guardarSesion", { pedido_id: response.data.data.id})
commit('setState', response.data.data);
async elegirPedido({ commit, dispatch }, { pedido_id }) {
const response = await axios.get(`/api/subpedidos/${pedido_id}`);
dispatch("guardarSesion", { pedido_id: pedido_id})
commit('setPedido', response.data.data);
},
async modificarChismosa({ commit, dispatch }, { producto_id, cantidad, notas }) {
try {
@ -61,7 +75,7 @@ const actions = {
producto_id: producto_id,
notas: notas,
});
commit('setState', response.data.data);
commit('setPedido', response.data.data);
dispatch("ui/toast", { mensaje: 'Pedido modificado con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: error }, { root: true });
@ -73,12 +87,18 @@ const actions = {
total: monto,
notas: notas,
});
commit('setState', response.data.data);
commit('setPedido', response.data.data);
dispatch("ui/toast", { mensaje: 'Devoluciones modificadas con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: error }, { root: true });
}
},
async resetear({ commit, dispatch }) {
await axios.delete("/pedido/sesion");
dispatch("productos/getProductos", null, { root: true });
dispatch("ui/resetear", null, { root: true });
commit('reset');
},
};
const getters = {

View file

@ -1,10 +1,7 @@
import { dropWhile } from "lodash/array";
const state = {
show_chismosa: false,
show_devoluciones: false,
miga_inicial: { nombre: 'Categorias', action: 'productos/getProductos' },
migas: [{ nombre: 'Categorias', action: 'productos/getProductos' }],
migas: [{ nombre: 'Pedidos', action: 'pedido/resetear' }],
};
const mutations = {
@ -17,11 +14,27 @@ const mutations = {
addMiga(state, miga) {
state.migas.push(miga);
},
popUltimaBusqueda(state) {
if (!state.migas.at(-1).action)
state.migas.pop();
},
reset(state) {
state.show_chismosa = false;
state.show_devoluciones = false;
}
};
const actions = {
clickMiga({ dispatch }, { miga }) {
dispatch(miga.action, null, { root: true });
let dropWhile = (array, pred) => {
let result = array.slice(0);
while (result.length && pred(result[0])) {
result = result.slice(1)
}
return result;
}
dispatch(miga.action, miga.arguments ?? null, { root: true });
state.migas = dropWhile(state.migas.reverse(),(m => m.nombre !== miga.nombre)).reverse();
},
toast(_, { mensaje }) {
@ -38,6 +51,9 @@ const actions = {
: error.message;
dispatch("toast", { mensaje: errorMsg });
},
resetear({ commit }) {
commit("reset");
},
};
export default {

View file

@ -7,6 +7,11 @@
@import 'bulma';
@import '~bulma-switch';
html, body {
overflow-x: hidden;
max-width: 100%;
}
main.has-top-padding {
padding-top: 4.5rem !important;
}

View file

@ -10,12 +10,7 @@
<body>
<section class="section">
<div id="root" class="container">
<h1 class="title has-text-white">
Administración de Pedidos MPS
</h1>
<p class="subtitle has-text-white">
Bienvenidx a la administración de pedidos del <strong class="has-text-white">Mercado Popular de Subsistencia</strong>
</p>
<login-titulos></login-titulos>
@error('name')
<div class="notification is-warning">
Contraseña incorrecta, intentalo nuevamente.

View file

@ -1,5 +0,0 @@
@extends('layouts.app')
@section('content')
<admin-body></admin-body>
@endsection

View file

@ -11,12 +11,7 @@
<body>
<section class="section">
<div id="root" class="container">
<h1 class="title">
Pedidos MPS
</h1>
<p class="subtitle">
Bienvenidx a la sección de compras de la aplicación del <strong>Mercado Popular de Subsistencia</strong>
</p>
<login-titulos></login-titulos>
@error('name')
<div class="notification is-danger">
Contraseña incorrecta, intentalo nuevamente.

View file

@ -1,23 +1,18 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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="{{ mix('css/app.css') }}">
<script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script>
</head>
<body>
<section class="section">
</head>
<body>
<section class="section">
<div id="root" class="container">
<admin-boton-login></admin-boton-login>
<h1 class="title">
Pedidos MPS
</h1>
<p class="subtitle">
Bienvenidx a la aplicación de pedidos del <strong>Mercado Popular de Subsistencia</strong>
</p>
<login-titulos></login-titulos>
@error('name')
<div class="notification is-danger">
Contraseña incorrecta, intentalo nuevamente.
@ -29,7 +24,7 @@
<comunes-login-form></comunes-login-form>
</form>
</div>
</section>
<script src="{{ mix('js/app.js') }}" defer></script>
</body>
</section>
<script src="{{ mix('js/app.js') }}" defer></script>
</body>
</html>

View file

@ -1,5 +0,0 @@
@extends('layouts.app')
@section('content')
<compras-body></compras-body>
@endsection

View file

@ -17,14 +17,9 @@
</head>
<body class="has-navbar-fixed-top">
<div id="root">
<comunes-nav-bar>
<template #logout-form>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none" hidden="hidden">
@csrf
</form>
</template>
</comunes-nav-bar>
<pedidos-nav-migas></pedidos-nav-migas>
<main id="main" class="py-4 has-top-padding">
@yield('content')
</main>

View file

@ -1,6 +0,0 @@
@extends('layouts.app')
@section('content')
<pedidos-body></pedidos-body>
<pedidos-devoluciones-modal></pedidos-devoluciones-modal>
@endsection

View file

@ -1,8 +0,0 @@
@extends('layouts.app')
@section('content')
<app-main></app-main>
@endsection
@section('scripts')
@endsection

View file

@ -1,8 +1,6 @@
<?php
use Illuminate\Support\Facades\Route;
use App\GrupoDeCompra;
use App\Producto;
/*
|--------------------------------------------------------------------------
@ -15,33 +13,17 @@ use App\Producto;
|
*/
Route::middleware('api')->group(function () {
Route::get('/regiones', function() {
return GrupoDeCompra::all()->pluck('region')->unique()->flatten();
});
Route::prefix('grupos-de-compra')->group( function(){
Route::get('/', function() {
$atributos_a_ocultar = ['telefono', 'cantidad_de_nucleos', 'correo', 'referente_finanzas', 'created_at', 'updated_at'];
return GrupoDeCompra::all()->makeHidden($atributos_a_ocultar)->sortBy('nombre')->groupBy('region');
});
Route::middleware('api')->group(function() {
Route::get('/regiones', 'Api\GrupoDeCompraController@regiones');
Route::get('/regiones/{region}', 'Api\GrupoDeCompraController@region');
Route::prefix('grupos-de-compra')->group(function() {
Route::get('/{grupoDeCompra}', 'Api\GrupoDeCompraController@show');
Route::get('/{gdc}/devoluciones', function($gdc) {
$habilitadas = GrupoDeCompra::find($gdc)->devoluciones_habilitadas;
return ['devoluciones' => $habilitadas];
});
Route::post('/{gdc}/devoluciones', function($gdc) {
GrupoDeCompra::find($gdc)->toggleDevoluciones();
return response()->noContent();
});
Route::post('/{gdc}/devoluciones', 'Api\GrupoDeCompraController@toggleDevoluciones');
});
Route::prefix('subpedidos')->group(function () {
Route::get('/','Api\SubpedidoController@index');
Route::get('/resources', 'Api\SubpedidoController@indexResources');
Route::get('{subpedido}','Api\SubpedidoController@show');
Route::post('/','Api\SubpedidoController@store');
Route::post('/{subpedido}/sync', 'Api\SubpedidoController@syncProductos');
@ -55,13 +37,10 @@ Route::middleware('api')->group(function () {
});
//@TO DO -> esta ruta debe estar en middleware de auth y/o subpedido
Route::get('/categorias', function() {
return Producto::all()->pluck('categoria')->unique()->flatten();
});
Route::get('/categorias', 'Api\ProductoController@categorias');
//@TO DO -> esta ruta debe estar en middleware de auth y/o subpedido
Route::prefix('productos')->group(function () {
Route::get('/','Api\ProductoController@index');
Route::get('{producto}','Api\ProductoController@show');
});
});

View file

@ -4,7 +4,6 @@ use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpKernel\Exception\HttpException;
/*
|--------------------------------------------------------------------------
| Web Routes
@ -31,42 +30,9 @@ Route::middleware(['auth'])->group(function () {
Route::middleware(['auth', 'role:barrio'])->group(function() {
Route::get('/pedido', 'RouteController@main')->name('pedido');
Route::get('/productos', 'ProductoController@index')->name('productos.index');
Route::name('subpedidos.')->prefix("subpedidos")->group(function() {
Route::get('/', function() {
return view('subpedidos_create');
})->name('create');
Route::post('guardar_sesion', function() {
$r = request();
if (!isset($r["subpedido"])) {
throw new HttpException(400, "La request necesita un subpedido para guardar en sesión");
}
if (!isset($r["grupo_de_compra_id"])) {
throw new HttpException(400, "La request necesita un grupo de compra para guardar en sesión");
}
session(["subpedido_nombre" => $r["subpedido"]["nombre"]]);
session(["subpedido_id" => $r["subpedido"]["id"]]);
session(["gdc" => $r["grupo_de_compra_id"]]);
return "Subpedido guardado en sesión";
})->name('guardarSesion');
Route::get('obtener_sesion', function() {
return [
'subpedido' => [
'nombre' => session("subpedido_nombre"),
'id' => session("subpedido_id")
],
'gdc' => session("gdc")
];
})->name('obtenerSesion');
Route::post('sesion', 'SessionController@store');
Route::get('sesion', 'SessionController@fetch');
});
Route::get('/pedido/sesion', 'SessionController@fetch');
Route::post('/pedido/sesion', 'SessionController@store');
Route::delete('/pedido/sesion', 'SessionController@destroy');
});
Route::get('/admin/login', 'AdminController@show')->name('admin.login');
@ -75,9 +41,7 @@ Route::middleware(['auth', 'role:admin_barrio'])->group(function () {
Route::get('/admin', 'RouteController@main')->name('admin');
Route::get('/admin/exportar-planillas-a-pdf/{gdc}', 'AdminController@exportarPedidosAPdf');
Route::get('/admin/exportar-pedido-a-csv/{gdc}', 'AdminController@exportarPedidoACSV');
Route::get('/admin/exportar-pedido-con-nucleos-a-csv/{gdc}', 'AdminController@exportarPedidoConNucleosACSV');
});
@ -88,6 +52,6 @@ Route::middleware(['auth', 'role:comision'])->group( function() {
Route::get('/compras/pedidos/descargar', 'ComprasController@descargarPedidos')->name('compras.pedidos.descargar');
Route::get('/compras/pedidos/notas', 'ComprasController@descargarNotas')->name('compras.pedidos.descargar');
Route::get('/compras/pedidos/pdf', 'ComprasController@pdf')->name('compras.pedidos.pdf');
Route::post('/compras/canasta', 'ComprasController@cargarCanasta')->name('compras.canasta');
Route::get('/compras/canasta/ejemplo', 'ComprasController@descargarCanastaEjemplo')->name('compras.canasta.ejemplo');
Route::post('/compras/canasta', 'ComprasController@cargarCanasta')->name('compras.canasta');
});