Merge pull request 'Saldos' (#46) from funcion/saldos into funcion/refactor-general

Reviewed-on: nathalie/pedi2#46
This commit is contained in:
Alejandro Tasistro 2025-06-19 21:09:26 -03:00
commit 1115764d81
30 changed files with 592 additions and 71 deletions

View file

@ -11,10 +11,11 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use League\Csv\Exception;
class GrupoDeCompra extends Model
{
protected $fillable = ["nombre", "region", "devoluciones_habilitadas"];
protected $fillable = ["nombre", "region", "devoluciones_habilitadas", "saldo"];
protected $table = 'grupos_de_compra';
public function subpedidos(): HasMany
@ -69,11 +70,17 @@ class GrupoDeCompra extends Model
return $total;
}
public function totalATransferir()
public function totalDePedido()
{
return $this->totalCentralesQueNoPaganTransporte()
+ $this->totalCentralesQuePaganTransporte()
+ $this->totalTransporte();
+ $this->totalTransporte()
;
}
public function totalATransferir()
{
return $this->totalDePedido() - $this->saldo;
}
public function totalCentralesQueNoPaganTransporte()
@ -159,6 +166,10 @@ class GrupoDeCompra extends Model
}
//Asume que los productos están gruadados en orden de fila
/**
* @throws Exception
*/
public static function obtenerTemplateDeFilasVacias(int $columns): array
{
$productosFilaID = Producto::productosFilaID();
@ -176,6 +187,9 @@ class GrupoDeCompra extends Model
return $template;
}
/**
* @throws Exception
*/
public function exportarPedidoEnCSV()
{
$records = $this->generarColumnaCantidades();
@ -184,6 +198,9 @@ class GrupoDeCompra extends Model
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-' . $fecha . '.csv', $records);
}
/**
* @throws Exception
*/
public function generarColumnaCantidades(): array
{
$productos_en_pedido = $this->productosPedidos();
@ -206,6 +223,9 @@ class GrupoDeCompra extends Model
return $records;
}
/**
* @throws Exception
*/
public function exportarPedidoConNucleosEnCSV()
{
$productos_en_pedido = $this->productosPedidos();
@ -284,4 +304,9 @@ class GrupoDeCompra extends Model
->get()
->keyBy('producto_id');
}
public function setSaldo(float $saldo) {
$this->saldo = $saldo;
$this->save();
}
}

View file

