CrmDatabase.kt
142 lignes · 6002 octets
package fr.ebii.card2vcf.data import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import fr.ebii.card2vcf.sync.SyncMetaDao import fr.ebii.card2vcf.sync.SyncMetaEntity import fr.ebii.card2vcf.sync.SyncOpDao import fr.ebii.card2vcf.sync.SyncOpEntity @Database( entities = [ CrmContactEntity::class, CrmContactFts::class, DuplicateExclusionEntity::class, EntrepriseEntity::class, ProjetEntity::class, TacheEntity::class, InteractionEntity::class, WorkflowEntity::class, SyncOpEntity::class, SyncMetaEntity::class, RdvEntity::class, ReservationEntity::class, IndisponibiliteEntity::class, NoteProjetEntity::class, ], version = 8, exportSchema = false, ) @TypeConverters(Converters::class) abstract class CrmDatabase : RoomDatabase() { abstract fun crmContactDao(): CrmContactDao abstract fun entrepriseDao(): EntrepriseDao abstract fun projetDao(): ProjetDao abstract fun tacheDao(): TacheDao abstract fun interactionDao(): InteractionDao abstract fun workflowDao(): WorkflowDao abstract fun syncOpDao(): SyncOpDao abstract fun syncMetaDao(): SyncMetaDao abstract fun rdvDao(): RdvDao abstract fun reservationDao(): ReservationDao abstract fun indisponibiliteDao(): IndisponibiliteDao abstract fun noteProjetDao(): NoteProjetDao companion object { @Volatile private var instance: CrmDatabase? = null /** v3→v4 : colonne `lastError` sur `sync_ops` (état d'échec de push par op). */ val MIGRATION_3_4 = object : Migration(3, 4) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE sync_ops ADD COLUMN lastError TEXT") } } /** v5→v6 : colonnes `audioPath` et `transcriptionStatut` sur la table `interactions`. */ val MIGRATION_5_6 = object : Migration(5, 6) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE interactions ADD COLUMN audioPath TEXT") db.execSQL("ALTER TABLE interactions ADD COLUMN transcriptionStatut TEXT") } } /** v6→v7 : colonne `transcriptionErreur` sur `interactions` et `notes_projet`. */ val MIGRATION_6_7 = object : Migration(6, 7) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE interactions ADD COLUMN transcriptionErreur TEXT") db.execSQL("ALTER TABLE notes_projet ADD COLUMN transcriptionErreur TEXT") } } /** v4→v5 : table `notes_projet` (notes markdown de projet, champs audio pour phase 2). */ val MIGRATION_4_5 = object : Migration(4, 5) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL( """ CREATE TABLE IF NOT EXISTS notes_projet ( localId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, serverId TEXT, projetServerId TEXT NOT NULL, titre TEXT NOT NULL, texte TEXT NOT NULL, audioPath TEXT, transcriptionStatut TEXT, auteur TEXT NOT NULL, createdAt INTEGER NOT NULL, updatedAt INTEGER ) """.trimIndent(), ) } } /** * v7→v8 : planification des tâches (début, durée en jours ouvrés, * étiquette, regroupement, dépendances, sous-tâches) et réglages de * planification du projet. Le serveur envoyait déjà ces champs dans le * pull ; le client les ignorait. */ val MIGRATION_7_8 = object : Migration(7, 8) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE taches ADD COLUMN debut TEXT") db.execSQL("ALTER TABLE taches ADD COLUMN dureeJours INTEGER") db.execSQL("ALTER TABLE taches ADD COLUMN etiquette TEXT") db.execSQL("ALTER TABLE taches ADD COLUMN parentId TEXT") db.execSQL("ALTER TABLE taches ADD COLUMN dependDeJson TEXT NOT NULL DEFAULT '[]'") db.execSQL("ALTER TABLE taches ADD COLUMN sousTachesJson TEXT NOT NULL DEFAULT '[]'") db.execSQL("ALTER TABLE projets ADD COLUMN planification INTEGER NOT NULL DEFAULT 0") db.execSQL("ALTER TABLE projets ADD COLUMN echeance TEXT") db.execSQL("ALTER TABLE projets ADD COLUMN joursOuvres INTEGER NOT NULL DEFAULT 7") } } /** * v1/v2/v3 sync schema : pas de migration incrémentale — reset local à l'upgrade (acceptable v1-v3). * À partir de v3→v4 les migrations sont additives ; le fallback destructif reste en filet. * Les tests utilisent Room.inMemoryDatabaseBuilder sans fallback destructif. */ fun get(context: Context): CrmDatabase = instance ?: synchronized(this) { instance ?: Room.databaseBuilder( context.applicationContext, CrmDatabase::class.java, "card2vcf-crm.db", ) .addMigrations( MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, ) .fallbackToDestructiveMigration() .build() .also { instance = it } } } }
GitRust