diff --git a/app/Helpers/PedidosExportHelper.php b/app/Helpers/PedidosExportHelper.php index c8a16dc..1be6ff7 100644 --- a/app/Helpers/PedidosExportHelper.php +++ b/app/Helpers/PedidosExportHelper.php @@ -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; } } diff --git a/app/Http/Controllers/Api/SubpedidoController.php b/app/Http/Controllers/Api/SubpedidoController.php index e340e01..deb67a4 100644 --- a/app/Http/Controllers/Api/SubpedidoController.php +++ b/app/Http/Controllers/Api/SubpedidoController.php @@ -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); + } } diff --git a/app/Http/Controllers/ComisionesController.php b/app/Http/Controllers/ComisionesController.php index 4076c37..5a0b363 100644 --- a/app/Http/Controllers/ComisionesController.php +++ b/app/Http/Controllers/ComisionesController.php @@ -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 { diff --git a/app/Http/Controllers/FaltantesYSobrantesController.php b/app/Http/Controllers/FaltantesYSobrantesController.php new file mode 100644 index 0000000..c98bd60 --- /dev/null +++ b/app/Http/Controllers/FaltantesYSobrantesController.php @@ -0,0 +1,20 @@ + '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)); + } +} diff --git a/app/Http/Resources/GrupoDeCompraResource.php b/app/Http/Resources/GrupoDeCompraResource.php index 82f2120..2546cfc 100644 --- a/app/Http/Resources/GrupoDeCompraResource.php +++ b/app/Http/Resources/GrupoDeCompraResource.php @@ -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, ]; } } diff --git a/app/Http/Resources/PedidoFaltantesYSobrantesResource.php b/app/Http/Resources/PedidoFaltantesYSobrantesResource.php new file mode 100644 index 0000000..7c381e8 --- /dev/null +++ b/app/Http/Resources/PedidoFaltantesYSobrantesResource.php @@ -0,0 +1,28 @@ +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, + ]; + } +} diff --git a/app/Subpedido.php b/app/Subpedido.php index df989af..5c2f24f 100644 --- a/app/Subpedido.php +++ b/app/Subpedido.php @@ -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 diff --git a/database/migrations/2025_08_27_195849_tipo_pedido_faltantes_y_sobrantes.php b/database/migrations/2025_08_27_195849_tipo_pedido_faltantes_y_sobrantes.php new file mode 100644 index 0000000..9e8f87b --- /dev/null +++ b/database/migrations/2025_08_27_195849_tipo_pedido_faltantes_y_sobrantes.php @@ -0,0 +1,30 @@ + 'faltantes_y_sobrantes']); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $faltantesYSobrantes = TipoPedido::where('nombre', 'faltantes_y_sobrantes')->firstOrFail(); + $faltantesYSobrantes->delete(); + } +}; diff --git a/package-lock.json b/package-lock.json index 615add8..33e0403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "www", "dependencies": { "animate.css": "^4.1.1", "bulma": "^0.9.4", diff --git a/resources/js/components/admin/Body.vue b/resources/js/components/admin/Body.vue index 1f41a28..5f2e68d 100644 --- a/resources/js/components/admin/Body.vue +++ b/resources/js/components/admin/Body.vue @@ -1,19 +1,30 @@ @@ -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"; diff --git a/resources/js/components/admin/DropdownDescargar.vue b/resources/js/components/admin/DropdownDescargar.vue index 47ea269..fb661b2 100644 --- a/resources/js/components/admin/DropdownDescargar.vue +++ b/resources/js/components/admin/DropdownDescargar.vue @@ -2,7 +2,11 @@
@@ -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' }, + ], } }, } diff --git a/resources/js/components/pedidos/Chismosa.vue b/resources/js/components/pedidos/Chismosa.vue index 5c215a8..d86b0d2 100644 --- a/resources/js/components/pedidos/Chismosa.vue +++ b/resources/js/components/pedidos/Chismosa.vue @@ -14,7 +14,7 @@ {{ cantidad_transporte }} {{ total_transporte }} - +

Devoluciones

{{ notas_abreviadas }} @@ -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"]), diff --git a/resources/js/components/pedidos/ProductoCantidad.vue b/resources/js/components/pedidos/ProductoCantidad.vue index 8deebec..4dd3c74 100644 --- a/resources/js/components/pedidos/ProductoCantidad.vue +++ b/resources/js/components/pedidos/ProductoCantidad.vue @@ -2,7 +2,7 @@
-
@@ -19,7 +19,7 @@
-