@ -9,6 +9,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use League\Csv\Exception;
class CanastaHelper
{
@ -25,7 +26,8 @@ class CanastaHelper
$log = CanastaLog::where('descripcion', self::CANASTA_CARGADA)
->orderBy('created_at', 'desc')
->first();
$nombre = str_replace("csv/canastas/", "", $log->path);
$nombre = str_replace(storage_path(), "", $log->path);
$nombre = str_replace("/csv/canastas/", "", $nombre);
$result["nombre"] = str_replace(".csv", "", $nombre);
$result["fecha"] = $log->created_at;
return $result;
@ -38,17 +40,22 @@ class CanastaHelper
$nombre = $data->getClientOriginalName();
$data->move(storage_path($path), $nombre);
$storage_path = storage_path($path);
$data->move($storage_path, $nombre);
self::log($path . $nombre, self::ARCHIVO_SUBIDO);
self::log($storage_path . $nombre, self::ARCHIVO_SUBIDO);
return $nombre;
}
/**
* @throws Exception
*/
public static function cargarCanasta($archivo) {
self::limpiarTablas();
$registros = CsvHelper::getRecords($archivo);
$registros = CsvHelper::getRecords($archivo, "No se pudo leer el archivo.");
$toInsert = [];
$categoria = '';

View file

@ -13,8 +13,11 @@ use League\Csv\Writer;
class CsvHelper
{
public static function getRecords($filePath): Iterator {
$csv = Reader::createFromPath(storage_path($filePath));
/**
* @throws Exception
*/
public static function getRecords($filePath, $message): Iterator {
$csv = Reader::createFromPath($filePath);
try {
$csv->setDelimiter("|");
$csv->setEnclosure("'");
@ -22,7 +25,7 @@ class CsvHelper
return $csv->getRecords();
} catch (InvalidArgument|Exception $e) {
Log::error($e->getMessage());
return null;
throw new Exception($message, $e);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Helpers;
use App\CanastaLog;
use Illuminate\Support\Facades\Log;
use League\Csv\Exception;
class TransporteHelper
{
@ -20,6 +21,9 @@ class TransporteHelper
return self::cantidadTransporte($monto) * self::COSTO_TRANSPORTE;
}
/**
* @throws Exception
*/
public static function filaTransporte()
{
$ultimaCanasta = CanastaLog::where('descripcion', CanastaHelper::CANASTA_CARGADA)
@ -27,12 +31,14 @@ class TransporteHelper
->pluck('path')
->first();
$registros = CsvHelper::getRecords($ultimaCanasta);
$registros = CsvHelper::getRecords($ultimaCanasta, "No se encontró la ultima canasta.");
$error = 'No hay fila de tipo T en la planilla: ' . $ultimaCanasta;
foreach ($registros as $key => $registro)
if ($registro[CanastaHelper::TIPO] == 'T') return $key;
if ($registro[CanastaHelper::TIPO] == 'T')
return $key;
Log::error('No hay fila de tipo T en la planilla: ' . $ultimaCanasta);
return null;
Log::error($error);
throw new Exception($error);
}
}

View file

@ -3,7 +3,7 @@
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use League\Csv\Exception;
class AdminController extends Controller
{
@ -20,9 +20,13 @@ class AdminController extends Controller
$gdc->exportarPedidosAPdf();
}
public function exportarPedidoACSV(GrupoDeCompra $gdc): BinaryFileResponse
public function exportarPedidoACSV(GrupoDeCompra $gdc)
{
$gdc->exportarPedidoEnCSV();
try {
$gdc->exportarPedidoEnCSV();
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()]);
}
$pattern = storage_path('csv/exports/'. $gdc->nombre . '-*.csv');
$files = glob($pattern);
@ -33,9 +37,13 @@ class AdminController extends Controller
return response()->download($files[0]);
}
public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc): BinaryFileResponse
public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc)
{
$gdc->exportarPedidoConNucleosEnCSV();
try {
$gdc->exportarPedidoConNucleosEnCSV();
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()]);
}
$pattern = storage_path('csv/exports/'.$gdc->nombre.'-completo-*.csv');
$files = glob($pattern);

View file

@ -4,9 +4,8 @@ namespace App\Http\Controllers\Api;
use App\GrupoDeCompra;
use App\Http\Controllers\Controller;
use App\Http\Resources\GrupoDeCompraReducido;
use App\Http\Resources\GrupoDeCompraComisionesResource;
use App\Http\Resources\GrupoDeCompraResource;
use http\Env\Request;
class GrupoDeCompraController extends Controller
{
@ -32,4 +31,18 @@ class GrupoDeCompraController extends Controller
GrupoDeCompra::find($gdc)->toggleDevoluciones();
return response()->noContent();
}
public function setSaldo(int $gdc) {
$valid = request()->validate([
'saldo' => ['required', 'min:0'],
]);
$grupoDeCompra = GrupoDeCompra::find($gdc);
$grupoDeCompra->setSaldo($valid['saldo']);
return response()->noContent();
}
public function saldos()
{
return GrupoDeCompraComisionesResource::collection(GrupoDeCompra::all());
}
}

View file

