From afddeadeac663ed2d0cc6d66d00340a32beac68f Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 14 Jun 2025 17:39:51 -0300 Subject: [PATCH 01/32] Agregado saldo a grupo de compra --- app/GrupoDeCompra.php | 12 +++++-- app/Http/Resources/GrupoDeCompraResource.php | 2 ++ ..._06_14_172643_agregar_saldos_a_barrios.php | 34 +++++++++++++++++++ .../js/components/admin/TablaPedidos.vue | 12 ++++++- resources/js/store/modules/admin.js | 4 +++ 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php diff --git a/app/GrupoDeCompra.php b/app/GrupoDeCompra.php index 12f789d..83cf6a6 100644 --- a/app/GrupoDeCompra.php +++ b/app/GrupoDeCompra.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Log; 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 +69,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() diff --git a/app/Http/Resources/GrupoDeCompraResource.php b/app/Http/Resources/GrupoDeCompraResource.php index ef16fb5..b19aa13 100644 --- a/app/Http/Resources/GrupoDeCompraResource.php +++ b/app/Http/Resources/GrupoDeCompraResource.php @@ -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()), diff --git a/database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php b/database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php new file mode 100644 index 0000000..653c3a1 --- /dev/null +++ b/database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php @@ -0,0 +1,34 @@ +double('saldo', 10, 2); + }); + } + + /** + * 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'); + }); + } +} diff --git a/resources/js/components/admin/TablaPedidos.vue b/resources/js/components/admin/TablaPedidos.vue index 2a46c20..246d762 100644 --- a/resources/js/components/admin/TablaPedidos.vue +++ b/resources/js/components/admin/TablaPedidos.vue @@ -43,7 +43,15 @@ $ {{ total_transporte }} - Total a depositar: + Total de pedido: + $ {{ total_de_pedido }} + + + Saldo a favor: + $ {{ saldo }} + + + Total a transferir: $ {{ total_a_transferir }} @@ -67,7 +75,9 @@ export default { "total_devoluciones", "cantidad_transporte", "total_transporte", + "total_de_pedido", "total_a_transferir", + "saldo", ]), ...mapGetters('admin', ['pedidosAprobados']), }, diff --git a/resources/js/store/modules/admin.js b/resources/js/store/modules/admin.js index 4ad44ee..26dbd52 100644 --- a/resources/js/store/modules/admin.js +++ b/resources/js/store/modules/admin.js @@ -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`]; From 02aba80fc94c7514591969ef5718a7adb7053037 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 14 Jun 2025 17:40:40 -0300 Subject: [PATCH 02/32] Comisiones store --- resources/js/components/compras/Body.vue | 35 ++++++++++++++++++++++-- resources/js/store/index.js | 2 ++ resources/js/store/modules/comisiones.js | 28 +++++++++++++++++++ routes/api.php | 1 + 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 resources/js/store/modules/comisiones.js diff --git a/resources/js/components/compras/Body.vue b/resources/js/components/compras/Body.vue index 72d6bb0..41af6a6 100644 --- a/resources/js/components/compras/Body.vue +++ b/resources/js/components/compras/Body.vue @@ -41,6 +41,22 @@ +
+
+ + + + + + + +
{{ gdc.nombre }}{{ gdc.saldo }}
+
+
@@ -48,6 +64,7 @@ import TabsSecciones from "../comunes/TabsSecciones.vue"; import DropdownDescargar from "./DropdownDescargar.vue"; import CanastaInput from "./CanastaInput.vue"; +import { mapActions, mapState } from "vuex"; export default { components: { @@ -57,8 +74,11 @@ export default { }, data() { return { - tabs: [{ id: "pedidos-compras", nombre: "Pedidos" }, - { id: "canasta-compras", nombre: "Canasta" }], + tabs: [ + { id: "pedidos-compras", nombre: "Pedidos" }, + { id: "canasta-compras", nombre: "Canasta" }, + { id: "saldos-compras", nombre: "Saldos" }, + ], tabActiva: "pedidos-compras", seccionActiva: "pedidos-compras-seccion", archivo: undefined, @@ -69,6 +89,15 @@ export default { this.tabActiva = tabId; this.seccionActiva = tabId + "-seccion"; }, - } + ...mapActions('comisiones', ['getGruposDeCompra']), + }, + computed: { + ...mapState('comisiones', [ + 'grupos_de_compra', + ]), + }, + async mounted() { + await this.getGruposDeCompra(); + }, } diff --git a/resources/js/store/index.js b/resources/js/store/index.js index a0f3a09..210c418 100644 --- a/resources/js/store/index.js +++ b/resources/js/store/index.js @@ -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, diff --git a/resources/js/store/modules/comisiones.js b/resources/js/store/modules/comisiones.js new file mode 100644 index 0000000..04f480f --- /dev/null +++ b/resources/js/store/modules/comisiones.js @@ -0,0 +1,28 @@ +import axios from "axios"; + +const state = { + grupos_de_compra: null, +}; + +const mutations = { + setGruposDeCompra(state, { grupos_de_compra }) { + state.grupos_de_compra = grupos_de_compra; + }, +}; + +const actions = { + async getGruposDeCompra({ commit }) { + const response = await axios.get('/api/grupos-de-compra'); + commit('setGruposDeCompra', response.data); + }, +}; + +const getters = {}; + +export default { + namespaced: true, + state, + mutations, + actions, + getters, +}; diff --git a/routes/api.php b/routes/api.php index 21f7fd5..7747128 100644 --- a/routes/api.php +++ b/routes/api.php @@ -18,6 +18,7 @@ Route::middleware('api')->group(function() { Route::get('/regiones/{region}', 'Api\GrupoDeCompraController@region'); Route::prefix('grupos-de-compra')->group(function() { + Route::get('/', 'Api\GrupoDeCompraController@index'); Route::get('/{grupoDeCompra}', 'Api\GrupoDeCompraController@show'); Route::post('/{gdc}/devoluciones', 'Api\GrupoDeCompraController@toggleDevoluciones'); }); From 6787dde7114eb64e4e8ef4faa50b35ce5543fc80 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 14 Jun 2025 22:46:47 -0300 Subject: [PATCH 03/32] Editor de saldos en compras --- app/GrupoDeCompra.php | 5 ++ .../Api/GrupoDeCompraController.php | 9 ++++ app/Http/Resources/GrupoDeCompraResource.php | 4 +- .../js/components/admin/TablaPedidos.vue | 2 +- resources/js/components/compras/Body.vue | 51 +++++++++++++++++-- resources/js/store/modules/comisiones.js | 28 ++++++++-- routes/api.php | 1 + 7 files changed, 90 insertions(+), 10 deletions(-) diff --git a/app/GrupoDeCompra.php b/app/GrupoDeCompra.php index 83cf6a6..a5f8cc4 100644 --- a/app/GrupoDeCompra.php +++ b/app/GrupoDeCompra.php @@ -286,4 +286,9 @@ class GrupoDeCompra extends Model ->get() ->keyBy('producto_id'); } + + public function setSaldo(float $saldo) { + $this->saldo = $saldo; + $this->save(); + } } diff --git a/app/Http/Controllers/Api/GrupoDeCompraController.php b/app/Http/Controllers/Api/GrupoDeCompraController.php index 754b1ae..1c57878 100644 --- a/app/Http/Controllers/Api/GrupoDeCompraController.php +++ b/app/Http/Controllers/Api/GrupoDeCompraController.php @@ -32,4 +32,13 @@ 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 new GrupoDeCompraResource($grupoDeCompra); + } } diff --git a/app/Http/Resources/GrupoDeCompraResource.php b/app/Http/Resources/GrupoDeCompraResource.php index b19aa13..ee0552e 100644 --- a/app/Http/Resources/GrupoDeCompraResource.php +++ b/app/Http/Resources/GrupoDeCompraResource.php @@ -21,14 +21,14 @@ 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), + '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()), + 'cantidad_transnumber_formatporte' => number_format($this->cantidadTransporte()), ]; } } diff --git a/resources/js/components/admin/TablaPedidos.vue b/resources/js/components/admin/TablaPedidos.vue index 246d762..469a64a 100644 --- a/resources/js/components/admin/TablaPedidos.vue +++ b/resources/js/components/admin/TablaPedidos.vue @@ -48,7 +48,7 @@ Saldo a favor: - $ {{ saldo }} + - $ {{ saldo }} Total a transferir: diff --git a/resources/js/components/compras/Body.vue b/resources/js/components/compras/Body.vue index 41af6a6..1045710 100644 --- a/resources/js/components/compras/Body.vue +++ b/resources/js/components/compras/Body.vue @@ -47,11 +47,31 @@ :class="seccionActiva === 'saldos-compras-seccion' ? 'is-active' : 'is-hidden'" >
- +
+ + + + + + - + +
BarrioSaldo
{{ gdc.nombre }}{{ gdc.saldo }} + + + +
@@ -71,6 +91,7 @@ export default { TabsSecciones, DropdownDescargar, CanastaInput, + InputFileButton, }, data() { return { @@ -82,6 +103,7 @@ export default { tabActiva: "pedidos-compras", seccionActiva: "pedidos-compras-seccion", archivo: undefined, + saldo_modificado: {}, } }, methods: { @@ -89,7 +111,30 @@ export default { this.tabActiva = tabId; this.seccionActiva = tabId + "-seccion"; }, - ...mapActions('comisiones', ['getGruposDeCompra']), + ...mapActions('comisiones', ['getGruposDeCompra', 'setSaldo']), + async confirmar_saldo(gdc_id) { + var saldo = this.getSaldo(gdc_id); + await this.setSaldo({ + gdc_id: gdc_id, + saldo: saldo, + }); + this.saldo_modificado[gdc_id] = false; + await this.getGruposDeCompra(); + }, + saldoModificado(gdc_id) { + this.saldo_modificado[gdc_id] = true; + }, + isSaldoModificado(gdc_id) { + return gdc_id in this.saldo_modificado && this.saldo_modificado[gdc_id]; + }, + getSaldo(gdc_id) { + for (var i = 0; i < this.grupos_de_compra.length; i++) { + if (this.grupos_de_compra[i].id == gdc_id) { + return this.grupos_de_compra[i].saldo; + } + } + return 0; + }, }, computed: { ...mapState('comisiones', [ diff --git a/resources/js/store/modules/comisiones.js b/resources/js/store/modules/comisiones.js index 04f480f..024de04 100644 --- a/resources/js/store/modules/comisiones.js +++ b/resources/js/store/modules/comisiones.js @@ -1,12 +1,21 @@ import axios from "axios"; const state = { - grupos_de_compra: null, + grupos_de_compra: [], }; const mutations = { - setGruposDeCompra(state, { grupos_de_compra }) { - state.grupos_de_compra = grupos_de_compra; + setGruposDeCompra(state, { data }) { + state.grupos_de_compra = data; + }, + setGrupoDeCompra(state, gdc) { + for (var i = 0; i < state.grupos_de_compra.length; i++) { + if (state.grupos_de_compra[i].id == gdc.id) { + state.grupos_de_compra[i] = gdc; + return; + } + } + state.grupos_de_compra.push(gdc); }, }; @@ -15,9 +24,20 @@ const actions = { const response = await axios.get('/api/grupos-de-compra'); commit('setGruposDeCompra', response.data); }, + async setSaldo({ commit }, { gdc_id, saldo }) { + const response = await axios.post( + "api/grupos-de-compra/" + gdc_id + "/saldo", + { saldo: saldo } + ); + commit('setGrupoDeCompra', response.data.data); + }, }; -const getters = {}; +const getters = { + getSaldo() { + return (gdc_id) => state.grupos_de_compra.find(gdc => gdc.id === gdc_id)?.saldo ?? 0; + }, +}; export default { namespaced: true, diff --git a/routes/api.php b/routes/api.php index 7747128..e2acfdd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -21,6 +21,7 @@ Route::middleware('api')->group(function() { Route::get('/', 'Api\GrupoDeCompraController@index'); 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 () { From b752f9e8c5cd90200f755774328d76954d86c1d7 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sun, 15 Jun 2025 00:19:30 -0300 Subject: [PATCH 04/32] Subir saldos como csv --- app/Http/Controllers/ComprasController.php | 36 +++++++++++ resources/js/components/compras/Body.vue | 62 ++++++++++++++++++- .../js/components/comunes/InputFileButton.vue | 56 +++++++++++++++++ routes/web.php | 1 + 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 resources/js/components/comunes/InputFileButton.vue diff --git a/app/Http/Controllers/ComprasController.php b/app/Http/Controllers/ComprasController.php index 93ef20a..40daea7 100644 --- a/app/Http/Controllers/ComprasController.php +++ b/app/Http/Controllers/ComprasController.php @@ -8,10 +8,14 @@ use App\Producto; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use League\Csv\Reader; +use DatabaseSeeder; class ComprasController { const CANASTAS_PATH = 'csv/canastas/'; + const BARRIO = "Barrio"; + const SALDO = "Saldo"; public function indexPedidos() { return view('compras_pedidos'); @@ -59,4 +63,36 @@ class ComprasController $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(); + $csv = Reader::createFromPath($file, 'r'); + try { + $csv->setDelimiter("|"); + $csv->setEnclosure("'"); + $csv->setHeaderOffset(0); + $records = $csv->getRecords(); + } catch (InvalidArgument|Exception $e) { + Log::error($e->getMessage()); + return response()->json([ + 'message' => 'No se pudo leer el csv', + ]); + } + + foreach ($records as $record) { + $barrio = $record[self::BARRIO]; + $saldo = $record[self::SALDO]; + GrupoDeCompra::where('nombre', $barrio) + ->update(['saldo' => $saldo]); + } + + return response()->json([ + 'message' => 'Saldos cargados exitosamente', + ]); + } } diff --git a/resources/js/components/compras/Body.vue b/resources/js/components/compras/Body.vue index 1045710..dc0a7c6 100644 --- a/resources/js/components/compras/Body.vue +++ b/resources/js/components/compras/Body.vue @@ -47,7 +47,38 @@ :class="seccionActiva === 'saldos-compras-seccion' ? 'is-active' : 'is-hidden'" >
- +
+
+

+ + Cargar saldos +

+
+
+
+ La planilla de saldos tiene que tener el siguiente formato para que la aplicación la lea correctamente: +
    +
  • Los valores deben usar punto y no coma decimal
  • +
  • El nombre de las columnas deben ser "Barrio" y "Saldo"
  • +
  • Las celdas deben separarse con '|'
  • +
  • No puede haber "enters" en ninguna celda
  • +
  • El nombre de los barrios debe estar exactamente igual que como están configurados en esta aplicacaión
  • +
+
+
+
+ Cargar un archivo de saldos sólo reemplazará los saldos de los barrios presentes en la tabla. +
+
+
+
+ +
+
+ +
@@ -84,6 +115,7 @@ import TabsSecciones from "../comunes/TabsSecciones.vue"; import DropdownDescargar from "./DropdownDescargar.vue"; import CanastaInput from "./CanastaInput.vue"; +import InputFileButton from "../comunes/InputFileButton.vue"; import { mapActions, mapState } from "vuex"; export default { @@ -104,6 +136,7 @@ export default { seccionActiva: "pedidos-compras-seccion", archivo: undefined, saldo_modificado: {}, + show_saldos_file_dialog: false, } }, methods: { @@ -112,6 +145,7 @@ export default { this.seccionActiva = tabId + "-seccion"; }, ...mapActions('comisiones', ['getGruposDeCompra', 'setSaldo']), + ...mapActions('ui',["toast"]), async confirmar_saldo(gdc_id) { var saldo = this.getSaldo(gdc_id); await this.setSaldo({ @@ -135,6 +169,32 @@ export default { } return 0; }, + async saldosSubido(event) { + var archivo = event.archivo; + if (archivo.type === "text/csv") { + const formData = new FormData(); + formData.append("data", archivo); + try { + const response = await axios.post("/compras/saldos", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + this.getGruposDeCompra(); + this.toast({ mensaje: (response.data.message || "Canasta cargada exitosamente") }); + } catch (error) { + console.log(error); + this.toast({ mensaje: (error.response?.data?.message || "Hubo errores.") }); + } + event.component.cargando = false; + } else { + this.toast("El archivo debe ser .CSV"); + event.component.cargando = false; + } + }, + toggleSaldosFileDialog() { + this.show_saldos_file_dialog = !this.show_saldos_file_dialog; + }, }, computed: { ...mapState('comisiones', [ diff --git a/resources/js/components/comunes/InputFileButton.vue b/resources/js/components/comunes/InputFileButton.vue new file mode 100644 index 0000000..a9f70a4 --- /dev/null +++ b/resources/js/components/comunes/InputFileButton.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/routes/web.php b/routes/web.php index c45aebd..b963c10 100644 --- a/routes/web.php +++ b/routes/web.php @@ -54,4 +54,5 @@ Route::middleware(['auth', 'role:comision'])->group( function() { Route::get('/compras/pedidos/pdf', 'ComprasController@pdf')->name('compras.pedidos.pdf'); Route::get('/compras/canasta/ejemplo', 'ComprasController@descargarCanastaEjemplo')->name('compras.canasta.ejemplo'); Route::post('/compras/canasta', 'ComprasController@cargarCanasta')->name('compras.canasta'); + Route::post('/compras/saldos', 'ComprasController@cargarSaldos')->name('compras.canasta'); }); From 46675f9acf29766697c03ffc64455a4a96006d2d Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 14 Jun 2025 17:39:51 -0300 Subject: [PATCH 05/32] Agregado saldo a grupo de compra --- app/GrupoDeCompra.php | 12 +++++-- app/Http/Resources/GrupoDeCompraResource.php | 2 ++ ..._06_14_172643_agregar_saldos_a_barrios.php | 34 +++++++++++++++++++ .../js/components/admin/TablaPedidos.vue | 12 ++++++- resources/js/store/modules/admin.js | 4 +++ 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php diff --git a/app/GrupoDeCompra.php b/app/GrupoDeCompra.php index bb13a9f..c5172a9 100644 --- a/app/GrupoDeCompra.php +++ b/app/GrupoDeCompra.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Log; 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 +69,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() diff --git a/app/Http/Resources/GrupoDeCompraResource.php b/app/Http/Resources/GrupoDeCompraResource.php index ef16fb5..b19aa13 100644 --- a/app/Http/Resources/GrupoDeCompraResource.php +++ b/app/Http/Resources/GrupoDeCompraResource.php @@ -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()), diff --git a/database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php b/database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php new file mode 100644 index 0000000..653c3a1 --- /dev/null +++ b/database/migrations/2025_06_14_172643_agregar_saldos_a_barrios.php @@ -0,0 +1,34 @@ +double('saldo', 10, 2); + }); + } + + /** + * 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'); + }); + } +} diff --git a/resources/js/components/admin/TablaPedidos.vue b/resources/js/components/admin/TablaPedidos.vue index 9907ac7..bcc9713 100644 --- a/resources/js/components/admin/TablaPedidos.vue +++ b/resources/js/components/admin/TablaPedidos.vue @@ -43,7 +43,15 @@ - + + + + + + + + +
Barrio$ {{ total_transporte }}
Total a depositar:Total de pedido:$ {{ total_de_pedido }}
Saldo a favor:$ {{ saldo }}
Total a transferir: $ {{ total_a_transferir }}
@@ -67,7 +75,9 @@ export default { "total_devoluciones", "cantidad_transporte", "total_transporte", + "total_de_pedido", "total_a_transferir", + "saldo", ]), ...mapGetters('admin', ['pedidosAprobados']), }, diff --git a/resources/js/store/modules/admin.js b/resources/js/store/modules/admin.js index 4ad44ee..26dbd52 100644 --- a/resources/js/store/modules/admin.js +++ b/resources/js/store/modules/admin.js @@ -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`]; From eb1b5bbc2e802ff27eb80d22de99bba71a9d216d Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 14 Jun 2025 17:40:40 -0300 Subject: [PATCH 06/32] Comisiones store --- resources/js/components/comisiones/Body.vue | 35 +++++++++++++++++++-- resources/js/store/index.js | 2 ++ resources/js/store/modules/comisiones.js | 28 +++++++++++++++++ routes/api.php | 1 + 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 resources/js/store/modules/comisiones.js diff --git a/resources/js/components/comisiones/Body.vue b/resources/js/components/comisiones/Body.vue index 20573ec..42108a2 100644 --- a/resources/js/components/comisiones/Body.vue +++ b/resources/js/components/comisiones/Body.vue @@ -40,6 +40,22 @@
+
+
+ + + + + + + +
{{ gdc.nombre }}{{ gdc.saldo }}
+
+
@@ -47,6 +63,7 @@ import TabsSecciones from "../comunes/TabsSecciones.vue"; import DropdownDescargar from "./DropdownDescargar.vue"; import CanastaInput from "./CanastaInput.vue"; +import { mapActions, mapState } from "vuex"; export default { name: "ComisionesBody", @@ -57,8 +74,11 @@ export default { }, 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, @@ -69,6 +89,15 @@ export default { this.tabActiva = tabId; this.seccionActiva = tabId + "-seccion"; }, - } + ...mapActions('comisiones', ['getGruposDeCompra']), + }, + computed: { + ...mapState('comisiones', [ + 'grupos_de_compra', + ]), + }, + async mounted() { + await this.getGruposDeCompra(); + }, } diff --git a/resources/js/store/index.js b/resources/js/store/index.js index a0f3a09..210c418 100644 --- a/resources/js/store/index.js +++ b/resources/js/store/index.js @@ -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, diff --git a/resources/js/store/modules/comisiones.js b/resources/js/store/modules/comisiones.js new file mode 100644 index 0000000..04f480f --- /dev/null +++ b/resources/js/store/modules/comisiones.js @@ -0,0 +1,28 @@ +import axios from "axios"; + +const state = { + grupos_de_compra: null, +}; + +const mutations = { + setGruposDeCompra(state, { grupos_de_compra }) { + state.grupos_de_compra = grupos_de_compra; + }, +}; + +const actions = { + async getGruposDeCompra({ commit }) { + const response = await axios.get('/api/grupos-de-compra'); + commit('setGruposDeCompra', response.data); + }, +}; + +const getters = {}; + +export default { + namespaced: true, + state, + mutations, + actions, + getters, +}; diff --git a/routes/api.php b/routes/api.php index a032bac..91aac3e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -20,6 +20,7 @@ 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('/{grupoDeCompra}', 'Api\GrupoDeCompraController@show'); Route::post('/{gdc}/devoluciones', 'Api\GrupoDeCompraController@toggleDevoluciones'); }); From eee23082db345ad494e388341c56f87390e63481 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 14 Jun 2025 22:46:47 -0300 Subject: [PATCH 07/32] Editor de saldos en compras --- app/GrupoDeCompra.php | 5 ++ .../Api/GrupoDeCompraController.php | 9 ++++ app/Http/Resources/GrupoDeCompraResource.php | 4 +- .../js/components/admin/TablaPedidos.vue | 2 +- resources/js/components/comisiones/Body.vue | 51 +++++++++++++++++-- resources/js/store/modules/comisiones.js | 28 ++++++++-- routes/api.php | 1 + 7 files changed, 90 insertions(+), 10 deletions(-) diff --git a/app/GrupoDeCompra.php b/app/GrupoDeCompra.php index c5172a9..a16f87b 100644 --- a/app/GrupoDeCompra.php +++ b/app/GrupoDeCompra.php @@ -290,4 +290,9 @@ class GrupoDeCompra extends Model ->get() ->keyBy('producto_id'); } + + public function setSaldo(float $saldo) { + $this->saldo = $saldo; + $this->save(); + } } diff --git a/app/Http/Controllers/Api/GrupoDeCompraController.php b/app/Http/Controllers/Api/GrupoDeCompraController.php index 754b1ae..1c57878 100644 --- a/app/Http/Controllers/Api/GrupoDeCompraController.php +++ b/app/Http/Controllers/Api/GrupoDeCompraController.php @@ -32,4 +32,13 @@ 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 new GrupoDeCompraResource($grupoDeCompra); + } } diff --git a/app/Http/Resources/GrupoDeCompraResource.php b/app/Http/Resources/GrupoDeCompraResource.php index b19aa13..ee0552e 100644 --- a/app/Http/Resources/GrupoDeCompraResource.php +++ b/app/Http/Resources/GrupoDeCompraResource.php @@ -21,14 +21,14 @@ 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), + '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()), + 'cantidad_transnumber_formatporte' => number_format($this->cantidadTransporte()), ]; } } diff --git a/resources/js/components/admin/TablaPedidos.vue b/resources/js/components/admin/TablaPedidos.vue index bcc9713..1aae42c 100644 --- a/resources/js/components/admin/TablaPedidos.vue +++ b/resources/js/components/admin/TablaPedidos.vue @@ -48,7 +48,7 @@ Saldo a favor: - $ {{ saldo }} + - $ {{ saldo }} Total a transferir: diff --git a/resources/js/components/comisiones/Body.vue b/resources/js/components/comisiones/Body.vue index 42108a2..990ed72 100644 --- a/resources/js/components/comisiones/Body.vue +++ b/resources/js/components/comisiones/Body.vue @@ -46,11 +46,31 @@ :class="seccionActiva === 'saldos-comisiones-seccion' ? 'is-active' : 'is-hidden'" >
- +
+ + + + + + - + +
BarrioSaldo
{{ gdc.nombre }}{{ gdc.saldo }} + + + +
@@ -71,6 +91,7 @@ export default { TabsSecciones, DropdownDescargar, CanastaInput, + InputFileButton, }, data() { return { @@ -82,6 +103,7 @@ export default { tabActiva: "pedidos-comisiones", seccionActiva: "pedidos-comisiones-seccion", archivo: undefined, + saldo_modificado: {}, } }, methods: { @@ -89,7 +111,30 @@ export default { this.tabActiva = tabId; this.seccionActiva = tabId + "-seccion"; }, - ...mapActions('comisiones', ['getGruposDeCompra']), + ...mapActions('comisiones', ['getGruposDeCompra', 'setSaldo']), + async confirmar_saldo(gdc_id) { + var saldo = this.getSaldo(gdc_id); + await this.setSaldo({ + gdc_id: gdc_id, + saldo: saldo, + }); + this.saldo_modificado[gdc_id] = false; + await this.getGruposDeCompra(); + }, + saldoModificado(gdc_id) { + this.saldo_modificado[gdc_id] = true; + }, + isSaldoModificado(gdc_id) { + return gdc_id in this.saldo_modificado && this.saldo_modificado[gdc_id]; + }, + getSaldo(gdc_id) { + for (var i = 0; i < this.grupos_de_compra.length; i++) { + if (this.grupos_de_compra[i].id == gdc_id) { + return this.grupos_de_compra[i].saldo; + } + } + return 0; + }, }, computed: { ...mapState('comisiones', [ diff --git a/resources/js/store/modules/comisiones.js b/resources/js/store/modules/comisiones.js index 04f480f..024de04 100644 --- a/resources/js/store/modules/comisiones.js +++ b/resources/js/store/modules/comisiones.js @@ -1,12 +1,21 @@ import axios from "axios"; const state = { - grupos_de_compra: null, + grupos_de_compra: [], }; const mutations = { - setGruposDeCompra(state, { grupos_de_compra }) { - state.grupos_de_compra = grupos_de_compra; + setGruposDeCompra(state, { data }) { + state.grupos_de_compra = data; + }, + setGrupoDeCompra(state, gdc) { + for (var i = 0; i < state.grupos_de_compra.length; i++) { + if (state.grupos_de_compra[i].id == gdc.id) { + state.grupos_de_compra[i] = gdc; + return; + } + } + state.grupos_de_compra.push(gdc); }, }; @@ -15,9 +24,20 @@ const actions = { const response = await axios.get('/api/grupos-de-compra'); commit('setGruposDeCompra', response.data); }, + async setSaldo({ commit }, { gdc_id, saldo }) { + const response = await axios.post( + "api/grupos-de-compra/" + gdc_id + "/saldo", + { saldo: saldo } + ); + commit('setGrupoDeCompra', response.data.data); + }, }; -const getters = {}; +const getters = { + getSaldo() { + return (gdc_id) => state.grupos_de_compra.find(gdc => gdc.id === gdc_id)?.saldo ?? 0; + }, +}; export default { namespaced: true, diff --git a/routes/api.php b/routes/api.php index 91aac3e..c9d08d1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -23,6 +23,7 @@ Route::middleware('api')->group(function() { Route::get('/', 'Api\GrupoDeCompraController@index'); 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 () { From 665ab517fbae1ed85ec2292424fc661815ed8bdf Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sun, 15 Jun 2025 00:19:30 -0300 Subject: [PATCH 08/32] Subir saldos como csv --- app/Http/Controllers/ComisionesController.php | 36 +++++++++++ resources/js/components/comisiones/Body.vue | 62 ++++++++++++++++++- .../js/components/comunes/InputFileButton.vue | 56 +++++++++++++++++ routes/web.php | 1 + 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 resources/js/components/comunes/InputFileButton.vue diff --git a/app/Http/Controllers/ComisionesController.php b/app/Http/Controllers/ComisionesController.php index 9ffd3f1..1532ec5 100644 --- a/app/Http/Controllers/ComisionesController.php +++ b/app/Http/Controllers/ComisionesController.php @@ -8,10 +8,14 @@ use App\Producto; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use League\Csv\Reader; +use DatabaseSeeder; class ComisionesController { const CANASTAS_PATH = 'csv/canastas/'; + const BARRIO = "Barrio"; + const SALDO = "Saldo"; public function show() { @@ -67,4 +71,36 @@ class ComisionesController $file = storage_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(); + $csv = Reader::createFromPath($file, 'r'); + try { + $csv->setDelimiter("|"); + $csv->setEnclosure("'"); + $csv->setHeaderOffset(0); + $records = $csv->getRecords(); + } catch (InvalidArgument|Exception $e) { + Log::error($e->getMessage()); + return response()->json([ + 'message' => 'No se pudo leer el csv', + ]); + } + + foreach ($records as $record) { + $barrio = $record[self::BARRIO]; + $saldo = $record[self::SALDO]; + GrupoDeCompra::where('nombre', $barrio) + ->update(['saldo' => $saldo]); + } + + return response()->json([ + 'message' => 'Saldos cargados exitosamente', + ]); + } } diff --git a/resources/js/components/comisiones/Body.vue b/resources/js/components/comisiones/Body.vue index 990ed72..eef8c6b 100644 --- a/resources/js/components/comisiones/Body.vue +++ b/resources/js/components/comisiones/Body.vue @@ -46,7 +46,38 @@ :class="seccionActiva === 'saldos-comisiones-seccion' ? 'is-active' : 'is-hidden'" >
- +
+
+

+ + Cargar saldos +

+
+
+
+ La planilla de saldos tiene que tener el siguiente formato para que la aplicación la lea correctamente: +
    +
  • Los valores deben usar punto y no coma decimal
  • +
  • El nombre de las columnas deben ser "Barrio" y "Saldo"
  • +
  • Las celdas deben separarse con '|'
  • +
  • No puede haber "enters" en ninguna celda
  • +
  • El nombre de los barrios debe estar exactamente igual que como están configurados en esta aplicacaión
  • +
+
+
+
+ Cargar un archivo de saldos sólo reemplazará los saldos de los barrios presentes en la tabla. +
+
+
+
+ +
+
+ +
@@ -83,6 +114,7 @@ import TabsSecciones from "../comunes/TabsSecciones.vue"; import DropdownDescargar from "./DropdownDescargar.vue"; import CanastaInput from "./CanastaInput.vue"; +import InputFileButton from "../comunes/InputFileButton.vue"; import { mapActions, mapState } from "vuex"; export default { @@ -104,6 +136,7 @@ export default { seccionActiva: "pedidos-comisiones-seccion", archivo: undefined, saldo_modificado: {}, + show_saldos_file_dialog: false, } }, methods: { @@ -112,6 +145,7 @@ export default { this.seccionActiva = tabId + "-seccion"; }, ...mapActions('comisiones', ['getGruposDeCompra', 'setSaldo']), + ...mapActions('ui',["toast"]), async confirmar_saldo(gdc_id) { var saldo = this.getSaldo(gdc_id); await this.setSaldo({ @@ -135,6 +169,32 @@ export default { } return 0; }, + async saldosSubido(event) { + var archivo = event.archivo; + if (archivo.type === "text/csv") { + const formData = new FormData(); + formData.append("data", archivo); + try { + const response = await axios.post("/comisiones/saldos", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + this.getGruposDeCompra(); + this.toast({ mensaje: (response.data.message || "Canasta cargada exitosamente") }); + } catch (error) { + console.log(error); + this.toast({ mensaje: (error.response?.data?.message || "Hubo errores.") }); + } + event.component.cargando = false; + } else { + this.toast("El archivo debe ser .CSV"); + event.component.cargando = false; + } + }, + toggleSaldosFileDialog() { + this.show_saldos_file_dialog = !this.show_saldos_file_dialog; + }, }, computed: { ...mapState('comisiones', [ diff --git a/resources/js/components/comunes/InputFileButton.vue b/resources/js/components/comunes/InputFileButton.vue new file mode 100644 index 0000000..a9f70a4 --- /dev/null +++ b/resources/js/components/comunes/InputFileButton.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/routes/web.php b/routes/web.php index 56ac0be..73d3165 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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.canasta'); }); From a497ae134e37ce989625fabd9efa37fc64693e42 Mon Sep 17 00:00:00 2001 From: ale Date: Wed, 18 Jun 2025 19:28:28 -0300 Subject: [PATCH 09/32] Agregado espacio abajo --- resources/js/components/pedidos/Body.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/pedidos/Body.vue b/resources/js/components/pedidos/Body.vue index 706adc2..7e85e18 100644 --- a/resources/js/components/pedidos/Body.vue +++ b/resources/js/components/pedidos/Body.vue @@ -1,5 +1,5 @@ diff --git a/resources/js/components/comisiones/saldos/TablaSaldos.vue b/resources/js/components/comisiones/saldos/TablaSaldos.vue new file mode 100644 index 0000000..46130b1 --- /dev/null +++ b/resources/js/components/comisiones/saldos/TablaSaldos.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/resources/js/store/modules/comisiones.js b/resources/js/store/modules/comisiones.js index 261fe41..4857cc0 100644 --- a/resources/js/store/modules/comisiones.js +++ b/resources/js/store/modules/comisiones.js @@ -1,12 +1,14 @@ 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); From a7096e7dc0778647a4c2168c45779f8780e102a6 Mon Sep 17 00:00:00 2001 From: ale Date: Thu, 19 Jun 2025 18:36:58 -0300 Subject: [PATCH 29/32] Quitado import no usado --- resources/js/components/comisiones/saldos/SaldosSeccion.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/comisiones/saldos/SaldosSeccion.vue b/resources/js/components/comisiones/saldos/SaldosSeccion.vue index b095dc8..c9b413a 100644 --- a/resources/js/components/comisiones/saldos/SaldosSeccion.vue +++ b/resources/js/components/comisiones/saldos/SaldosSeccion.vue @@ -1,6 +1,6 @@
Barrio