Coverage for modules\planner\migrations\0001_initial.py: 75%
24 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 17:00 +0800
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 17:00 +0800
1# Generated by Django 5.1.15 on 2026-09-02 13:52
3import django.db.models.deletion
4import django.utils.timezone
5import uuid
6from decimal import Decimal
7from django.conf import settings
8from django.db import migrations, models
11def ensure_planner_schema(apps, schema_editor):
12 existing_tables = schema_editor.connection.introspection.table_names()
13 from modules.planner.models import Project, Board, BoardColumn, Sprint, Task, TaskComment, TimeLog
14 historical_models = {
15 'Project': Project,
16 'Board': Board,
17 'BoardColumn': BoardColumn,
18 'Sprint': Sprint,
19 'Task': Task,
20 'TaskComment': TaskComment,
21 'TimeLog': TimeLog,
22 }
24 if 'planner_project' not in existing_tables:
25 for name in ('Project', 'Board', 'BoardColumn', 'Sprint', 'Task', 'TaskComment', 'TimeLog'):
26 schema_editor.create_model(historical_models[name])
27 return
29 with schema_editor.connection.cursor() as cursor:
30 columns = {row[1] for row in cursor.execute('PRAGMA table_info(planner_project)').fetchall()}
31 additions = {
32 'goal': 'TEXT NULL',
33 'roadmap_start': 'DATE NULL',
34 'roadmap_end': 'DATE NULL',
35 'department_id': 'CHAR(32) NULL',
36 }
37 for column, definition in additions.items():
38 if column not in columns:
39 schema_editor.execute(f'ALTER TABLE planner_project ADD COLUMN {column} {definition}')
42class Migration(migrations.Migration):
44 initial = True
46 dependencies = [
47 ('team', '0002_alter_teammember_role'),
48 migrations.swappable_dependency(settings.AUTH_USER_MODEL),
49 ]
51 operations = [
52 migrations.SeparateDatabaseAndState(
53 state_operations=[
54 migrations.CreateModel(
55 name='Board',
56 fields=[
57 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
58 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
59 ('updated_at', models.DateTimeField(auto_now=True)),
60 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
61 ('name', models.CharField(default='Main Board', max_length=128)),
62 ],
63 options={
64 'abstract': False,
65 },
66 ),
67 migrations.CreateModel(
68 name='BoardColumn',
69 fields=[
70 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
71 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
72 ('updated_at', models.DateTimeField(auto_now=True)),
73 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
74 ('name', models.CharField(max_length=64)),
75 ('order', models.IntegerField(default=0)),
76 ('limit', models.IntegerField(default=0)),
77 ('status_category', models.CharField(choices=[('BACKLOG', 'Backlog'), ('TODO', 'To Do'), ('IN_PROGRESS', 'In Progress'), ('IN_REVIEW', 'In Review'), ('DONE', 'Done')], default='TODO', max_length=32)),
78 ('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='columns', to='planner.board')),
79 ],
80 options={
81 'ordering': ['order'],
82 },
83 ),
84 migrations.CreateModel(
85 name='Project',
86 fields=[
87 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
88 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
89 ('updated_at', models.DateTimeField(auto_now=True)),
90 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
91 ('name', models.CharField(max_length=128)),
92 ('key', models.CharField(db_index=True, max_length=16)),
93 ('description', models.TextField(blank=True, null=True)),
94 ('goal', models.TextField(blank=True, null=True)),
95 ('roadmap_start', models.DateField(blank=True, null=True)),
96 ('roadmap_end', models.DateField(blank=True, null=True)),
97 ('color', models.CharField(default='#6366F1', max_length=32)),
98 ('status', models.CharField(choices=[('ACTIVE', 'Active'), ('ARCHIVED', 'Archived'), ('COMPLETED', 'Completed')], default='ACTIVE', max_length=16)),
99 ('department', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='planner_projects', to='team.department')),
100 ('lead', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='led_projects', to=settings.AUTH_USER_MODEL)),
101 ],
102 options={
103 'ordering': ['name'],
104 'unique_together': {('tenant_id', 'key')},
105 },
106 ),
107 migrations.AddField(
108 model_name='board',
109 name='project',
110 field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='boards', to='planner.project'),
111 ),
112 migrations.CreateModel(
113 name='Sprint',
114 fields=[
115 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
116 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
117 ('updated_at', models.DateTimeField(auto_now=True)),
118 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
119 ('name', models.CharField(max_length=128)),
120 ('goal', models.TextField(blank=True, null=True)),
121 ('start_date', models.DateField(blank=True, null=True)),
122 ('end_date', models.DateField(blank=True, null=True)),
123 ('is_active', models.BooleanField(default=False)),
124 ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sprints', to='planner.project')),
125 ],
126 options={
127 'abstract': False,
128 },
129 ),
130 migrations.CreateModel(
131 name='Task',
132 fields=[
133 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
134 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
135 ('updated_at', models.DateTimeField(auto_now=True)),
136 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
137 ('title', models.CharField(max_length=255)),
138 ('description', models.TextField(blank=True, null=True)),
139 ('task_number', models.IntegerField()),
140 ('priority', models.CharField(choices=[('LOW', 'Low'), ('MEDIUM', 'Medium'), ('HIGH', 'High'), ('URGENT', 'Urgent')], default='MEDIUM', max_length=16)),
141 ('status', models.CharField(choices=[('BACKLOG', 'Backlog'), ('TODO', 'To Do'), ('IN_PROGRESS', 'In Progress'), ('IN_REVIEW', 'In Review'), ('DONE', 'Done')], default='TODO', max_length=16)),
142 ('order', models.IntegerField(default=0)),
143 ('due_date', models.DateField(blank=True, null=True)),
144 ('estimated_hours', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=6)),
145 ('tags', models.JSONField(blank=True, default=list)),
146 ('assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tasks', to=settings.AUTH_USER_MODEL)),
147 ('board', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='planner.board')),
148 ('column', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='planner.boardcolumn')),
149 ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='planner.project')),
150 ('reporter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reported_tasks', to=settings.AUTH_USER_MODEL)),
151 ('sprint', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='planner.sprint')),
152 ],
153 options={
154 'ordering': ['order', '-created_at'],
155 'unique_together': {('project', 'task_number')},
156 },
157 ),
158 migrations.CreateModel(
159 name='TaskComment',
160 fields=[
161 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
162 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
163 ('updated_at', models.DateTimeField(auto_now=True)),
164 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
165 ('content', models.TextField()),
166 ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='task_comments', to=settings.AUTH_USER_MODEL)),
167 ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='planner.task')),
168 ],
169 options={
170 'ordering': ['created_at'],
171 },
172 ),
173 migrations.CreateModel(
174 name='TimeLog',
175 fields=[
176 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
177 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
178 ('updated_at', models.DateTimeField(auto_now=True)),
179 ('tenant_id', models.UUIDField(db_index=True, help_text='Organization / Tenant ID')),
180 ('hours', models.DecimalField(decimal_places=2, max_digits=6)),
181 ('description', models.CharField(blank=True, max_length=255, null=True)),
182 ('logged_date', models.DateField(default=django.utils.timezone.now)),
183 ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='time_logs', to='planner.task')),
184 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='time_logs', to=settings.AUTH_USER_MODEL)),
185 ],
186 options={
187 'ordering': ['-logged_date', '-created_at'],
188 },
189 ),
190 ],
191 database_operations=[
192 migrations.RunPython(ensure_planner_schema, migrations.RunPython.noop),
193 ],
194 ),
195 ]