Compare commits

..

39 commits

Author SHA1 Message Date
ale
e6fe5e8960 Agregada opcion para faltantes y sobrantes + usando v-for para las opciones 2025-09-14 12:22:35 -03:00
ale
94e55f8161 Agregada ruta para faltantes y sobrantes 2025-09-14 12:13:00 -03:00
ale
7d87471d8b Agregado método de controlador para faltantes y sobrantes 2025-09-14 12:12:53 -03:00
ale
0ecff9ae68 Agregada lógica para faltantes y sobrantes 2025-09-14 12:12:44 -03:00
ale
ac8ee0799b Mejores mensajes 2025-09-14 11:59:48 -03:00
ale
14193ba027 Merge remote-tracking branch 'origin/funcion/faltantes-y-sobrantes' into funcion/faltantes-y-sobrantes 2025-09-13 20:35:14 -03:00
ale
7c67ee7870 Eliminada variable innecesaria 2025-09-13 20:34:26 -03:00
ale
0e8d179f6a Eliminada variable innecesaria 2025-09-13 20:33:52 -03:00
ale
37cebf11ee Eliminado parámetro innecesario 2025-09-13 20:21:08 -03:00
ale
c6059f57e1 Eliminada declaracion innecesaria 2025-09-13 20:21:01 -03:00
ale
ca4dd862d0 Cambiada logica de deshabilitacion de botones de - y de confirmar para que dependa de la cantidad pedida en el barrio 2025-09-13 20:18:59 -03:00
ale
deea20904b Agregado getter para obtener cantidad total pedida de un producto 2025-09-13 20:18:15 -03:00
ale
a6f726f8a0 Agregada logica para adaptar migas a faltantes y sobrantes 2025-09-13 19:53:45 -03:00
ale
5f847dc9af Cambiada canasta por pedidos-main para tener chismosa y migas 2025-09-13 19:42:35 -03:00
ale
7c368c3d24 Cambiada logica de devoluciones en chismosa para no mostrar si el pedido no es de un nucleo 2025-09-13 19:42:21 -03:00
ale
88deb88f04 Agregada lógica para cargar pedido de faltantes y sobrantes del barrio 2025-09-13 19:33:09 -03:00
ale
a34bfd53a8 Quitado middleware de barrio para guardar pedido en sesión 2025-09-13 19:32:47 -03:00
ale
3e1c61da68 Agregada validación para checkear que el producto fue pedido 2025-09-13 19:32:33 -03:00
ale
e370ce2a0d Agregado método para abrir faltantes y sobrantes, cargando productos 2025-09-13 19:13:55 -03:00
ale
d7710c306b Agregada accion para togglear faltantes y sobrantes desde otras stores 2025-09-13 19:13:42 -03:00
ale
97fb03f299 Agregada canasta a body de admin, para faltantes y sobrantes 2025-09-13 19:13:21 -03:00
ale
e8b5936233 Agregado control para faltantes y sobrantes en store de ui 2025-09-13 19:03:05 -03:00
ale
d7cef79a50 Agregada información de productos y cantidades pedidas por el barrio 2025-09-13 18:35:35 -03:00
ale
f155f0d824 Agregado botón para faltantes y sobrantes 2025-09-13 18:16:51 -03:00
ale
38ff38fb05 Formato 2025-09-13 18:16:40 -03:00
ale
45c8fc1af0 Agregado componente para tabla totales 2025-09-13 18:15:56 -03:00
ale
f521c726ef Limpieza 2025-09-13 17:53:49 -03:00
ale
59af3693cb Eliminado resource innecesario 2025-09-13 17:43:39 -03:00
ale
e69c379304 Mejorada lógica de sync pedido de faltantes y sobrantes con validación de cantidad para el caso faltantes 2025-09-13 17:43:32 -03:00
ale
f008b9e263 Agregado default "" a parametro 'notas' en syncProducto 2025-09-13 17:43:09 -03:00
ale
71a6e496e9 Modificada logica de syncProductos para considerar caso de faltantes (con cantidades negativas) 2025-08-27 21:10:36 -03:00
ale
dcf1659042 Agregados prefijos de rutas 2025-08-27 20:59:29 -03:00
ale
8dc76eca67 Agregada lista de productos con id y cantidad a resource de grupo de compra para admin 2025-08-27 20:59:19 -03:00
ale
c25b369e26 Agregada ruta para obtener pedido de faltantes y sobrantes 2025-08-27 20:58:54 -03:00
ale
bc37347c30 Agregado método para crear pedido de faltantes y sobrantes de un barrio 2025-08-27 20:41:37 -03:00
ale
5670ddf55a Agregado resource para pedido de faltantes y sobrantes 2025-08-27 20:40:12 -03:00
ale
deca03865f Agregado resource para grupo de compra de faltantes y sobrantes 2025-08-27 20:26:45 -03:00
ale
68cb740187 Agregado tipo pedido faltantes y sobrantes a db 2025-08-27 20:01:31 -03:00
ale
cdd10a870d actualizado 2025-08-27 19:46:19 -03:00
21 changed files with 398 additions and 123 deletions

