Coverage Summary for Class: AppRoomDatabase (com.imecatro.demosales.datasource)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| AppRoomDatabase |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/2)
|
| AppRoomDatabase$Companion |
0%
(0/2)
|
0%
(0/2)
|
0%
(0/21)
|
0%
(0/69)
|
| Total |
0%
(0/3)
|
0%
(0/2)
|
0%
(0/22)
|
0%
(0/71)
|
package com.imecatro.demosales.datasource
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.imecatro.demosales.data.clients.datasource.ClientsDao
import com.imecatro.demosales.data.clients.model.ClientRoomEntity
import com.imecatro.demosales.data.clients.model.PurchaseRoomEntity
import com.imecatro.demosales.data.sales.datasource.OrdersRoomDao
import com.imecatro.demosales.data.sales.datasource.SalesRoomDao
import com.imecatro.demosales.data.sales.model.OrderDataRoomModel
import com.imecatro.demosales.data.sales.model.SaleDataRoomModel
import com.imecatro.products.data.datasource.CategoriesDao
import com.imecatro.products.data.datasource.ProductsDao
import com.imecatro.products.data.model.CategoryRoomEntity
import com.imecatro.products.data.model.ProductRoomEntity
import com.imecatro.products.data.model.StockRoomEntity
/**
* The main Room database for the application.
*
* This database aggregates entities from all feature modules and provides
* access to their respective DAOs.
*
* @property productsRoomDao DAO for products.
* @property categoriesRoomDao DAO for categories.
* @property salesRoomDao DAO for sales.
* @property ordersRoomDao DAO for orders.
* @property clientsRoomDao DAO for clients.
*/
@Database(
entities = [ProductRoomEntity::class,
SaleDataRoomModel::class,
OrderDataRoomModel::class,
ClientRoomEntity::class,
PurchaseRoomEntity::class,
StockRoomEntity::class,
CategoryRoomEntity::class],
version = 14
)
abstract class AppRoomDatabase : RoomDatabase() {
abstract fun productsRoomDao(): ProductsDao
abstract fun categoriesRoomDao(): CategoriesDao
abstract fun salesRoomDao(): SalesRoomDao
abstract fun ordersRoomDao(): OrdersRoomDao
abstract fun clientsRoomDao(): ClientsDao
companion object {
private var productsDao: ProductsDao? = null
private var salesDao: SalesRoomDao? = null
private var ordersDao: OrdersRoomDao? = null
private var clientsDao: ClientsDao? = null
private var categoriesDao: CategoriesDao? = null
/**
* Initializes and returns the [AppRoomDatabase] instance.
*
* @param context The application context.
* @return The initialized [AppRoomDatabase].
*/
fun initDatabase(context: Context): AppRoomDatabase {
val db = Room.databaseBuilder(
context,
AppRoomDatabase::class.java,
"puntrosales_demo_database"
)
.addMigrations(
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9,
MIGRATION_9_10,
MIGRATION_10_11,
MIGRATION_11_12,
MIGRATION_12_13,
MIGRATION_13_14
)
.build()
productsDao = db.productsRoomDao()
salesDao = db.salesRoomDao()
ordersDao = db.ordersRoomDao()
clientsDao = db.clientsRoomDao()
categoriesDao = db.categoriesRoomDao()
return db
}
/**
* Checks if the database DAOs are initialized.
*
* @return True if initialized, false otherwise.
*/
fun isActive(): Boolean {
return productsDao?.let { true } ?: false
}
}
}
val MIGRATION_6_7 = object : Migration(6, 7) {
override fun migrate(db: SupportSQLiteDatabase) {
// Round to, say, 6 decimals (adjust to your domain needs)
db.execSQL(
"""
UPDATE products_table
SET price = ROUND(price, 6)
""".trimIndent()
)
db.execSQL(
"""
UPDATE order_table
SET productPrice = ROUND(productPrice, 6)
""".trimIndent()
)
}
}
val MIGRATION_7_8 = object : Migration(7, 8) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE sales_table ADD COLUMN extra REAL NOT NULL DEFAULT 0.0")
}
}
val MIGRATION_8_9 = object : Migration(8, 9) {
override fun migrate(db: SupportSQLiteDatabase) {
// Evitar conflictos por FKs durante recreate
db.execSQL("PRAGMA foreign_keys=OFF")
// Renombrar tabla vieja
db.execSQL("ALTER TABLE `sales_table` RENAME TO `sales_table_old`")
// Esquema nuevo: totals_* son NULLABLE y SIN DEFAULT (porque totals es @Embedded nullable)
db.execSQL("""
CREATE TABLE IF NOT EXISTS `sales_table` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`clientId` INTEGER,
`creationDateMillis` INTEGER NOT NULL,
`status` TEXT NOT NULL,
`note` TEXT NOT NULL,
`totals_subtotal` REAL,
`totals_discount` REAL,
`totals_extra` REAL,
`totals_total` REAL,
`client_name_at_sale` TEXT,
`client_address_at_sale` TEXT
)
""".trimIndent())
// Detectar si la tabla vieja tenía columna 'extra'
val hasExtra = db.query("PRAGMA table_info(`sales_table_old`)").use { c ->
var found = false
val nameIdx = c.getColumnIndex("name")
while (c.moveToNext()) {
if (nameIdx >= 0 && c.getString(nameIdx) == "extra") { found = true; break }
}
found
}
val extraExpr = if (hasExtra) "COALESCE(s.extra, 0.0)" else "0.0"
// Copiar datos calculando totales
db.execSQL("""
INSERT INTO `sales_table` (
id, clientId, creationDateMillis, status, note,
totals_subtotal, totals_discount, totals_extra, totals_total,
client_name_at_sale, client_address_at_sale
)
SELECT
s.id,
s.clientId,
s.creationDateMillis,
s.status,
s.note,
/* subtotal */ COALESCE((SELECT SUM(o.qty * o.productPrice)
FROM order_table o
WHERE o.sale_id = s.id), 0.0),
/* discount */ 0.0,
/* extra */ $extraExpr,
/* total */ COALESCE((SELECT SUM(o.qty * o.productPrice)
FROM order_table o
WHERE o.sale_id = s.id), 0.0) + $extraExpr,
/* snapshot antiguos: NULL */
NULL,
NULL
FROM `sales_table_old` s
""".trimIndent())
// Limpiar vieja y recrear índice
db.execSQL("DROP TABLE `sales_table_old`")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_sales_table_clientId` ON `sales_table` (`clientId`)")
db.execSQL("PRAGMA foreign_keys=ON")
}
}
val MIGRATION_9_10 = object : Migration(9, 10) {
override fun migrate(db: SupportSQLiteDatabase) {
// 1) Crear categories_table si no existe (shape exacto)
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS categories_table (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL
)
""".trimIndent()
)
// 🔴 FALTABA: crear el índice que Room espera (no unique)
db.execSQL(
"""
CREATE INDEX IF NOT EXISTS index_categories_table_name
ON categories_table(name)
""".trimIndent()
)
// 2) Crear nueva tabla products con category_id + FK
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS products_table_new (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL,
price REAL NOT NULL,
currency TEXT NOT NULL,
unit TEXT NOT NULL,
stock REAL NOT NULL,
details TEXT NOT NULL,
imageUri TEXT NOT NULL,
category_id INTEGER,
FOREIGN KEY(category_id) REFERENCES categories_table(id)
ON UPDATE NO ACTION
ON DELETE SET NULL
)
""".trimIndent()
)
// 3) Copiar datos desde la products_table vieja (category_id = NULL)
db.execSQL(
"""
INSERT INTO products_table_new
(id, name, price, currency, unit, stock, details, imageUri, category_id)
SELECT id, name, price, currency, unit, stock, details, imageUri, NULL
FROM products_table
""".trimIndent()
)
// 4) Reemplazar tabla
db.execSQL("DROP TABLE products_table")
db.execSQL("ALTER TABLE products_table_new RENAME TO products_table")
// 5) Índice esperado por Room en products_table.category_id
db.execSQL(
"""
CREATE INDEX IF NOT EXISTS index_products_table_category_id
ON products_table(category_id)
""".trimIndent()
)
}
}
val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(db: SupportSQLiteDatabase) {
// Since the new 'barcode' column is nullable (String?),
// SQLite will automatically set existing rows to NULL.
// No default value is needed.
db.execSQL("ALTER TABLE products_table ADD COLUMN barcode TEXT")
}
}
val MIGRATION_11_12 = object : Migration(11, 12) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE client_table ADD COLUMN latitude REAL")
db.execSQL("ALTER TABLE client_table ADD COLUMN longitude REAL")
}
}
val MIGRATION_12_13 = object : Migration(12, 13) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("""
CREATE TABLE IF NOT EXISTS `purchases_table` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`purchaseNumber` TEXT NOT NULL,
`client_id` INTEGER NOT NULL,
`description` TEXT NOT NULL,
`amount` REAL NOT NULL,
`date` INTEGER NOT NULL,
FOREIGN KEY(`client_id`) REFERENCES `client_table`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent())
db.execSQL("CREATE INDEX IF NOT EXISTS `index_purchases_table_client_id` ON `purchases_table` (`client_id`)")
}
}
val MIGRATION_13_14 = object : Migration(13, 14) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE client_table ADD COLUMN accumulatedPurchases REAL NOT NULL DEFAULT 0.0")
db.execSQL("ALTER TABLE client_table ADD COLUMN isFavorite INTEGER NOT NULL DEFAULT 0")
}
}