@ -4,23 +4,32 @@ namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Helpers\CanastaHelper;
use App\Helpers\CsvHelper;
use App\Http\Resources\GrupoDeCompraResource;
use App\Producto;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use League\Csv\Exception;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ComisionesController
{
const CANASTAS_PATH = 'csv/canastas/';
const BARRIO = "Barrio";
const SALDO = "Saldo";
public function show()
{
return view('auth/login');
}
public function descargarPedidos(): BinaryFileResponse
public function descargarPedidos()
{
Producto::planillaTotales();
try {
Producto::planillaTotales();
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
$pattern = storage_path('csv/exports/pedidos-por-barrio-*.csv');
$files = glob($pattern);
@ -55,7 +64,11 @@ class ComisionesController
]);
$nombre = CanastaHelper::guardarCanasta($request->file('data'), self::CANASTAS_PATH);
CanastaHelper::cargarCanasta(self::CANASTAS_PATH . $nombre);
try {
CanastaHelper::cargarCanasta(storage_path(self::CANASTAS_PATH . $nombre));
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
return response()->json([
'message' => 'Canasta cargada exitosamente',
@ -64,7 +77,31 @@ class ComisionesController
public function descargarCanastaEjemplo(): BinaryFileResponse
{
$file = storage_path('csv/productos.csv');
$file = resource_path('csv/productos.csv');
return response()->download($file);
}
public function cargarSaldos(Request $request): JsonResponse
{
$request->validate([
'data' => 'required|file|mimes:csv,txt|max:2048',
]);
$file = $request->file('data')->getPathname();
try {
$records = CsvHelper::getRecords($file, "No se pudo leer el archivo.");
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
foreach ($records as $record) {
$barrio = $record[self::BARRIO];
$saldo = $record[self::SALDO];
GrupoDeCompra::where('nombre', $barrio)
->update(['saldo' => $saldo]);
}
return response()->json(GrupoDeCompraResource::collection(GrupoDeCompra::all()));
}
}

View file

@ -3,7 +3,7 @@
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Http\Resources\GrupoDeCompraReducido;
use App\Http\Resources\GrupoDeCompraPedidoResource;
use App\Http\Resources\GrupoDeCompraResource;
use App\UserRole;
use Illuminate\Http\Request;
@ -22,7 +22,7 @@ class UserController extends Controller
$grupo_de_compra = GrupoDeCompra::find($user->grupo_de_compra_id);
switch (UserRole::findOrFail($user->role_id)->nombre) {
case 'barrio':
$result['grupo_de_compra'] = new GrupoDeCompraReducido($grupo_de_compra);
$result['grupo_de_compra'] = new GrupoDeCompraPedidoResource($grupo_de_compra);
break;
case 'admin_barrio':
$result['grupo_de_compra'] = new GrupoDeCompraResource($grupo_de_compra);

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GrupoDeCompraComisionesResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request): array {
return [
'id' => $this->id,
'nombre' => $this->nombre,
'saldo' => $this->saldo,
];
}
}

View file

@ -4,7 +4,7 @@ namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GrupoDeCompraReducido extends JsonResource
class GrupoDeCompraPedidoResource extends JsonResource
{
/**
* Transform the resource into an array.

View file

@ -21,9 +21,11 @@ class GrupoDeCompraResource extends JsonResource
'devoluciones_habilitadas' => $this->devoluciones_habilitadas,
'pedidos' => SubpedidoResource::collection($this->subpedidos),
'total_a_recaudar' => number_format($this->totalARecaudar(),2),
'saldo' => number_format($this->saldo, 2, ".", ""),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),
'total_barrial' => number_format($this->totalBarrial(),2),
'total_devoluciones' => number_format($this->totalDevoluciones(),2),
'total_de_pedido' => number_format($this->totalDePedido(),2),
'total_a_transferir' => number_format($this->totalATransferir(),2),
'total_transporte' => number_format($this->totalTransporte()),
'cantidad_transporte' => number_format($this->cantidadTransporte()),

View file

@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use League\Csv\Exception;
class Producto extends Model
{
@ -75,6 +76,9 @@ class Producto extends Model
->get();
}
/**
* @throws Exception
*/
static public function planillaTotales()
{
$headers = ['Producto'];

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarSaldosABarrios extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Agregar columna 'saldo' a la tabla 'grupos_de_compra'
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->double('saldo', 10, 2)->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// Remover columna 'saldo' de la tabla 'grupos_de_compra'
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->dropColumn('saldo');
});
}
}

View file

@ -11,9 +11,10 @@ class CanastaSeeder extends Seeder
* Run the database seeds.
*
* @return void
* @throws \League\Csv\Exception
*/
public function run()
{
CanastaHelper::cargarCanasta(self::ARCHIVO_DEFAULT);
CanastaHelper::cargarCanasta(resource_path(self::ARCHIVO_DEFAULT));
}
}

