untitled

Pasted Nov 10, 2026, 10:28 PM1 file292 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgs810qgrd11ya3
untitled
@Transaction
    suspend fun editarLoteCompleto(
        loteId: String,
        detallesNuevos: List<LoteDetalleEntity>,
        inventariosLoteNuevos: List<LoteInventarioEntity>
    ) {
        check(puedeEditarProduccionLote(loteId)) {
            "No se puede editar este lote porque tiene movimientos, ventas o reservas asociados."
        }

        val loteOriginal = obtenerLotePorId(loteId)
            ?: error("No se encontró el lote de producción.")

        for (detalle in detallesNuevos) {
            val presentacion = obtenerPresentacionPorId(
                detalle.presentacionId
            ) ?: error("No se encontró una de las presentaciones.")

            val aroma = obtenerAromaPorId(presentacion.aromaId)
                ?: error("No se encontró el aroma de la presentación.")

            check(aroma.productoId == loteOriginal.productoId) {
                "No puedes agregar presentaciones de otro producto."
            }
        }

        val detallesActuales = obtenerDetallesLote(loteId)
        val inventariosActuales = obtenerInventariosDeLote(loteId)


        check(detallesActuales.isNotEmpty()) {
            "No se encontraron detalles de producción para este lote."
        }

        check(detallesNuevos.isNotEmpty()) {
            "El lote debe conservar al menos una presentación."
        }

        check(detallesNuevos.all {
            it.loteId == loteId && it.cantidadFabricada > 0
        }) {
            "Hay cantidades de producción incorrectas."
        }

        check(detallesNuevos.map { it.presentacionId }.distinct().size ==
                detallesNuevos.size) {
            "No puedes repetir una presentación."
        }

        check(inventariosLoteNuevos.all {
            it.loteId == loteId &&
                    it.cantidadDisponible > 0 &&
                    it.cantidadReservada == 0 &&
                    it.sede.isNotBlank()
        }) {
            "Hay una distribución incorrecta."
        }

        val clavesNuevas = inventariosLoteNuevos.map {
            "${it.presentacionId}|${it.sede}"
        }

        check(clavesNuevas.distinct().size == clavesNuevas.size) {
            "Hay distribuciones duplicadas."
        }

        val clavesActuales = inventariosActuales.map {
            "${it.presentacionId}|${it.sede}"
        }

        check(clavesActuales.distinct().size == clavesActuales.size) {
            "El inventario actual del lote contiene distribuciones duplicadas."
        }

        check(inventariosActuales.all {
            it.cantidadDisponible >= 0 && it.cantidadReservada == 0
        }) {
            "El inventario actual contiene cantidades reservadas o incorrectas."
        }

        val presentacionesNuevas =
            detallesNuevos.map { it.presentacionId }.toSet()

        val presentacionesDistribuidas =
            inventariosLoteNuevos.map { it.presentacionId }.toSet()

        check(presentacionesNuevas == presentacionesDistribuidas) {
            "Las presentaciones fabricadas no coinciden con las distribuidas."
        }

        for (detalle in detallesNuevos) {
            val cantidadDistribuida = inventariosLoteNuevos
                .filter { it.presentacionId == detalle.presentacionId }
                .sumOf { it.cantidadDisponible }

            check(cantidadDistribuida == detalle.cantidadFabricada) {
                "La distribución no coincide con la cantidad fabricada."
            }
        }

        val presentacionesActuales =
            detallesActuales.map { it.presentacionId }.toSet()

        check(inventariosActuales.all {
            it.presentacionId in presentacionesActuales
        }) {
            "El inventario actual contiene presentaciones que no pertenecen al lote."
        }

        for (detalle in detallesActuales) {
            val cantidadActual = inventariosActuales
                .filter { it.presentacionId == detalle.presentacionId }
                .sumOf { it.cantidadDisponible + it.cantidadReservada }

            check(cantidadActual == detalle.cantidadFabricada) {
                "El inventario actual no coincide con la producción registrada."
            }
        }

        val actualesPorClave = inventariosActuales.associateBy {
            "${it.presentacionId}|${it.sede}"
        }

        val nuevasPorClave = inventariosLoteNuevos.associateBy {
            "${it.presentacionId}|${it.sede}"
        }

        val todasLasClaves =
            (actualesPorClave.keys + nuevasPorClave.keys).toSet()

        val fecha = System.currentTimeMillis()

        val movimientos = mutableListOf<MovimientoInventarioEntity>()

        // Ajustar inventario general e inventario FIFO.
        for (clave in todasLasClaves) {

            val inventarioActual = actualesPorClave[clave]
            val inventarioNuevo = nuevasPorClave[clave]

            val presentacionId =
                inventarioNuevo?.presentacionId
                    ?: inventarioActual!!.presentacionId

            val sede =
                inventarioNuevo?.sede
                    ?: inventarioActual!!.sede

            val cantidadAnterior =
                inventarioActual?.cantidadDisponible ?: 0

            val cantidadNueva =
                inventarioNuevo?.cantidadDisponible ?: 0

            val diferencia = cantidadNueva - cantidadAnterior

            if (diferencia == 0) continue

            // Actualizar inventario general.
            val inventarioGeneral = buscarInventario(
                presentacionId = presentacionId,
                sede = sede
            )

            if (inventarioGeneral == null) {

                check(diferencia > 0) {
                    "No existe inventario general para retirar unidades."
                }

                guardarInventario(
                    InventarioEntity(
                        id = UUID.randomUUID().toString(),
                        presentacionId = presentacionId,
                        sede = sede,
                        cantidadDisponible = diferencia,
                        cantidadReservada = 0,
                        fechaActualizacion = fecha
                    )
                )

            } else {

                val actualizado = ajustarInventarioDisponible(
                    presentacionId = presentacionId,
                    sede = sede,
                    diferencia = diferencia,
                    fecha = fecha
                )

                check(actualizado == 1) {
                    "No se pudo ajustar el inventario general. Puede que no haya existencias suficientes."
                }
            }

            // Actualizar inventario individual del lote.
            if (inventarioActual != null) {

                val actualizado = ajustarInventarioLote(
                    inventarioLoteId = inventarioActual.id,
                    diferencia = diferencia
                )

                check(actualizado == 1) {
                    "No se pudo ajustar el inventario FIFO del lote."
                }

            } else if (cantidadNueva > 0) {

                guardarInventariosLote(
                    listOf(
                        inventarioNuevo!!.copy(
                            id = UUID.randomUUID().toString(),
                            loteId = loteId,
                            cantidadReservada = 0
                        )
                    )
                )
            }

            // Registrar el ajuste sin borrar el historial anterior.
            movimientos.add(
                MovimientoInventarioEntity(
                    id = UUID.randomUUID().toString(),
                    presentacionId = presentacionId,
                    sede = sede,
                    tipoMovimiento = if (diferencia > 0)
                        "AJUSTE_PRODUCCION_ENTRADA"
                    else
                        "AJUSTE_PRODUCCION_SALIDA",
                    cantidad = kotlin.math.abs(diferencia),
                    referenciaId = loteId,
                    observacion = "Ajuste de producción del lote"
                )
            )
        }

        // Actualizar cantidades de presentaciones existentes.
        val detallesActualesPorPresentacion =
            detallesActuales.associateBy { it.presentacionId }

        for (detalleNuevo in detallesNuevos) {

            val detalleActual =
                detallesActualesPorPresentacion[detalleNuevo.presentacionId]

            if (detalleActual != null) {

                val actualizado = actualizarCantidadDetalleLote(
                    detalleId = detalleActual.id,
                    loteId = loteId,
                    cantidad = detalleNuevo.cantidadFabricada
                )

                check(actualizado == 1) {
                    "No se pudo actualizar una presentación del lote."
                }

            } else {

                // Agregar una presentación nueva.
                guardarDetallesLote(
                    listOf(
                        detalleNuevo.copy(
                            id = UUID.randomUUID().toString(),
                            loteId = loteId
                        )
                    )
                )
            }
        }

        // Retirar presentaciones que ya no pertenecen al lote.
        for (detalleActual in detallesActuales) {

            if (detalleActual.presentacionId !in presentacionesNuevas) {

                val eliminado = eliminarDetalleLote(
                    detalleId = detalleActual.id,
                    loteId = loteId
                )

                check(eliminado == 1) {
                    "No se pudo retirar una presentación del lote."
                }
            }
        }

        if (movimientos.isNotEmpty()) {
            guardarMovimientos(movimientos)
        }
    }

9,842 chars · codepastes.com