View file

@ -35,6 +35,7 @@ class PedidosExportHelper
)
);
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
@ -43,16 +44,16 @@ class PedidosExportHelper
static public function pedidosDeOllas()
{
$filePath = "csv/exports/pedidos-de-ollas-" . now()->format('Y-m-d') . ".csv";
$tipo_olla = self::getTipoId('olla');
$barrios = GrupoDeCompra::barriosMenosPrueba()
->whereHas('subpedidos', function ($query) {
$tipo_olla = self::getTipoId('olla');
->whereHas('subpedidos', function ($query) use ($tipo_olla) {
$query->where('tipo_pedido_id', $tipo_olla);
})
->get();
$contenido = self::generarContenidoCSV($barrios,
fn($grupoId) => "subpedidos.grupo_de_compra_id = $grupoId
AND subpedidos.tipo_pedido_id = 2");
AND subpedidos.tipo_pedido_id = $tipo_olla");
$ollas = self::cantidadDeOllasParaCSV($barrios, $contenido);
self::exportarCSV(
@ -128,6 +129,32 @@ class PedidosExportHelper
);
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
* @throws Exception
*/
static public function faltantesYSobrantes()
{
$filePath = "csv/exports/faltantes-y-sobrantes-" . now()->format('Y-m-d') . ".csv";
$tipoPedidoId = self::getTipoId('faltantes_y_sobrantes');
$barrios = GrupoDeCompra::barriosMenosPrueba()
->whereHas('subpedidos', function ($query) use ($tipoPedidoId) {
$query->where('tipo_pedido_id', $tipoPedidoId);
})
->get();
$contenido = self::generarContenidoCSV($barrios,
fn($grupoId) => "subpedidos.grupo_de_compra_id = $grupoId
AND subpedidos.tipo_pedido_id = $tipoPedidoId");
self::exportarCSV(
$filePath,
$barrios,
$contenido
);
}
/**
* @throws InvalidArgument
* @throws CannotInsertRecord
@ -252,7 +279,7 @@ class PedidosExportHelper
*/
public static function getTipoId(string $tipo)
{
$tipo_olla = TipoPedido::where('nombre', $tipo)->first()->id;
return $tipo_olla;
$tipoPedido = TipoPedido::where('nombre', $tipo)->first()->id;
return $tipoPedido;
}
}

View file

@ -68,23 +68,11 @@ class SubpedidoController extends Controller
// recibe request, saca producto y cantidad, valida, y pasa a syncProducto en Subpedido
public function syncProductos(Subpedido $subpedido) {
if ($subpedido->aprobado)
abort(400, "No se puede modificar un pedido aprobado.");
$valid = request()->validate([
'cantidad' => ['integer','required','min:0'],
'notas' => 'nullable',
'producto_id' => [
'required',
Rule::in(Producto::all()->pluck('id')),
]
]);
$producto = Producto::find($valid['producto_id']);
$notas = $valid['notas'];
$cantidad = $valid['cantidad'];
$subpedido->syncProducto($producto, $cantidad, $notas ?? "");
return new SubpedidoResource($subpedido);
$faltantesYSobrantes = TipoPedido::firstOrCreate(['nombre' => 'faltantes_y_sobrantes']);
if ($subpedido->tipo_pedido_id == $faltantesYSobrantes->id) {
return $this->syncFaltantesYSobrantes($subpedido);
}
return $this->syncPedidoNormal($subpedido);
}
public function toggleAprobacion(Subpedido $subpedido) {
@ -107,4 +95,64 @@ class SubpedidoController extends Controller
return new SubpedidoResource($subpedido);
}
/**
* @param Subpedido $subpedido
* @return SubpedidoResource
*/
public function syncPedidoNormal(Subpedido $subpedido): SubpedidoResource
{
if ($subpedido->aprobado)
abort(400, "No se puede modificar un pedido aprobado.");
$valid = request()->validate([
'cantidad' => ['integer', 'required', 'min:0'],
'notas' => 'nullable',
'producto_id' => [
'required',
Rule::in(Producto::all()->pluck('id')),
]
]);
$producto = Producto::find($valid['producto_id']);
$notas = $valid['notas'];
$cantidad = $valid['cantidad'];
$subpedido->syncProducto($producto, $cantidad, $notas ?? "");
return new SubpedidoResource($subpedido);
}
/**
* @param Subpedido $subpedido
* @return SubpedidoResource
*/
public function syncFaltantesYSobrantes(Subpedido $subpedido): SubpedidoResource
{
$valid = request()->validate([
'cantidad' => ['integer', 'required'],
'producto_id' => [
'required',
Rule::in(Producto::all()->pluck('id')),
]
]);
$producto = Producto::find($valid['producto_id']);
if ($producto->bono)
abort(400, "No puede haber faltado un bono");
$cantidad = $valid['cantidad'];
if ($cantidad < 0) { // caso faltantes
$barrio = GrupoDeCompra::find($subpedido->grupo_de_compra_id);
$productoPedido = $barrio->productosPedidos()->where('producto_id', $producto->id)->first();
if (!$productoPedido)
abort(400, "No pueden faltar productos no pedidos");
$cantidadPedida = intval($productoPedido->cantidad_pedida);
if ($cantidadPedida + $cantidad < 0)
abort(400, 'No puede faltar más de lo pedido');
}
$subpedido->syncProducto($producto, $cantidad);
return new SubpedidoResource($subpedido);
}
}

View file

@ -65,6 +65,23 @@ class ComisionesController
return response()->download($files[0]);
}
public function descargarFaltantesYSobrantes()
{
try {
PedidosExportHelper::faltantesYSobrantes();
} catch (CannotInsertRecord|InvalidArgument|Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
$pattern = storage_path('csv/exports/faltantes-y-sobrantes-*.csv');
$files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
}
public function descargarNotas()
{
try {

View file

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Http\Resources\PedidoFaltantesYSobrantesResource;
use App\TipoPedido;
class FaltantesYSobrantesController extends Controller
{
public function pedido(GrupoDeCompra $gdc)
{
$tipoFaltantesYSobrantes = TipoPedido::firstOrCreate(['nombre' => 'faltantes_y_sobrantes']);
$pedido = $gdc->subpedidos()->firstOrCreate([
'nombre' => 'Faltantes y Sobrantes de ' . $gdc->nombre,
'tipo_pedido_id' => $tipoFaltantesYSobrantes->id,
]);
return response()->json(new PedidoFaltantesYSobrantesResource($pedido));
}
}

View file

@ -15,6 +15,14 @@ class GrupoDeCompraResource extends JsonResource
*/
public function toArray($request): array
{
$productos_cantidades = [];
$productos_pedidos = $this->productosPedidos();
foreach ($productos_pedidos as $productoPedido) {
$productos_cantidades[] = [
"id" => $productoPedido->producto_id,
"cantidad" => $productoPedido->cantidad_pedida
];
}
return [
'id' => $this->id,
'nombre' => $this->nombre,
@ -29,6 +37,7 @@ class GrupoDeCompraResource extends JsonResource
'total_a_transferir' => number_format($this->totalATransferir(),2),
'total_transporte' => number_format($this->totalTransporte()),
'cantidad_transporte' => number_format($this->cantidadTransporte()),
'productos_cantidades' => $productos_cantidades,
];
}
}

View file

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

View file

@ -109,7 +109,7 @@ class Subpedido extends Model
}
// Actualiza el pedido, agregando o quitando del subpedido según sea necesario. Debe ser llamado desde el controlador de subpedidos, luego de validar que los parámetros $producto y $cantidad son correctos. También calcula el subtotal por producto.
public function syncProducto(Producto $producto, int $cantidad, string $notas)
public function syncProducto(Producto $producto, int $cantidad, string $notas = "")
{
if ($cantidad) {
//si la cantidad es 1 o más se agrega el producto o actualiza la cantidad

View file

@ -0,0 +1,30 @@
<?php
use App\TipoPedido;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
TipoPedido::firstOrCreate(['nombre' => 'faltantes_y_sobrantes']);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$faltantesYSobrantes = TipoPedido::where('nombre', 'faltantes_y_sobrantes')->firstOrFail();
$faltantesYSobrantes->delete();
}
};

1
package-lock.json generated
View file

@ -4,7 +4,6 @@
"requires": true,
"packages": {
"": {
"name": "www",
"dependencies": {
"animate.css": "^4.1.1",
"bulma": "^0.9.4",

View file

@ -1,19 +1,30 @@
<template>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<tabs-secciones :tabs="tabs" :tabInicial="tabActiva"/>
<div class="block" id="pedidos-seccion"
:class="seccionActiva === 'pedidos-seccion' ? 'is-active' : 'is-hidden'">
<div class="block pb-6" id="pedidos-tabla-y-dropdown" v-if="hayPedidos">
<dropdown-descargar/>
<tabla-pedidos/>
<div>
<pedidos-main v-if="show_faltantes_y_sobrantes"/>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop" v-else>
<tabs-secciones :tabs="tabs" :tabInicial="tabActiva"/>
<div class="block" id="pedidos-seccion"
:class="seccionActiva === 'pedidos-seccion' ? 'is-active' : 'is-hidden'">
<div class="block pb-6" id="pedidos-tabla-y-dropdown" v-if="hayPedidos">
<div class="is-flex is-justify-content-space-between mb-3">
<button class="button is-danger" @click="abrirFaltantesYSobrantes">
<span class="icon">
<i class="fa fa-refresh"></i>
</span>
<span>Faltantes y Sobrantes</span>
</button>
<dropdown-descargar/>
</div>
<tabla-pedidos/>
</div>
<p class="has-text-centered" v-else>
Todavía no hay ningún pedido para administrar.
</p>
</div>
<div class="block pb-6" id="caracteristicas-seccion"
:class="seccionActiva === 'caracteristicas-seccion' ? 'is-active' : 'is-hidden'">
<caracteristicas-opcionales/>
</div>
<p class="has-text-centered" v-else>
Todavía no hay ningún pedido para administrar.
</p>
</div>
<div class="block pb-6" id="caracteristicas-seccion"
:class="seccionActiva === 'caracteristicas-seccion' ? 'is-active' : 'is-hidden'">
<caracteristicas-opcionales/>
</div>
</div>
</template>
@ -23,10 +34,14 @@ import CaracteristicasOpcionales from "./CaracteristicasOpcionales.vue";
import TabsSecciones from "../comunes/TabsSecciones.vue";
import DropdownDescargar from "./DropdownDescargar.vue";
import TablaPedidos from "./TablaPedidos.vue";
import { mapActions, mapGetters } from "vuex";
import { mapActions, mapGetters, mapMutations, mapState } from "vuex";
import Canasta from "../pedidos/Canasta.vue";
import PedidosMain from "../pedidos/PedidosMain.vue";
export default {
name: "AdminBody",
components: {
PedidosMain,
Canasta,
CaracteristicasOpcionales,
TabsSecciones,
DropdownDescargar,
@ -42,9 +57,12 @@ export default {
},
computed: {
...mapGetters('admin', ['hayPedidos']),
...mapState('ui', ['show_faltantes_y_sobrantes'])
},
methods: {
...mapActions('admin', ['getGrupoDeCompra']),
...mapActions('admin', ['getGrupoDeCompra', 'abrirFaltantesYSobrantes']),
...mapActions('ui', ['toast']),
...mapMutations('ui', ['toggleFaltantesYSobrantes']),
setSeccionActiva(tabId) {
this.tabActiva = tabId;
this.seccionActiva = tabId + "-seccion";

View file

@ -2,7 +2,11 @@
<div class="buttons is-right">
<div class="dropdown" :class="{'is-active': dropdownActivo}" @mouseleave="dropdownActivo = false">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu" :disabled="!hayAprobados" @click="dropdownActivo = !dropdownActivo">
<button class="button"
aria-haspopup="true"
aria-controls="dropdown-menu"
:disabled="!hayAprobados"
@click="dropdownActivo = !dropdownActivo">
<span class="icon is-small">
<i class="fas fa-download"></i>
</span>

View file

@ -18,71 +18,25 @@
/>
</tbody>
</table>
<table class="table is-striped is-bordered">
<tr>
<th colspan="2" class="has-background-black has-text-white has-text-centered">TOTALES</th>
</tr>
<tr>
<th>Total a recaudar:</th>
<td class="has-text-right">$ {{ devoluciones_habilitadas ? total_a_recaudar : total_sin_devoluciones }}</td>
</tr>
<tr>
<th>Total bonos barriales:</th>
<td class="has-text-right">$ {{ total_barrial }}</td>
</tr>
<tr v-if="devoluciones_habilitadas">
<th>Total devoluciones:</th>
<td class="has-text-right">- $ {{ total_devoluciones }}</td>
</tr>
<tr>
<th>Cantidad bonos de transporte:</th>
<td class="has-text-right">{{ cantidad_transporte }}</td>
</tr>
<tr>
<th>Total bonos de transporte:</th>
<td class="has-text-right">$ {{ total_transporte }}</td>
</tr>
<tr>
<th>Total de pedido:</th>
<td class="has-text-right">$ {{ total_de_pedido }}</td>
</tr>
<tr>
<th>{{ texto_saldo }}</th>
<td class="has-text-right"> $ {{ saldo }}</td>
</tr>
<tr>
<th>Total a transferir:</th>
<td class="has-text-right">$ {{ total_a_transferir }}</td>
</tr>
</table>
<tabla-totales/>
</div>
</template>
<script>
import FilaPedido from "./FilaPedido.vue";
import { mapGetters, mapState } from "vuex";
import TablaTotales from "./TablaTotales.vue";
export default {
components: {
TablaTotales,
FilaPedido
},
computed: {
...mapState('admin', [
"devoluciones_habilitadas",
"pedidos",
"total_a_recaudar",
"total_sin_devoluciones",
"total_barrial",
"total_devoluciones",
"cantidad_transporte",
"total_transporte",
"total_de_pedido",
"total_a_transferir",
"saldo",
]),
...mapGetters('admin', ['pedidosAprobados']),
texto_saldo() {
return this.saldo < 0 ? "Deuda:" : "Saldo a favor:";
}
},
}
</script>

View file

@ -0,0 +1,69 @@
<script>
import { mapGetters, mapState } from "vuex";
export default {
name: 'TablaTotales',
computed: {
...mapState('admin', [
"devoluciones_habilitadas",
"total_a_recaudar",
"total_sin_devoluciones",
"total_barrial",
"total_devoluciones",
"cantidad_transporte",
"total_transporte",
"total_de_pedido",
"total_a_transferir",
"saldo",
]),
...mapGetters('admin', ['pedidosAprobados']),
texto_saldo() {
return this.saldo < 0 ? "Deuda:" : "Saldo a favor:";
}
}
}
</script>
<template>
<table class="table is-striped is-bordered">
<tr>
<th colspan="2" class="has-background-black has-text-white has-text-centered">TOTALES</th>
</tr>
<tr>
<th>Total a recaudar:</th>
<td class="has-text-right">$ {{ devoluciones_habilitadas ? total_a_recaudar : total_sin_devoluciones }}</td>
</tr>
<tr>
<th>Total bonos barriales:</th>
<td class="has-text-right">$ {{ total_barrial }}</td>
</tr>
<tr v-if="devoluciones_habilitadas">
<th>Total devoluciones:</th>
<td class="has-text-right">- $ {{ total_devoluciones }}</td>
</tr>
<tr>
<th>Cantidad bonos de transporte:</th>
<td class="has-text-right">{{ cantidad_transporte }}</td>
</tr>
<tr>
<th>Total bonos de transporte:</th>
<td class="has-text-right">$ {{ total_transporte }}</td>
</tr>
<tr>
<th>Total de pedido:</th>
<td class="has-text-right">$ {{ total_de_pedido }}</td>
</tr>
<tr>
<th>{{ texto_saldo }}</th>
<td class="has-text-right"> $ {{ saldo }}</td>
</tr>
<tr>
<th>Total a transferir:</th>
<td class="has-text-right">$ {{ total_a_transferir }}</td>
</tr>
</table>
</template>
<style>
</style>

View file

@ -14,17 +14,8 @@
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="/comisiones/pedidos/descargar" class="dropdown-item">
Pedidos por barrio en csv
</a>
<a href="/comisiones/pedidos/notas" class="dropdown-item">
Notas por barrio en csv
</a>
<a href="/comisiones/pedidos/pdf" class="dropdown-item">
Pedidos por barrio en pdf
</a>
<a href="/comisiones/pedidos/ollas" class="dropdown-item">
Pedidos de ollas en csv
<a v-for="(opcion,i) in opciones" :key="i" :href="opcion.href" class="dropdown-item">
{{ opcion.nombre }}
</a>
</div>
</div>
@ -36,7 +27,14 @@
export default {
data() {
return {
dropdownActivo: false
dropdownActivo: false,
opciones: [
{ nombre: 'Pedidos por barrio en csv', href: '/comisiones/pedidos/descargar' },
{ 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' },
{ nombre: 'Faltantes y sobrantes en csv', href: '/comisiones/faltantes-y-sobrantes' },
],
}
},
}

View file

@ -14,7 +14,7 @@
<th class="has-text-right">{{ cantidad_transporte }}</th>
<th class="has-text-right">{{ total_transporte }}</th>
</tr>
<tr v-if="grupo_de_compra.devoluciones_habilitadas && !aprobado">
<tr v-if="mostrarDevoluciones">
<th><p>Devoluciones</p></th>
<td>
<abbr :title="devoluciones_notas">{{ notas_abreviadas }}</abbr>
@ -49,6 +49,7 @@ import { mapMutations, mapState } from "vuex";
export default {
components: { ProductoRow },
computed: {
...mapState('login', ["rol"]),
...mapState('pedido',[
"grupo_de_compra",
"productos",
@ -65,6 +66,9 @@ export default {
mostrar_tabla() {
return this.productos?.length !== 0;
},
mostrarDevoluciones() {
return this.rol === "barrio" && this.grupo_de_compra.devoluciones_habilitadas && !this.aprobado;
}
},
methods: {
...mapMutations('ui',["toggleDevoluciones"]),

View file

@ -2,7 +2,7 @@
<div class="is-justify-content-center">
<div class="field has-addons is-justify-content-center contador">
<div class="control">
<button class="button is-small" :disabled="cantidadControl < 1" v-if="!aprobado" @click.capture="decrementar();">
<button class="button is-small" :disabled="!puedeDecrementar" v-if="!aprobado" @click.capture="decrementar();">
<i class="fa fa-solid fa-minus"></i>
</button>
</div>
@ -19,7 +19,7 @@
<i class="fa fa-solid fa-plus"></i>
</button>
</div>
<button :disabled="!hayCambios || cantidadControl < 0" v-if="!aprobado" class="button is-small is-success ml-1" @click="confirmar()">
<button :disabled="!puedeConfirmar" v-if="!aprobado" class="button is-small is-success ml-1" @click="confirmar()">
<span class="icon">
<i class="fas fa-check"></i>
</span>
@ -82,8 +82,13 @@ export default {
this.actualizar();
},
computed: {
...mapState('login', ["rol"]),
...mapState('pedido', ["aprobado"]),
...mapGetters('pedido', ["enChismosa", "cantidad", "notas"]),
...mapGetters('admin', ["cantidadBarrial"]),
cantidadTotal() {
return this.cantidadBarrial(this.producto_id);
},
cantidadEnChismosa() {
return this.cantidad(this.producto_id);
},
@ -99,6 +104,14 @@ export default {
faltaNotas() {
return this.requiere_notas && this.cantidadControl > 0 && !this.notasControl;
},
puedeDecrementar() {
const min = this.rol === "admin_barrio" ? -1*this.cantidadTotal : 0;
return this.cantidadControl > min;
},
puedeConfirmar() {
const min = this.rol === "admin_barrio" ? -1*this.cantidadTotal : 0;
return this.hayCambios && this.cantidadControl >= min;
}
},
methods: {
...mapActions('pedido', ["modificarChismosa"]),

View file

@ -17,6 +17,7 @@ const state: AdminState = {
total_transporte: undefined,
cantidad_transporte: undefined,
saldo: undefined,
productos_cantidades: [],
};
const mutations = {
@ -40,6 +41,8 @@ const mutations = {
state.total_transporte = grupo_de_compra.total_transporte;
state.cantidad_transporte = grupo_de_compra.cantidad_transporte;
state.saldo = grupo_de_compra.saldo;
state.productos_cantidades = grupo_de_compra.productos_cantidades
.map(pc => ({...pc, cantidad: parseInt(pc.cantidad)}));
},
toggleCaracteristica(state, { caracteristica_id }) {
state[`${caracteristica_id}_habilitadas`] = !state[`${caracteristica_id}_habilitadas`];
@ -62,6 +65,14 @@ const actions = {
await axios.post(`/api/grupos-de-compra/${state.grupo_de_compra_id}/${caracteristica_id}`);
commit('toggleCaracteristica', { caracteristica_id: caracteristica_id })
},
async abrirFaltantesYSobrantes({ dispatch }) {
const { data } = await axios.get(`/admin/${state.grupo_de_compra_id}/faltantes-y-sobrantes`)
const pedido = { pedido_id: data.id };
dispatch("productos/init", null, { root: true });
dispatch("pedido/elegirPedido", pedido, { root: true });
dispatch("ui/migasFaltantesYSobrantes", null, { root: true });
dispatch("ui/toggleFaltantesYSobrantes", null, { root: true });
}
};
const getters = {
@ -85,6 +96,10 @@ const getters = {
},
getCaracteristica() {
return (caracteristica) => state[`${caracteristica}_habilitadas`];
},
cantidadBarrial() {
return (producto_id) =>
state.productos_cantidades.find(pc => pc.id === producto_id)?.cantidad ?? 0;
}
};

View file

@ -17,6 +17,7 @@ export interface Barrio {
total_transporte?: number,
cantidad_transporte?: number,
saldo?: number,
productos_cantidades: ProductoCantidad[],
}
export interface Pedido {
@ -31,3 +32,8 @@ export interface Pedido {
devoluciones_total: number,
devoluciones_notas: string
}
export interface ProductoCantidad {
id: number,
cantidad: number,
}

View file

@ -4,6 +4,7 @@ const state: UiState = {
show_chismosa: false,
show_devoluciones: false,
show_tags: true,
show_faltantes_y_sobrantes: false,
burger_activa: false,
tags_interactuada: false,
migas: [{ nombre: 'Pedidos', action: 'pedido/resetear' }],
@ -24,6 +25,9 @@ const mutations = {
state.tags_interactuada = manual;
state.show_tags = !state.show_tags;
},
toggleFaltantesYSobrantes(state) {
state.show_faltantes_y_sobrantes = !state.show_faltantes_y_sobrantes;
},
toggleBurger(state) {
state.burger_activa = !state.burger_activa;
},
@ -41,6 +45,9 @@ const mutations = {
migasOllas(state) {
state.migas.reverse().pop();
},
migasFaltantesYSobrantes(state) {
state.migas[0] = { nombre: 'Administración', action: 'ui/toggleFaltantesYSobrantes' };
}
};
const actions = {
@ -80,6 +87,12 @@ const actions = {
migasOllas({ commit }) {
commit("migasOllas");
},
toggleFaltantesYSobrantes({ commit }) {
commit("toggleFaltantesYSobrantes");
},
migasFaltantesYSobrantes({ commit }) {
commit("migasFaltantesYSobrantes");
}
};
export default {

View file

@ -3,6 +3,8 @@ export interface UiState {
show_chismosa: boolean,
show_devoluciones: boolean,
show_tags: boolean,
show_faltantes_y_sobrantes: boolean,
burger_activa: boolean,
tags_interactuada: boolean,
migas: Miga[],
canasta_actual?: DatosCanasta,

View file

@ -29,7 +29,7 @@ Route::middleware(['auth'])->group(function () {
Route::get('/user/grupo_de_compra', 'UserController@grupoDeCompra');
});
Route::middleware(['auth', 'role:barrio'])->group(function() {
Route::middleware(['auth'])->group(function() {
Route::get('/pedido', 'RouteController@main')->name('pedido');
Route::get('/pedido/sesion', 'SessionController@fetch');
Route::post('/pedido/sesion', 'SessionController@store');
@ -38,27 +38,28 @@ Route::middleware(['auth', 'role:barrio'])->group(function() {
Route::get('/admin/login', 'AdminController@show')->name('admin.login');
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');
Route::middleware(['auth', 'role:admin_barrio'])->prefix('admin')->group(function () {
Route::get('/', 'RouteController@main')->name('admin');
Route::get('/exportar-planillas-a-pdf/{gdc}', 'AdminController@exportarPedidosAPdf');
Route::get('/exportar-pedido-a-csv/{gdc}', 'AdminController@exportarPedidoACSV');
Route::get('/exportar-pedido-con-nucleos-a-csv/{gdc}', 'AdminController@exportarPedidoConNucleosACSV');
Route::get('/{gdc}/faltantes-y-sobrantes', 'FaltantesYSobrantesController@pedido');
});
Route::get('/comisiones/login', 'ComisionesController@show')->name('comisiones.login');
Route::middleware(['auth', 'role:comision'])->group( function() {
Route::get('/comisiones', 'RouteController@main')->name('comisiones');
Route::get('/comisiones/pedidos/descargar', 'ComisionesController@descargarPedidos')->name('comisiones.pedidos.descargar');
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/ollas', 'ComisionesController@descargarPedidosDeOllas')->name('comisiones.pedidos.ollas');
Route::get('/comisiones/canasta/ejemplo', 'ComisionesController@descargarCanastaEjemplo')->name('comisiones.canasta.ejemplo');
Route::post('/comisiones/canasta', 'ComisionesController@cargarCanasta')->name('comisiones.canasta');
Route::get('/comisiones/saldos/ejemplo', 'ComisionesController@descargarSaldosEjemplo')->name('comisiones.saldos.ejemplo');
Route::post('/comisiones/saldos', 'ComisionesController@cargarSaldos')->name('comisiones.saldos');
Route::put('/comisiones/parametros/{parametro_id}', 'ComisionesController@modificarParametros');
Route::middleware(['auth', 'role:comision'])->prefix('comisiones')->group( function() {
Route::get('/', 'RouteController@main')->name('comisiones');
Route::get('/pedidos/descargar', 'ComisionesController@descargarPedidos')->name('comisiones.pedidos.descargar');
Route::get('/pedidos/notas', 'ComisionesController@descargarNotas')->name('comisiones.pedidos.notas');
Route::get('/pedidos/pdf', 'ComisionesController@pdf')->name('comisiones.pedidos.pdf');
Route::get('/pedidos/ollas', 'ComisionesController@descargarPedidosDeOllas')->name('comisiones.pedidos.ollas');
Route::get('/canasta/ejemplo', 'ComisionesController@descargarCanastaEjemplo')->name('comisiones.canasta.ejemplo');
Route::post('/canasta', 'ComisionesController@cargarCanasta')->name('comisiones.canasta');
Route::get('/saldos/ejemplo', 'ComisionesController@descargarSaldosEjemplo')->name('comisiones.saldos.ejemplo');
Route::post('/saldos', 'ComisionesController@cargarSaldos')->name('comisiones.saldos');
Route::put('/parametros/{parametro_id}', 'ComisionesController@modificarParametros')->name('comisiones.parametros.modificar');
Route::get('/faltantes-y-sobrantes', 'ComisionesController@descargarFaltantesYSobrantes')->name('comisiones.faltantesYsobrantes');
});
Route::get('/ollas/login', 'OllasController@show')->name('ollas.login');