-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
293 lines (232 loc) · 10.3 KB
/
models.py
File metadata and controls
293 lines (232 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
"""
Modèles SQLAlchemy - Chantier Tracker
Définition de toutes les tables de la base de données
"""
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
# Table d'association pour les dépendances entre tâches (many-to-many self-referential)
task_dependencies = db.Table('task_dependencies',
db.Column('task_id', db.Integer, db.ForeignKey('task.id'), primary_key=True),
db.Column('depends_on_id', db.Integer, db.ForeignKey('task.id'), primary_key=True)
)
# Table d'association tâche ↔ checklist item (dépendance achat/démarche)
task_checklist = db.Table('task_checklist',
db.Column('task_id', db.Integer, db.ForeignKey('task.id'), primary_key=True),
db.Column('checklist_id', db.Integer, db.ForeignKey('checklist_item.id'), primary_key=True)
)
class ChecklistItem(db.Model):
"""Achat ou démarche à effectuer, peut bloquer des tâches"""
__tablename__ = 'checklist_item'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
category = db.Column(db.String(20), nullable=False) # 'achat' ou 'demarche'
done = db.Column(db.Boolean, default=False)
due_date = db.Column(db.Date, nullable=True) # Date de livraison / échéance
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Tâches qui dépendent de cet item
blocked_tasks = db.relationship(
'Task',
secondary=task_checklist,
back_populates='checklist_deps'
)
def __repr__(self):
return f'<ChecklistItem {self.category}:{self.title}>'
class User(UserMixin, db.Model):
"""Utilisateur de l'application"""
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
role = db.Column(db.String(20), nullable=False, default='owner') # 'owner' ou 'contractor'
weekly_summary = db.Column(db.Boolean, default=True) # Recevoir résumé hebdomadaire
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relations
comments = db.relationship('Comment', backref='author', lazy='dynamic')
def set_password(self, password):
"""Hasher le mot de passe"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Vérifier le mot de passe"""
return check_password_hash(self.password_hash, password)
def can_delete(self):
"""Seuls les propriétaires peuvent supprimer"""
return self.role == 'owner'
def __repr__(self):
return f'<User {self.username}>'
class Floor(db.Model):
"""Étage du bâtiment"""
__tablename__ = 'floor'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False) # ex: "RDC", "1er étage"
order = db.Column(db.Integer, default=0) # Pour l'ordre d'affichage
# Relations
rooms = db.relationship('Room', backref='floor', lazy='dynamic', cascade='all, delete-orphan')
@property
def progress(self):
"""Calcul du % d'avancement de l'étage"""
all_tasks = []
for room in self.rooms:
all_tasks.extend(room.tasks.all())
if not all_tasks:
return 0
return int(sum(t.progress for t in all_tasks) / len(all_tasks))
def __repr__(self):
return f'<Floor {self.name}>'
class Room(db.Model):
"""Pièce du logement"""
__tablename__ = 'room'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # ex: "Salon", "Chambre 1"
floor_id = db.Column(db.Integer, db.ForeignKey('floor.id'), nullable=False)
icon = db.Column(db.String(10), default='🏠') # Emoji pour la pièce
color = db.Column(db.String(7), default='#6366f1') # Couleur hex
# Relations
tasks = db.relationship('Task', backref='room', lazy='dynamic')
photos = db.relationship('Photo', backref='room', lazy='dynamic')
@property
def progress(self):
"""Calcul du % d'avancement de la pièce"""
tasks_list = self.tasks.all()
if not tasks_list:
return 0
return int(sum(t.progress for t in tasks_list) / len(tasks_list))
@property
def task_counts(self):
"""Nombre de tâches par statut"""
tasks_list = self.tasks.all()
return {
'todo': sum(1 for t in tasks_list if t.status == 'todo'),
'in_progress': sum(1 for t in tasks_list if t.status == 'in_progress'),
'blocked': sum(1 for t in tasks_list if t.status == 'blocked'),
'done': sum(1 for t in tasks_list if t.status == 'done'),
'total': len(tasks_list)
}
def __repr__(self):
return f'<Room {self.name}>'
class WorkType(db.Model):
"""Type de travaux (corps de métier)"""
__tablename__ = 'work_type'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # ex: "Plomberie", "Électricité"
color = db.Column(db.String(7), nullable=False) # Couleur hex pour le badge
icon = db.Column(db.String(10), default='🔧') # Emoji
contractor_name = db.Column(db.String(100)) # Nom de l'entreprise
# Relations
tasks = db.relationship('Task', backref='work_type', lazy='dynamic')
@property
def progress(self):
"""Calcul du % d'avancement du type de travaux"""
tasks_list = self.tasks.all()
if not tasks_list:
return 0
return int(sum(t.progress for t in tasks_list) / len(tasks_list))
def __repr__(self):
return f'<WorkType {self.name}>'
class Task(db.Model):
"""Tâche du chantier - entité centrale"""
__tablename__ = 'task'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
# Localisation et catégorie
room_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=False)
work_type_id = db.Column(db.Integer, db.ForeignKey('work_type.id'), nullable=False)
# Statut et priorité
status = db.Column(db.String(20), nullable=False, default='todo')
# Valeurs : 'todo' | 'in_progress' | 'blocked' | 'done'
priority = db.Column(db.String(10), nullable=False, default='normal')
# Valeurs : 'critical' | 'high' | 'normal' | 'low'
# Planification
start_date = db.Column(db.Date)
end_date = db.Column(db.Date)
actual_end_date = db.Column(db.Date)
# Avancement
progress = db.Column(db.Integer, default=0) # 0 à 100
# Qui réalise : True = entrepreneur, False = nous-mêmes (défaut)
is_self_managed = db.Column(db.Boolean, default=False)
# Métadonnées
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relations
photos = db.relationship('Photo', backref='task', lazy='dynamic', cascade='all, delete-orphan')
comments = db.relationship('Comment', backref='task', lazy='dynamic', cascade='all, delete-orphan')
# Dépendances self-referential (tâches dont celle-ci dépend)
dependencies = db.relationship(
'Task',
secondary=task_dependencies,
primaryjoin=(id == task_dependencies.c.task_id),
secondaryjoin=(id == task_dependencies.c.depends_on_id),
backref='dependents',
lazy='dynamic'
)
# Achats / démarches dont cette tâche dépend
checklist_deps = db.relationship(
'ChecklistItem',
secondary=task_checklist,
back_populates='blocked_tasks'
)
@property
def is_blocked_by_dependencies(self):
"""Vérifie si toutes les dépendances sont terminées"""
task_blocked = any(dep.status != 'done' for dep in self.dependencies)
checklist_blocked = any(not item.done for item in self.checklist_deps)
return task_blocked or checklist_blocked
def update_status_from_dependencies(self):
"""Force le statut 'bloqué' si les dépendances ne sont pas terminées"""
if self.is_blocked_by_dependencies and self.status not in ('done', 'blocked'):
self.status = 'blocked'
@property
def status_label(self):
"""Libellé français du statut"""
labels = {
'todo': 'À faire',
'in_progress': 'En cours',
'blocked': 'Bloquée',
'done': 'Terminée'
}
return labels.get(self.status, self.status)
@property
def priority_label(self):
"""Libellé français de la priorité"""
labels = {
'critical': 'Critique',
'high': 'Haute',
'normal': 'Normale',
'low': 'Basse'
}
return labels.get(self.priority, self.priority)
@property
def is_overdue(self):
"""Vérifie si la tâche est en retard"""
if self.end_date and self.status != 'done':
return self.end_date < datetime.utcnow().date()
return False
def __repr__(self):
return f'<Task {self.title}>'
class Photo(db.Model):
"""Photo attachée à une tâche ou une pièce"""
__tablename__ = 'photo'
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False)
original_filename = db.Column(db.String(255))
caption = db.Column(db.String(300))
task_id = db.Column(db.Integer, db.ForeignKey('task.id'), nullable=True)
room_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=True)
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
file_size = db.Column(db.Integer) # Taille en bytes
def __repr__(self):
return f'<Photo {self.filename}>'
class Comment(db.Model):
"""Commentaire horodaté sur une tâche"""
__tablename__ = 'comment'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
task_id = db.Column(db.Integer, db.ForeignKey('task.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return f'<Comment by {self.user_id} on task {self.task_id}>'