-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase.py
More file actions
176 lines (144 loc) · 6.1 KB
/
database.py
File metadata and controls
176 lines (144 loc) · 6.1 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
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from datetime import datetime, timedelta
from sqlalchemy.sql import func
db = SQLAlchemy()
# --- Models ---
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
email = db.Column(db.String(100), unique=True, nullable=False)
password_hash = db.Column(db.String(200))
tenants = db.relationship('Tenant', backref='landlord', lazy=True)
class Tenant(db.Model):
__tablename__ = 'tenants'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
unit_name = db.Column(db.String(50), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
installments = db.relationship('Installment', backref='tenant', cascade="all, delete-orphan", lazy=True)
class Installment(db.Model):
__tablename__ = 'installments'
id = db.Column(db.Integer, primary_key=True)
tenant_id = db.Column(db.Integer, db.ForeignKey('tenants.id'), nullable=False)
due_date = db.Column(db.Date, nullable=False)
amount = db.Column(db.Float, nullable=False)
paid = db.Column(db.Float, default=0.0)
# --- Initialization ---
def init_db(app):
db.init_app(app)
with app.app_context():
db.create_all()
# --- Operations ---
def get_dashboard_data(user_id):
"""Fetch tenants and upcoming payment alerts for a specific user."""
# 1. Get user's tenants with installment progress
tenants = Tenant.query.filter_by(user_id=user_id).all()
tenant_data = []
for t in tenants:
total_inst = len(t.installments)
completed_inst = sum(1 for i in t.installments if i.paid >= i.amount)
tenant_data.append({
'id': t.id,
'name': t.name,
'unit_name': t.unit_name,
'total_installments': total_inst,
'completed_installments': completed_inst
})
# 2. Get alerts (payments due within 15 days OR Overdue)
today = datetime.today().date()
warning_date = today + timedelta(days=15)
# نجلب الدفعات غير المكتملة والتي تاريخها اليوم أو قبل 15 يوم (يشمل المتأخرات)
alerts_query = db.session.query(Installment, Tenant).join(Tenant).filter(
Tenant.user_id == user_id,
Installment.paid < Installment.amount,
Installment.due_date <= warning_date
).order_by(Installment.due_date).all() # ترتيب حسب التاريخ
alerts_data = []
for i, t in alerts_query:
days_diff = (i.due_date - today).days
# تصنيف الحالة
if days_diff < 0:
status = 'overdue' # أحمر
days_label = abs(days_diff) # عدد أيام التأخير
else:
status = 'upcoming' # برتقالي
days_label = days_diff # عدد الأيام المتبقية
alerts_data.append({
'name': t.name,
'unit_name': t.unit_name,
'remaining': i.amount - i.paid,
'due_date': i.due_date.strftime('%Y-%m-%d'),
'status': status, # overdue or upcoming
'days_diff': days_label # Number of days
})
return tenant_data, alerts_data
def get_financial_summary(user_id):
"""Calculate total paid and remaining amounts for the user's properties."""
installments = db.session.query(Installment).join(Tenant).filter(Tenant.user_id == user_id).all()
total_paid = 0
total_remaining = 0
for inst in installments:
paid_val = inst.paid if inst.paid is not None else 0.0
amount_val = inst.amount if inst.amount is not None else 0.0
total_paid += paid_val
total_remaining += (amount_val - paid_val)
return {
'total_paid': total_paid,
'total_remaining': total_remaining
}
def add_tenant(user_id, name, unit):
"""Add a new tenant if the name doesn't exist for this user."""
existing = Tenant.query.filter_by(user_id=user_id, name=name).first()
if existing:
return False
new_tenant = Tenant(user_id=user_id, name=name, unit_name=unit)
db.session.add(new_tenant)
db.session.commit()
return True
def get_tenant_details(tenant_id, user_id):
"""Get tenant object and their installments securely."""
tenant = Tenant.query.filter_by(id=tenant_id, user_id=user_id).first()
if not tenant:
return None, None
installments_data = [{
'id': i.id,
'date': i.due_date.strftime('%Y-%m-%d'),
'amount': i.amount,
'paid': i.paid
} for i in tenant.installments]
# Sort by date
installments_data.sort(key=lambda x: x['date'])
return tenant, installments_data
def add_installments_from_excel(tenant_id, data_list):
"""Bulk add installments. data_list = [(date_str, amount), ...]"""
for date_val, amount in data_list:
try:
# Handle various date formats potentially coming from Excel
if isinstance(date_val, str):
d_obj = datetime.strptime(date_val, '%Y-%m-%d').date()
else:
d_obj = date_val.date() # Assume it's a datetime object
new_inst = Installment(tenant_id=tenant_id, due_date=d_obj, amount=float(amount))
db.session.add(new_inst)
except Exception as e:
print(f"Skipping row due to error: {e}")
continue
db.session.commit()
def update_installment(inst_id, paid=None, amount=None):
"""Update payment or total amount for an installment."""
inst = Installment.query.get(inst_id)
if inst:
if paid is not None:
inst.paid = paid
if amount is not None:
inst.amount = amount
db.session.commit()
def delete_tenant(tenant_id, user_id):
"""Delete a tenant and all associated data."""
tenant = Tenant.query.filter_by(id=tenant_id, user_id=user_id).first()
if tenant:
db.session.delete(tenant)
db.session.commit()