Files
daywork/intern-log/src/db/schema.ts
T

221 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Dexie, { type Table } from 'dexie'
export interface Project {
id?: number
name: string
archived: number
created_at: string
}
export interface WorkLog {
id?: number
date: string
created_at: string
}
export interface LogItem {
id?: number
work_log_id: number
content: string
project_id: number | null
category: string
minutes: number | null
is_milestone: number
needs_followup: number
takeaway: string | null
remark: string | null
sort_order: number
created_at: string
}
export interface Attendance {
id?: number
date: string
clock_in_at: string | null
clock_out_at: string | null
kind: string
note: string | null
location?: string | null
created_at: string
}
export interface WeeklyNote {
id?: number
week_start: string
highlights: string | null
challenges: string | null
plan_next: string | null
created_at: string
}
export interface Goal {
id?: number
title: string // 长期计划,如:学完产品文档 / 本月健身 20 次
kind: string
start_date: string // YYYY-MM-DD
end_date: string
note: string | null
uuid: string
created_at: string
updated_at: string
}
export interface PlanItem {
id?: number
date: string // YYYY-MM-DD
start_time: string // HH:mm
end_time: string // HH:mm
title: string // 计划内容,如:产品学习 / 健身
kind: string // 类别:学习 / 健身 / 自律 / 其他自定义
note: string | null
status: string // todo | done | skip
actual_minutes: number // 计时时钟累计的实际投入分钟
timer_started_at: string | null
goal_id: number | null // 来源长期计划(本地 id)
uuid: string
created_at: string
updated_at: string
}
export interface Memo {
id?: number
title: string
content: string
created_at: string
updated_at: string
}
export interface Credential {
id?: number
system: string
account: string
password: string
remark: string
created_at: string
updated_at: string
}
export interface Transaction {
id?: number
type: string // income | expense
category: string
amount: number // 单位:分
date: string // YYYY-MM-DD
note: string
reimbursable: number // 支出是否需要报销
reimbursed: number // 是否已报销
created_at: string
}
// 同步镜像:记录上次同步时每条数据的哈希,用于增量差异检测
export interface SyncMirror {
id?: number
table: string
uuid: string
hash: string
}
// 删除墓碑:同步时告知其他端该记录已删除
export interface SyncTombstone {
id?: number
table: string
uuid: string
deleted_at: string
}
export class WorklogDB extends Dexie {
project!: Table<Project, number>
work_log!: Table<WorkLog, number>
log_item!: Table<LogItem, number>
attendance!: Table<Attendance, number>
weekly_note!: Table<WeeklyNote, number>
setting!: Table<{ key: string; value: string }, string>
memo!: Table<Memo, number>
credential!: Table<Credential, number>
// 注意:transaction 表通过 db.table('transaction') 访问,
// 因为 db.transaction 会与 Dexie 内置的 transaction() 方法冲突
sync_mirror!: Table<SyncMirror, number>
tombstone!: Table<SyncTombstone, number>
plan!: Table<PlanItem, number>
goal!: Table<Goal, number>
constructor(dbName = 'intern_worklog') {
super(dbName)
this.version(1).stores({
project: '++id, &name, archived',
work_log: '++id, &date',
log_item: '++id, work_log_id, project_id, category',
attendance: '++id, &date',
weekly_note: '++id, &week_start',
setting: 'key'
})
this.version(2).stores({
memo: '++id, updated_at',
credential: '++id, system, updated_at'
})
this.version(3).stores({
transaction: '++id, date, type, category'
})
this.version(4).stores({
sync_mirror: '++id, table, uuid',
tombstone: '++id, table, uuid, deleted_at'
})
this.version(5).stores({
plan: '++id, date, status'
}).upgrade(async tx => {
// 旧固定分类 key 迁移为用户自定义分类标签(v0.4.0 起分类为用户数据)
const map: Record<string, string> = {
dev: '项目实操',
test: '项目实操',
bugfix: '售后服务',
learning: '产品学习',
meeting: '打杂',
doc: '打杂',
other: '打杂'
}
await tx.table('log_item').toCollection().modify((item: { category?: string }) => {
if (item.category && map[item.category]) item.category = map[item.category]
})
})
this.version(6).stores({
plan: '++id, date, status, goal_id',
goal: '++id, start_date'
})
}
}
// 当前登录用户绑定的数据库实例:不同用户物理隔离不同库,登录后由 auth 模块注入
export let db: WorklogDB | undefined
export function bindDatabase(instance: WorklogDB): void {
db = instance
}
export function unbindDatabase(): void {
if (db) db.close()
db = undefined
}
export const SCHEMA_VERSION = 6
export const EXPENSE_CATEGORIES: string[] = ['餐饮', '交通', '住房', '通讯', '学习', '娱乐', '其他']
export const INCOME_CATEGORIES: string[] = ['工资', '报销', '其他']
export const DEFAULT_LOCATIONS: string[] = ['公司', '客户现场', '出差', '居家办公']
// 旧版固定分类 key → 默认标签的映射(仅用于历史数据迁移与展示兜底)
export const LEGACY_CATEGORY_LABELS: Record<string, string> = {
dev: '项目实操',
test: '项目实操',
bugfix: '售后服务',
learning: '产品学习',
meeting: '打杂',
doc: '打杂',
other: '打杂'
}
// v0.4.0 起分类即用户自定义标签本身;历史 key 在此直通转换
export function categoryLabel(key: string): string {
return LEGACY_CATEGORY_LABELS[key] || key
}