View file

@ -13,10 +13,11 @@ class GrupoDeCompraSeeder extends Seeder
* Run the database seeds.
*
* @return void
* @throws \League\Csv\Exception
*/
public function run()
{
$registros = CsvHelper::getRecords('csv/barrios.csv');
$registros = CsvHelper::getRecords(resource_path('csv/barrios.csv'), 'No se pudo leer la planilla de barrios.');
$gdcToInsert = [];
$usersToInsert = [];
$roles = UserRole::where('nombre', 'barrio')->orWhere('nombre', 'admin_barrio')->get();

View file

@ -43,7 +43,15 @@
<td class="has-text-right">$ {{ total_transporte }}</td>
</tr>
<tr>
<th>Total a depositar:</th>
<th>Total de pedido:</th>
<td class="has-text-right">$ {{ total_de_pedido }}</td>
</tr>
<tr>
<th>Saldo a favor:</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>
@ -67,7 +75,9 @@ export default {
"total_devoluciones",
"cantidad_transporte",
"total_transporte",
"total_de_pedido",
"total_a_transferir",
"saldo",
]),
...mapGetters('admin', ['pedidosAprobados']),
},

View file

@ -1,44 +1,22 @@
<template>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<comunes-tabs-secciones :tabs="tabs" :tabInicial="tabActiva"></comunes-tabs-secciones>
<div class="block pb-6" id="pedidos-comisiones-seccion"
<tabs-secciones :tabs="tabs" :tabInicial="tabActiva"/>
<div class="block pb-6"
id="pedidos-comisiones-seccion"
:class="seccionActiva === 'pedidos-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="pedidos-comisiones-tabla-y-dropdown">
<dropdown-descargar/>
</div>
</div>
<div class="block pb-6" id="canasta-comisiones-seccion"
<div class="block pb-6"
id="canasta-comisiones-seccion"
:class="seccionActiva === 'canasta-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="canasta-comisiones-seccion">
<article class="message is-warning">
<div class="message-header">
<p>Formato de la canasta</p>
</div>
<div class="message-body">
<div class="content">
La planilla de la canasta tiene que tener el siguiente formato para que la aplicación la lea correctamente:
<ul>
<li> Los precios deben usar punto y no coma decimal </li>
<li> El nombre de las columnas deben ser "Tipo", "Producto", y "Precio" respectivamente </li>
<li> Las celdas deben separarse con '|' </li>
<li> No puede haber "enters" en ninguna celda </li>
<li> El bono de transporte debe tener tipo 'T' </li>
</ul>
<a class="has-text-info" href="/comisiones/canasta/ejemplo">Planilla de ejemplo.</a>
<article class="message is-danger mt-2">
<div class="message-body">
<div class="content">
Cuidado! Cargar una nueva canasta elimina todos los pedidos de la aplicación.
</div>
</div>
</article>
</div>
</div>
</article>
<div class="buttons is-right">
<canasta-input/>
</div>
</div>
<canasta-seccion/>
</div>
<div class="block pb-6"
id="saldos-comisiones-seccion"
:class="seccionActiva === 'saldos-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<saldos-seccion/>
</div>
</div>
</template>
@ -46,22 +24,29 @@
<script>
import TabsSecciones from "../comunes/TabsSecciones.vue";
import DropdownDescargar from "./DropdownDescargar.vue";
import CanastaInput from "./CanastaInput.vue";
import InputFileButton from "../comunes/InputFileButton.vue";
import CanastaSeccion from "./canasta/CanastaSeccion.vue";
import SaldosSeccion from "./saldos/SaldosSeccion.vue";
import { mapActions } from "vuex";
export default {
name: "ComisionesBody",
components: {
SaldosSeccion,
CanastaSeccion,
TabsSecciones,
DropdownDescargar,
CanastaInput,
InputFileButton,
},
data() {
return {
tabs: [{ id: "pedidos-comisiones", nombre: "Pedidos" },
{ id: "canasta-comisiones", nombre: "Canasta" }],
tabs: [
{ id: "pedidos-comisiones", nombre: "Pedidos" },
{ id: "canasta-comisiones", nombre: "Canasta" },
{ id: "saldos-comisiones", nombre: "Saldos" },
],
tabActiva: "pedidos-comisiones",
seccionActiva: "pedidos-comisiones-seccion",
archivo: undefined,
}
},
methods: {
@ -69,6 +54,6 @@ export default {
this.tabActiva = tabId;
this.seccionActiva = tabId + "-seccion";
},
}
},
}
</script>

