Scan to open on your phone
Point a camera at the QR code - it opens this exact paste, no app needed.
https://codepastes.com/zzgsa1bqrga1kx2tsin-tituloPlain Text
package com.negocio.desinfectantes.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import androidx.room.Transaction
import java.util.UUID
@Dao
interface ProductoDao {
// PRODUCTOS
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarProducto(producto: ProductoEntity)
@Query("""
UPDATE productos
SET nombre = :nombre,
tipo = :tipo
WHERE id = :productoId
""")
suspend fun actualizarProducto(
productoId: String,
nombre: String,
tipo: String
)
@Query("SELECT * FROM productos WHERE activo = 1 ORDER BY nombre ASC")
fun obtenerProductos(): Flow<List<ProductoEntity>>
@Query("SELECT * FROM productos ORDER BY nombre ASC")
fun obtenerTodosProductos(): Flow<List<ProductoEntity>>
@Query("SELECT * FROM productos WHERE activo = 0 ORDER BY nombre ASC")
fun obtenerProductosInactivos(): Flow<List<ProductoEntity>>
@Query("SELECT * FROM productos WHERE id = :id LIMIT 1")
suspend fun buscarProducto(id: String): ProductoEntity?
@Query("UPDATE productos SET activo = 0 WHERE id = :productoId")
suspend fun desactivarProducto(productoId: String)
@Query("UPDATE productos SET activo = 1 WHERE id = :productoId")
suspend fun reactivarProducto(productoId: String)
// AROMAS
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarAroma(aroma: AromaEntity)
@Query("SELECT * FROM aromas WHERE productoId = :productoId AND activo = 1 ORDER BY nombre ASC")
fun obtenerAromas(productoId: String): Flow<List<AromaEntity>>
@Query("SELECT * FROM aromas ORDER BY nombre ASC")
fun obtenerTodosAromas(): Flow<List<AromaEntity>>
@Query("SELECT * FROM aromas WHERE productoId = :productoId AND activo = 0 ORDER BY nombre ASC")
fun obtenerAromasInactivos(productoId: String): Flow<List<AromaEntity>>
@Query("UPDATE aromas SET activo = 1 WHERE id = :aromaId")
suspend fun reactivarAroma(aromaId: String)
@Query("SELECT * FROM aromas WHERE id = :aromaId LIMIT 1")
suspend fun buscarAroma(aromaId: String): AromaEntity?
@Query("SELECT * FROM aromas WHERE activo = 0 ORDER BY nombre ASC")
fun obtenerTodosAromasInactivos(): Flow<List<AromaEntity>>
@Query("SELECT COUNT(*) FROM presentaciones WHERE aromaId = :aromaId")
suspend fun contarPresentacionesDeAroma(aromaId: String): Int
@Query("UPDATE aromas SET activo = 0 WHERE id = :aromaId")
suspend fun eliminarAroma(aromaId: String)
@Query("""
UPDATE aromas
SET nombre = :nuevoNombre
WHERE id = :aromaId
""")
suspend fun actualizarAroma(
aromaId: String,
nuevoNombre: String
)
// PRESENTACIONES
@Query("SELECT * FROM presentaciones WHERE activo = 1 ORDER BY nombre ASC")
fun obtenerTodasPresentacionesActivas(): Flow<List<PresentacionEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarPresentacion(presentacion: PresentacionEntity)
@Query("SELECT * FROM presentaciones WHERE aromaId = :aromaId AND activo = 1")
fun obtenerPresentaciones(aromaId: String): Flow<List<PresentacionEntity>>
@Query("SELECT * FROM presentaciones WHERE aromaId = :aromaId AND activo = 0")
fun obtenerPresentacionesInactivas(aromaId: String): Flow<List<PresentacionEntity>>
@Query("UPDATE presentaciones SET activo = 1 WHERE id = :presentacionId")
suspend fun reactivarPresentacion(presentacionId: String)
@Query("SELECT * FROM presentaciones WHERE activo = 0 ORDER BY nombre ASC")
fun obtenerTodasPresentacionesInactivas(): Flow<List<PresentacionEntity>>
@Query("UPDATE presentaciones SET activo = 0 WHERE id = :presentacionId")
suspend fun eliminarPresentacion(presentacionId: String)
// INVENTARIO
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarInventario(inventario: InventarioEntity)
@Query("SELECT COUNT(*) FROM aromas WHERE productoId = :productoId AND activo = 1")
suspend fun contarAromasDeProducto(productoId: String): Int
@Query("""
SELECT COALESCE(SUM(cantidadDisponible + cantidadReservada), 0)
FROM inventario
WHERE presentacionId = :presentacionId
""")
suspend fun contarExistenciasPresentacion(presentacionId: String): Int
@Query("SELECT * FROM inventario WHERE presentacionId = :presentacionId")
fun obtenerInventario(presentacionId: String): Flow<List<InventarioEntity>>
@Query("SELECT * FROM inventario ORDER BY sede ASC")
fun obtenerTodoInventario(): Flow<List<InventarioEntity>>
@Query("""
SELECT * FROM inventario
WHERE presentacionId = :presentacionId AND sede = :sede
LIMIT 1
""")
suspend fun buscarInventario(
presentacionId: String,
sede: String
): InventarioEntity?
// PRODUCCIONES
@Insert
suspend fun guardarProduccion(produccion: ProduccionEntity)
@Query("SELECT * FROM producciones ORDER BY fechaCreacion DESC")
fun obtenerProducciones(): Flow<List<ProduccionEntity>>
@Query("SELECT * FROM producciones WHERE id = :produccionId LIMIT 1")
suspend fun buscarProduccion(produccionId: String): ProduccionEntity?
// DISTRIBUCIONES DE PRODUCCION
@Insert
suspend fun guardarDistribucion(
distribucion: DistribucionProduccionEntity
)
@Query("""
SELECT * FROM distribuciones_produccion
WHERE produccionId = :produccionId
ORDER BY sede ASC
""")
fun obtenerDistribuciones(
produccionId: String
): Flow<List<DistribucionProduccionEntity>>
// MOVIMIENTOS DE INVENTARIO
@Insert
suspend fun guardarMovimiento(
movimiento: MovimientoInventarioEntity
)
@Query("""
SELECT * FROM movimientos_inventario
ORDER BY fechaCreacion DESC
""")
fun obtenerTodosMovimientos(): Flow<List<MovimientoInventarioEntity>>
@Query("""
SELECT * FROM movimientos_inventario
WHERE presentacionId = :presentacionId
ORDER BY fechaCreacion DESC
""")
fun obtenerMovimientosPresentacion(
presentacionId: String
): Flow<List<MovimientoInventarioEntity>>
@Query("""
SELECT * FROM movimientos_inventario
WHERE sede = :sede
ORDER BY fechaCreacion DESC
""")
fun obtenerMovimientosSede(
sede: String
): Flow<List<MovimientoInventarioEntity>>
// GUARDADO COMPLETO DE PRODUCCION
@Insert
suspend fun guardarDistribuciones(
distribuciones: List<DistribucionProduccionEntity>
)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarInventarios(
inventarios: List<InventarioEntity>
)
@Insert
suspend fun guardarMovimientos(
movimientos: List<MovimientoInventarioEntity>
)
@Transaction
suspend fun registrarProduccionCompleta(
produccion: ProduccionEntity,
distribuciones: List<DistribucionProduccionEntity>,
inventarios: List<InventarioEntity>,
movimientos: List<MovimientoInventarioEntity>
) {
guardarProduccion(produccion)
guardarDistribuciones(distribuciones)
guardarInventarios(inventarios)
guardarMovimientos(movimientos)
}
// CLIENTES
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarCliente(cliente: ClienteEntity)
@Query("SELECT * FROM clientes WHERE activo = 1 ORDER BY nombre ASC")
fun obtenerClientes(): Flow<List<ClienteEntity>>
@Query("SELECT * FROM clientes ORDER BY nombre ASC")
fun obtenerTodosClientes(): Flow<List<ClienteEntity>>
@Query("SELECT * FROM clientes WHERE id = :clienteId LIMIT 1")
suspend fun buscarCliente(clienteId: String): ClienteEntity?
@Query("UPDATE clientes SET activo = 0 WHERE id = :clienteId")
suspend fun desactivarCliente(clienteId: String)
@Query("UPDATE clientes SET activo = 1 WHERE id = :clienteId")
suspend fun reactivarCliente(clienteId: String)
// VENTAS
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarVenta(venta: VentaEntity)
@Query("SELECT * FROM ventas ORDER BY fechaCreacion DESC")
fun obtenerVentas(): Flow<List<VentaEntity>>
@Query("SELECT * FROM ventas WHERE id = :ventaId LIMIT 1")
suspend fun buscarVenta(ventaId: String): VentaEntity?
// DETALLES DE VENTA
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarDetalleVenta(detalle: DetalleVentaEntity)
@Query("SELECT * FROM detalles_venta WHERE ventaId = :ventaId")
fun obtenerDetallesVenta(ventaId: String): Flow<List<DetalleVentaEntity>>
// PAGOS Y ABONOS
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun guardarPagoVenta(pago: PagoVentaEntity)
@Query("SELECT * FROM pagos_venta WHERE ventaId = :ventaId ORDER BY fechaCreacion ASC")
fun obtenerPagosVenta(ventaId: String): Flow<List<PagoVentaEntity>>
@Query("SELECT * FROM pagos_venta ORDER BY fechaCreacion DESC")
fun obtenerTodosLosPagos(): Flow<List<PagoVentaEntity>>
// REGISTRO COMPLETO DE VENTAS
@Insert
suspend fun guardarDetallesVenta(
detalles: List<DetalleVentaEntity>
)
@Insert
suspend fun guardarPagosVenta(
pagos: List<PagoVentaEntity>
)
@Insert
suspend fun guardarMovimientosVenta(
movimientos: List<MovimientoInventarioEntity>
)
// DESCONTAR EXISTENCIAS DE UNA SEDE
@Query("""
UPDATE inventario
SET cantidadDisponible = cantidadDisponible - :cantidad,
fechaActualizacion = :fecha
WHERE presentacionId = :presentacionId
AND sede = :sede
AND cantidadDisponible >= :cantidad
""")
suspend fun descontarExistencias(
presentacionId: String,
sede: String,
cantidad: Int,
fecha: Long
): Int
// GUARDAR VENTA Y SUS MOVIMIENTOS EN UNA SOLA TRANSACCION
@Transaction
suspend fun registrarVentaCompleta(
venta: VentaEntity,
detalles: List<DetalleVentaEntity>,
pagos: List<PagoVentaEntity>,
movimientos: List<MovimientoInventarioEntity>,
descontarInventario: Boolean
) {
check(venta.estadoEntrega != "PENDIENTE_ENTREGA") {
"Un pedido debe pasar por el proceso de reserva antes de quedar pendiente de entrega."
}
check(descontarInventario == (venta.estadoEntrega == "ENTREGADA")) {
"El estado de entrega no coincide con la operación de inventario."
}
if (descontarInventario) {
for (detalle in detalles) {
val resultado = descontarExistencias(
presentacionId = detalle.presentacionId,
sede = venta.sede,
cantidad = detalle.cantidad,
fecha = System.currentTimeMillis()
)
check(resultado == 1) {
"No hay existencias suficientes para completar la venta."
}
}
}
guardarVenta(venta)
guardarDetallesVenta(detalles)
if (pagos.isNotEmpty()) {
guardarPagosVenta(pagos)
}
if (descontarInventario && movimientos.isNotEmpty()) {
guardarMovimientosVenta(movimientos)
}
}
// CONSULTAR DETALLES DE UN PEDIDO
@Query("""
SELECT * FROM detalles_venta
WHERE ventaId = :ventaId
""")
suspend fun buscarDetallesVenta(
ventaId: String
): List<DetalleVentaEntity>
// CAMBIAR ESTADO DE UN PEDIDO
@Query("""
UPDATE ventas
SET estadoEntrega = :nuevoEstado
WHERE id = :ventaId
AND estadoEntrega = :estadoActual
""")
suspend fun actualizarEstadoEntrega(
ventaId: String,
estadoActual: String,
nuevoEstado: String
): Int
// RESERVAR EXISTENCIAS
@Query("""
UPDATE inventario
SET cantidadDisponible = cantidadDisponible - :cantidad,
cantidadReservada = cantidadReservada + :cantidad,
fechaActualizacion = :fecha
WHERE presentacionId = :presentacionId
AND sede = :sede
AND cantidadDisponible >= :cantidad
""")
suspend fun reservarExistencias(
presentacionId: String,
sede: String,
cantidad: Int,
fecha: Long
): Int
// CONSUMIR EXISTENCIAS RESERVADAS AL ENTREGAR
@Query("""
UPDATE inventario
SET cantidadReservada = cantidadReservada - :cantidad,
fechaActualizacion = :fecha
WHERE presentacionId = :presentacionId
AND sede = :sede
AND cantidadReservada >= :cantidad
""")
suspend fun consumirExistenciasReservadas(
presentacionId: String,
sede: String,
cantidad: Int,
fecha: Long
): Int
// PASAR PEDIDO A PENDIENTE DE ENTREGA
@Transaction
suspend fun reservarPedidoParaEntrega(
ventaId: String
) {
val venta = buscarVenta(ventaId)
?: error("No se encontró el pedido.")
check(venta.estadoEntrega == "PENDIENTE_FABRICACION") {
"El pedido no está pendiente de fabricación."
}
val detalles = buscarDetallesVenta(ventaId)
check(detalles.isNotEmpty()) {
"El pedido no tiene productos registrados."
}
val fecha = System.currentTimeMillis()
val movimientos = mutableListOf<MovimientoInventarioEntity>()
val detallesAgrupados = detalles.groupBy { it.presentacionId }
for ((presentacionId, lineas) in detallesAgrupados) {
val cantidad = lineas.sumOf { it.cantidad }
val resultado = reservarExistencias(
presentacionId = presentacionId,
sede = venta.sede,
cantidad = cantidad,
fecha = fecha
)
check(resultado == 1) {
"No hay existencias suficientes para reservar el pedido."
}
movimientos.add(
MovimientoInventarioEntity(
id = UUID.randomUUID().toString(),
presentacionId = presentacionId,
sede = venta.sede,
tipoMovimiento = "RESERVA_PEDIDO",
cantidad = cantidad,
referenciaId = ventaId,
observacion = "Reserva de unidades para pedido"
)
)
}
guardarMovimientosVenta(movimientos)
val actualizado = actualizarEstadoEntrega(
ventaId = ventaId,
estadoActual = "PENDIENTE_FABRICACION",
nuevoEstado = "PENDIENTE_ENTREGA"
)
check(actualizado == 1) {
"No se pudo actualizar el estado del pedido."
}
}
// CONFIRMAR ENTREGA DE UN PEDIDO RESERVADO
@Transaction
suspend fun entregarPedidoReservado(
ventaId: String
) {
val venta = buscarVenta(ventaId)
?: error("No se encontró el pedido.")
check(venta.estadoEntrega == "PENDIENTE_ENTREGA") {
"El pedido no está pendiente de entrega."
}
val detalles = buscarDetallesVenta(ventaId)
check(detalles.isNotEmpty()) {
"El pedido no tiene productos registrados."
}
val fecha = System.currentTimeMillis()
val movimientos = mutableListOf<MovimientoInventarioEntity>()
val detallesAgrupados = detalles.groupBy { it.presentacionId }
for ((presentacionId, lineas) in detallesAgrupados) {
val cantidad = lineas.sumOf { it.cantidad }
val resultado = consumirExistenciasReservadas(
presentacionId = presentacionId,
sede = venta.sede,
cantidad = cantidad,
fecha = fecha
)
check(resultado == 1) {
"No se encontraron todas las unidades reservadas."
}
movimientos.add(
MovimientoInventarioEntity(
id = UUID.randomUUID().toString(),
presentacionId = presentacionId,
sede = venta.sede,
tipoMovimiento = "VENTA",
cantidad = cantidad,
referenciaId = ventaId,
observacion = "Salida por entrega de pedido"
)
)
}
guardarMovimientosVenta(movimientos)
val actualizado = actualizarEstadoEntrega(
ventaId = ventaId,
estadoActual = "PENDIENTE_ENTREGA",
nuevoEstado = "ENTREGADA"
)
check(actualizado == 1) {
"No se pudo confirmar la entrega del pedido."
}
}
@Query("""
UPDATE inventario
SET cantidadDisponible = cantidadDisponible + :cantidad,
cantidadReservada = cantidadReservada - :cantidad,
fechaActualizacion = :fecha
WHERE presentacionId = :presentacionId
AND sede = :sede
AND cantidadReservada >= :cantidad
""")
suspend fun liberarExistenciasReservadas(
presentacionId: String,
sede: String,
cantidad: Int,
fecha: Long
): Int
@Transaction
suspend fun cancelarPedido(ventaId: String) {
val venta = buscarVenta(ventaId)
?: error("No se encontró el pedido.")
check(
venta.estadoEntrega == "PENDIENTE_FABRICACION" ||
venta.estadoEntrega == "PENDIENTE_ENTREGA"
) {
"Este pedido no se puede cancelar."
}
val fecha = System.currentTimeMillis()
val movimientos = mutableListOf<MovimientoInventarioEntity>()
// Si ya estaba listo para entregar, liberamos las reservas.
if (venta.estadoEntrega == "PENDIENTE_ENTREGA") {
val detalles = buscarDetallesVenta(ventaId)
check(detalles.isNotEmpty()) {
"El pedido no tiene productos registrados."
}
val detallesAgrupados = detalles.groupBy { it.presentacionId }
for ((presentacionId, lineas) in detallesAgrupados) {
val cantidad = lineas.sumOf { it.cantidad }
val resultado = liberarExistenciasReservadas(
presentacionId,
venta.sede,
cantidad,
fecha
)
check(resultado == 1) {
"No se pudieron liberar todas las unidades reservadas."
}
movimientos.add(
MovimientoInventarioEntity(
id = UUID.randomUUID().toString(),
presentacionId = presentacionId,
sede = venta.sede,
tipoMovimiento = "LIBERACION_RESERVA",
cantidad = cantidad,
referenciaId = ventaId,
observacion = "Liberación de existencias por cancelación del pedido"
)
)
}
if (movimientos.isNotEmpty()) {
guardarMovimientosVenta(movimientos)
}
}
val actualizado = actualizarEstadoEntrega(
ventaId,
venta.estadoEntrega,
"CANCELADA"
)
check(actualizado == 1) {
"No se pudo cancelar el pedido."
}
}
// ESTADISTICAS DEL INICIO
// Total vendido durante el día
@Query("""
SELECT COALESCE(SUM(total), 0)
FROM ventas
WHERE fechaCreacion >= :inicioDia
AND fechaCreacion < :finDia
AND estadoEntrega != 'CANCELADA'
""")
fun obtenerVentasDelDia(
inicioDia: Long,
finDia: Long
): Flow<Double>
// Dinero recibido durante el día
@Query("""
SELECT COALESCE(SUM(monto), 0)
FROM pagos_venta
WHERE fechaCreacion >= :inicioDia
AND fechaCreacion < :finDia
AND ventaId IN (
SELECT id FROM ventas
WHERE estadoEntrega != 'CANCELADA'
)
""")
fun obtenerDineroRecibidoDelDia(
inicioDia: Long,
finDia: Long
): Flow<Double>
// Saldo pendiente de cobro de todas las ventas no canceladas
@Query("""
SELECT COALESCE(
SUM(MAX(
v.total - COALESCE(
(SELECT SUM(p.monto)
FROM pagos_venta p
WHERE p.ventaId = v.id),
0
),
0
)),
0
)
FROM ventas v
WHERE v.estadoEntrega != 'CANCELADA'
""")
fun obtenerTotalPorCobrar(): Flow<Double>
// Unidades disponibles en todas las sedes
@Query("""
SELECT COALESCE(SUM(cantidadDisponible), 0)
FROM inventario
""")
fun obtenerInventarioDisponible(): Flow<Int>
}
package com.negocio.desinfectantes.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "producciones")
data class ProduccionEntity(
@PrimaryKey
val id: String,
val presentacionId: String,
val cantidadProducida: Int,
val fechaCreacion: Long = System.currentTimeMillis()
)
package com.negocio.desinfectantes.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "distribuciones_produccion")
data class DistribucionProduccionEntity(
@PrimaryKey
val id: String,
val produccionId: String,
val sede: String,
val cantidad: Int,
val fechaCreacion: Long = System.currentTimeMillis()
)
package com.negocio.desinfectantes.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "inventario")
data class InventarioEntity(
@PrimaryKey
val id: String,
val presentacionId: String,
val sede: String,
val cantidadDisponible: Int = 0,
val cantidadReservada: Int = 0,
val fechaActualizacion: Long = System.currentTimeMillis()
)
package com.negocio.desinfectantes.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "movimientos_inventario")
data class MovimientoInventarioEntity(
@PrimaryKey
val id: String,
val presentacionId: String,
val sede: String,
val tipoMovimiento: String,
val cantidad: Int,
val referenciaId: String? = null,
val observacion: String? = null,
val fechaCreacion: Long = System.currentTimeMillis()
)
package com.negocio.desinfectantes.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "presentaciones")
data class PresentacionEntity(
@PrimaryKey
val id: String,
val aromaId: String,
val nombre: String,
val contenidoMl: Int,
val precio: Double,
val activo: Boolean = true,
val fechaCreacion: Long = System.currentTimeMillis()
)
package com.negocio.desinfectantes.data
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(
entities = [
ProductoEntity::class,
AromaEntity::class,
PresentacionEntity::class,
InventarioEntity::class,
ProduccionEntity::class,
DistribucionProduccionEntity::class,
MovimientoInventarioEntity::class,
ClienteEntity::class,
VentaEntity::class,
DetalleVentaEntity::class,
PagoVentaEntity::class
],
version = 5,
exportSchema = false
)
abstract class PrestigeDatabase : RoomDatabase() {
abstract fun productoDao(): ProductoDao
}
23,764 chars · codepastes.com