fix: persist tender preparation and correct calendar export
This commit is contained in:
+56
-13
@@ -19,7 +19,7 @@ try {
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DATA_DIR = path.join(__dirname, 'data');
|
||||
const DATA_DIR = process.env.IHALEIST_USER_DATA_DIR || path.join(__dirname, 'data');
|
||||
const DB_PATH = path.join(DATA_DIR, 'users.sqlite');
|
||||
export const BELGE_DIR = path.join(DATA_DIR, 'belgeler');
|
||||
|
||||
@@ -47,6 +47,11 @@ CREATE TABLE IF NOT EXISTS kullanici_verisi (
|
||||
PRIMARY KEY (email, anahtar)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tender_state (
|
||||
email TEXT NOT NULL, tender_key TEXT NOT NULL, data TEXT NOT NULL,
|
||||
snapshot TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (email, tender_key)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS api_anahtarlari (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL,
|
||||
@@ -73,7 +78,7 @@ CREATE TABLE IF NOT EXISTS belgeler (
|
||||
olusturma TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_belge_email ON belgeler(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_belge_ikn ON belgeler(email, ikn);
|
||||
|
||||
`;
|
||||
|
||||
export function available() { return Boolean(DatabaseSync); }
|
||||
@@ -83,26 +88,34 @@ export function openDb() {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
fs.mkdirSync(BELGE_DIR, { recursive: true });
|
||||
const db = new DatabaseSync(DB_PATH);
|
||||
db.exec(SCHEMA);
|
||||
// Geçiş: belgeler ihaleye bağlanabilsin diye ikn kolonu eklendi
|
||||
try {
|
||||
const k = db.prepare('PRAGMA table_info(belgeler)').all().map((c) => c.name);
|
||||
if (!k.includes('ikn')) {
|
||||
db.exec('ALTER TABLE belgeler ADD COLUMN ikn TEXT');
|
||||
console.log('[user-store] belgeler.ikn kolonu eklendi');
|
||||
db.exec('PRAGMA busy_timeout = 5000');
|
||||
db.exec('BEGIN IMMEDIATE');
|
||||
db.exec(SCHEMA);
|
||||
const columns = new Set(db.prepare('PRAGMA table_info(belgeler)').all().map((c) => c.name));
|
||||
// Existing volumes predate these columns. Indexes must be created AFTER migration.
|
||||
for (const column of ['ikn', 'son_kullanma']) {
|
||||
if (!columns.has(column)) db.exec(`ALTER TABLE belgeler ADD COLUMN ${column} TEXT`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[user-store] belge şema geçişi atlandı:', e.message);
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_belge_ikn ON belgeler(email, ikn)');
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (version TEXT PRIMARY KEY, applied_at TEXT NOT NULL)`);
|
||||
db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)')
|
||||
.run('20260906_document_columns', new Date().toISOString());
|
||||
db.exec('COMMIT');
|
||||
} catch (error) {
|
||||
try { db.exec('ROLLBACK'); } catch { /* original error is authoritative */ }
|
||||
db.close();
|
||||
throw error;
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
const now = () => new Date().toISOString();
|
||||
const withDb = (fn, fallback = null) => {
|
||||
const withDb = (fn) => {
|
||||
const db = openDb();
|
||||
if (!db) return fallback;
|
||||
if (!db) throw new Error('Kullanıcı veri deposu kullanılamıyor.');
|
||||
// Never report a failed write as a successful operation or a failed read as an empty account.
|
||||
try { return fn(db); }
|
||||
catch (e) { console.warn('[user-store]', e.message); return fallback; }
|
||||
finally { db.close(); }
|
||||
};
|
||||
|
||||
@@ -275,3 +288,33 @@ export function documentUsage(email) {
|
||||
return { adet: r?.adet ?? 0, bayt: r?.bayt ?? 0 };
|
||||
}, { adet: 0, bayt: 0 });
|
||||
}
|
||||
|
||||
// Per-tender compare-and-swap prevents stale tabs from overwriting newer preparation.
|
||||
export function getTenderStates(email) {
|
||||
return withDb(db => Object.fromEntries(db.prepare('SELECT * FROM tender_state WHERE email=?').all(email)
|
||||
.map(r => [r.tender_key, { data: JSON.parse(r.data), snapshot: JSON.parse(r.snapshot), version: r.version }])));
|
||||
}
|
||||
export function saveTenderState(email, key, patch, snapshot, expectedVersion) {
|
||||
const fields = new Set(['takipte','incelendi','kanbanStatus','atananPersonel','gorev','notes','checklist','bidDraft']);
|
||||
if (!key || key.length > 100 || !patch || typeof patch !== 'object' || Array.isArray(patch)
|
||||
|| Object.keys(patch).some(k => !fields.has(k)) || !Number.isInteger(expectedVersion) || expectedVersion < 0)
|
||||
throw new Error('Geçersiz hazırlık kaydı.');
|
||||
return withDb(db => {
|
||||
db.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
const row = db.prepare('SELECT * FROM tender_state WHERE email=? AND tender_key=?').get(email,key);
|
||||
if ((row?.version || 0) !== expectedVersion) {
|
||||
const error = new Error('Bu ihale başka bir sekmede değişti. Güncel kayıt yüklendi; değişikliğinizi tekrar uygulayın.');
|
||||
error.kod = 'VERSION_CONFLICT'; throw error;
|
||||
}
|
||||
const record = { data: {...(row ? JSON.parse(row.data) : {}), ...patch},
|
||||
snapshot: row ? {...JSON.parse(row.snapshot), ...snapshot} : snapshot,
|
||||
version: expectedVersion + 1 };
|
||||
db.prepare(`INSERT INTO tender_state VALUES (?,?,?,?,?,?)
|
||||
ON CONFLICT(email,tender_key) DO UPDATE SET data=excluded.data,snapshot=excluded.snapshot,
|
||||
version=excluded.version,updated_at=excluded.updated_at`)
|
||||
.run(email,key,JSON.stringify(record.data),JSON.stringify(record.snapshot || {}),record.version,now());
|
||||
db.exec('COMMIT'); return record;
|
||||
} catch (error) { db.exec('ROLLBACK'); throw error; }
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user