View file

@ -0,0 +1,46 @@
<script>
import { defineComponent } from "vue";
import CanastaInput from "./CanastaInput.vue";
export default defineComponent({
components: { CanastaInput },
name: "CanastaSeccion",
})
</script>
<template>
<div>
<article class="message is-warning">
<div class="message-header">
<p>Formato de la canasta</p>
</div>
<div class="message-body">
<div class="content">
La planilla de la canasta tiene que tener el siguiente formato para que la aplicación la lea correctamente:
<ul>
<li> Los precios deben usar punto y no coma decimal </li>
<li> El nombre de las columnas deben ser "Tipo", "Producto", y "Precio" respectivamente </li>
<li> Las celdas deben separarse con '|' </li>
<li> No puede haber "enters" en ninguna celda </li>
<li> El bono de transporte debe tener tipo 'T' </li>
</ul>
<a class="has-text-info" href="/comisiones/canasta/ejemplo">Planilla de ejemplo.</a>
<article class="message is-danger mt-2">
<div class="message-body">
<div class="content">
Cuidado! Cargar una nueva canasta elimina todos los pedidos de la aplicación.
</div>
</div>
</article>
</div>
</div>
</article>
<div class="buttons">
<canasta-input/>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,71 @@
<script>
import { mapActions, mapGetters, mapState } from "vuex";
export default {
name: "SaldoRow",
props: {
grupo_de_compra: {
type: Object,
required: true,
}
},
data() {
return {
saldoControl: 0,
inputSaldoInteractuado: false,
};
},
mounted() {
this.saldoControl = this.grupo_de_compra.saldo;
},
watch: {
lastFetch() {
this.saldoControl = this.grupo_de_compra.saldo;
},
},
methods: {
...mapActions("comisiones", ["setSaldo"]),
async confirmarSaldo() {
await this.setSaldo({
gdc_id: this.grupo_de_compra.id,
saldo: this.saldoControl,
});
this.inputSaldoInteractuado = false;
},
},
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>
<td>
<input :id="`saldo-input-${grupo_de_compra.id}`"
v-model="saldoControl"
class="input is-small"
type="number"
style="text-align: center"
@input="inputSaldoInteractuado = true">
</td>
<td>
<button :disabled="!(inputSaldoInteractuado && saldoModificado)"
class="button is-small is-success ml-1"
@click="confirmarSaldo">
<span class="icon">
<i class="fas fa-check"/>
</span>
</button>
</td>
</tr>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,76 @@
<script>
import InputFileButton from "../../comunes/InputFileButton.vue";
import { mapActions } from "vuex";
import TablaSaldos from "./TablaSaldos.vue";
export default {
name: "SaldosSeccion",
components: { TablaSaldos, InputFileButton },
data() {
return {
archivo: undefined,
show_saldos_file_dialog: true,
};
},
methods: {
...mapActions('ui',["toast"]),
...mapActions('comisiones', ['cargarSaldos']),
async archivoSubido(event) {
event.component.cargando = true;
const formData = new FormData();
formData.append('data',event.archivo);
await this.cargarSaldos(formData);
event.component.cargando = false;
},
toggleSaldosFileDialog() {
this.show_saldos_file_dialog = !this.show_saldos_file_dialog;
},
},
}
</script>
<template>
<div>
<div class="columns">
<div class="column">
<article class="message is-warning">
<div class="message-header">
<p>
<span class="icon is-small"
@click="toggleSaldosFileDialog">
<i :class="`fas ${!show_saldos_file_dialog ? 'fa-angle-down' : 'fa-angle-up'}`" aria-hidden="true"></i>
</span>
Cargar saldos
</p>
</div>
<div class="message-body" v-if="show_saldos_file_dialog">
<div class="content">
La planilla de saldos tiene que tener el siguiente formato para que la aplicación la lea correctamente:
<ul>
<li>Los valores deben usar punto y no coma decimal</li>
<li>El nombre de las columnas deben ser "Barrio" y "Saldo"</li>
<li>Las celdas deben separarse con '|'</li>
<li>No puede haber "enters" en ninguna celda</li>
<li>El nombre de los barrios debe estar exactamente igual que como est&aacute;n configurados en esta aplicacai&oacute;n</li>
</ul>
<article class="message is-danger mt-2">
<div class="message-body">
<div class="content">
Al cargar un archivo, se reemplazaran los saldos de los barrios que éste contenga, el resto quedará sin cambiar.
</div>
</div>
</article>
</div>
<input-file-button text="Subir archivo" @archivo-subido="archivoSubido" />
</div>
</article>
</div>
<div class="column">
<tabla-saldos/>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,38 @@
<script>
import { mapActions, mapState } from "vuex";
import SaldoRow from "./SaldoRow.vue";
export default {
name: "TablaSaldos",
components: { SaldoRow },
async mounted() {
await this.getGruposDeCompra();
},
methods: {
...mapActions("comisiones", ["getGruposDeCompra"]),
},
computed: {
...mapState("comisiones", ["grupos_de_compra"]),
}
}
</script>
<template>
<table class="table container">
<thead>
<tr>
<th>Barrio</th>
<th>Saldo</th>
</tr>
</thead>
<tbody>
<saldo-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,56 @@
<template>
<div class="file has-name">
<label class="file-label">
<input
class="file-input"
type="file"
name="canasta"
@change="archivoSubido"
/>
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-cloud-upload-alt"></i>
</span>
<span class="file-label">{{ text }}</span>
</span>
<span class="file-name" v-if="cargando">
{{ 'Cargando ' + archivo.nombre }}
</span>
</label>
</div>
</template>
<script>
export default {
name: "InputFileButton",
props: {
text: {
type: String,
required: true,
},
},
data() {
return {
archivo: null,
cargando: false,
};
},
methods: {
archivoSubido(event) {
const archivo = event.target.files[0];
if (archivo) {
this.archivo = { data: archivo, nombre: archivo.name };
this.$emit("archivo-subido", {
component: this,
archivo: archivo
});
this.cargando = true;
}
},
},
};
</script>
<style scoped>
</style>

View file

@ -1,5 +1,5 @@
<template>
<div id="pedidos-body">
<div id="pedidos-body" class="pb-6 mb-6">
<pedido-select v-if="!pedidoDefinido"/>
<div v-else>
<nav-migas/>

View file

@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuex from 'vuex';
import admin from "./modules/admin";
import comisiones from "./modules/comisiones";
import login from "./modules/login";
import pedido from "./modules/pedido";
import productos from "./modules/productos";
@ -11,6 +12,7 @@ Vue.use(Vuex);
export default new Vuex.Store({
modules: {
admin,
comisiones,
login,
pedido,
productos,

View file

@ -10,9 +10,11 @@ const state = {
total_sin_devoluciones: null,
total_barrial: null,
total_devoluciones: null,
total_de_pedido: null,
total_a_transferir: null,
total_transporte: null,
cantidad_transporte: null,
saldo: null,
};
const mutations = {
@ -26,9 +28,11 @@ const mutations = {
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_de_pedido = grupo_de_compra.total_de_pedido;
state.total_a_transferir = grupo_de_compra.total_a_transferir;
state.total_transporte = grupo_de_compra.total_transporte;
state.cantidad_transporte = grupo_de_compra.cantidad_transporte;
state.saldo = grupo_de_compra.saldo;
},
toggleCaracteristica(state, { caracteristica_id }) {
state[`${caracteristica_id}_habilitadas`] = !state[`${caracteristica_id}_habilitadas`];

View file

@ -0,0 +1,65 @@
import axios from "axios";
const state = {
lastFetch: undefined,
grupos_de_compra: [],
};
const mutations = {
setGruposDeCompra(state, { data }) {
state.grupos_de_compra = data;
state.lastFetch = new Date();
},
setSaldo(state, { gdc_id, saldo }) {
const barrio = state.grupos_de_compra.find(gdc => gdc.id === gdc_id);
const i = state.grupos_de_compra.indexOf(barrio);
state.grupos_de_compra[i].saldo = saldo;
},
};
const actions = {
async getGruposDeCompra({ commit }) {
const response = await axios.get('/api/grupos-de-compra/saldos');
commit('setGruposDeCompra', response.data);
},
async setSaldo({ commit, dispatch }, { gdc_id, saldo }) {
try {
await axios.post(
"api/grupos-de-compra/" + gdc_id + "/saldo",
{ saldo: saldo }
);
commit('setSaldo', { gdc_id: gdc_id, saldo: saldo });
dispatch("ui/toast", { mensaje: 'Saldo modificado con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: error }, { root: true });
}
},
async cargarSaldos({ commit, dispatch }, formData) {
try {
const response = await axios.post("/comisiones/saldos", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
commit('setGruposDeCompra', response);
dispatch("ui/toast", { mensaje: 'Saldos cargados con éxito' }, { root: true });
} catch (error) {
console.log(error);
dispatch("ui/error", { error: error }, { root: true });
}
},
};
const getters = {
saldo() {
return (gdc_id) => state.grupos_de_compra.find(gdc => gdc.id === gdc_id)?.saldo ?? 0;
},
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

View file

@ -20,8 +20,11 @@ Route::middleware('api')->group(function() {
Route::get('/canasta-actual', 'Api\CanastaController@canastaActual');
Route::prefix('grupos-de-compra')->group(function() {
Route::get('/', 'Api\GrupoDeCompraController@index');
Route::get('/saldos','Api\GrupoDeCompraController@saldos');
Route::get('/{grupoDeCompra}', 'Api\GrupoDeCompraController@show');
Route::post('/{gdc}/devoluciones', 'Api\GrupoDeCompraController@toggleDevoluciones');
Route::post('/{gdc}/saldo', 'Api\GrupoDeCompraController@setSaldo');
});
Route::prefix('subpedidos')->group(function () {

View file

@ -54,4 +54,5 @@ Route::middleware(['auth', 'role:comision'])->group( function() {
Route::get('/comisiones/pedidos/pdf', 'ComisionesController@pdf')->name('comisiones.pedidos.pdf');
Route::get('/comisiones/canasta/ejemplo', 'ComisionesController@descargarCanastaEjemplo')->name('comisiones.canasta.ejemplo');
Route::post('/comisiones/canasta', 'ComisionesController@cargarCanasta')->name('comisiones.canasta');
Route::post('/comisiones/saldos', 'ComisionesController@cargarSaldos')->name('comisiones.saldos');
});

1
run_watch.sh Executable file
View file

@ -0,0 +1 @@
sudo docker compose exec app npm run watch