kamau1 commited on
Commit
5d3b68c
Β·
1 Parent(s): 95005e1

Fix runtime error: remove await from sync function, update AuditService calls to new API

Browse files
docs/devlogs/server/runtimeerror.txt CHANGED
@@ -1,398 +1,57 @@
1
- ===== Application Startup at 2025-12-10 21:53:44 =====
2
 
3
- INFO: Started server process [7]
4
- INFO: Waiting for application startup.
5
- INFO: 2025-12-10T21:54:05 - app.main: ============================================================
6
- INFO: 2025-12-10T21:54:05 - app.main: πŸš€ SwiftOps API v1.0.0 | PRODUCTION
7
- INFO: 2025-12-10T21:54:05 - app.main: πŸ“Š Dashboard: Enabled
8
- INFO: 2025-12-10T21:54:05 - app.main: ============================================================
9
- INFO: 2025-12-10T21:54:05 - app.main: πŸ“¦ Database:
10
- INFO: 2025-12-10T21:54:05 - app.main: βœ“ Connected | 47 tables | 6 users
11
- INFO: 2025-12-10T21:54:05 - app.main: πŸ’Ύ Cache & Sessions:
12
- INFO: 2025-12-10T21:54:06 - app.services.otp_service: βœ… OTP Service initialized with Redis storage
13
- INFO: 2025-12-10T21:54:07 - app.main: βœ“ Redis: Connected
14
- INFO: 2025-12-10T21:54:07 - app.main: πŸ”Œ External Services:
15
- INFO: 2025-12-10T21:54:07 - app.main: βœ“ Cloudinary: Connected
16
- INFO: 2025-12-10T21:54:07 - app.main: βœ“ Resend: Configured
17
- INFO: 2025-12-10T21:54:07 - app.main: β—‹ WASender: Disconnected
18
- INFO: 2025-12-10T21:54:07 - app.main: βœ“ Supabase: Connected | 6 buckets
19
- INFO: 2025-12-10T21:54:07 - app.main: ⏰ Scheduler:
20
- INFO: 2025-12-10T21:54:07 - apscheduler.scheduler: Adding job tentatively -- it will be properly scheduled when the scheduler starts
21
- INFO: 2025-12-10T21:54:07 - apscheduler.scheduler: Added job "Daily Field Agent Reconciliation" to job store "default"
22
- INFO: 2025-12-10T21:54:07 - apscheduler.scheduler: Scheduler started
23
- INFO: 2025-12-10T21:54:07 - app.tasks.scheduler: Reconciliation scheduler started (runs at 10 PM Africa/Nairobi)
24
- INFO: 2025-12-10T21:54:07 - app.main: βœ“ Daily reconciliation scheduler started (runs at midnight)
25
- INFO: 2025-12-10T21:54:07 - app.main: ============================================================
26
- INFO: 2025-12-10T21:54:07 - app.main: βœ… Startup complete | Ready to serve requests
27
- INFO: 2025-12-10T21:54:07 - app.main: ============================================================
28
- INFO: Application startup complete.
29
- INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
30
- INFO: 2025-12-10T22:00:00 - apscheduler.executors.default: Running job "Daily Field Agent Reconciliation (trigger: cron[hour='22', minute='0'], next run at: 2025-12-10 22:00:00 UTC)" (scheduled at 2025-12-10 22:00:00+00:00)
31
- INFO: 2025-12-10T22:00:00 - app.tasks.scheduler: Starting scheduled validation for 2025-12-10
32
- INFO: 2025-12-10T22:00:00 - app.tasks.scheduler: Validating 1 projects for 2025-12-10
33
- INFO: 2025-12-10T22:00:00 - app.services.reconciliation.reconciliation_service: Starting reconciliation: project=0ade6bd1-e492-4e25-b681-59f42058d29a, date=2025-12-10, type=scheduled
34
- INFO: 2025-12-10T22:00:00 - app.services.reconciliation.reconciliation_service: Aggregated 1 agents in 71ms
35
- INFO: 2025-12-10T22:00:00 - app.services.reconciliation.anomaly_detector: Detected 0 anomalies across 1 agents
36
- ERROR: 2025-12-10T22:00:00 - app.services.reconciliation.reconciliation_service: Reconciliation failed: (psycopg2.ProgrammingError) can't adapt type 'dict'
37
- [SQL:
38
- UPDATE reconciliation_runs
39
- SET
40
- status = 'completed',
41
- completed_at = NOW(),
42
- agents_processed = %(agents_processed)s,
43
- timesheets_created = %(timesheets_created)s,
44
- timesheets_updated = %(timesheets_updated)s,
45
- assignments_processed = %(assignments_processed)s,
46
- expenses_processed = %(expenses_processed)s,
47
- summary_stats = %(summary_stats)s,
48
- anomalies_detected = %(anomalies)s,
49
- execution_time_ms = %(execution_time_ms)s,
50
- query_time_ms = %(query_time_ms)s,
51
- updated_at = NOW()
52
- WHERE id = %(run_id)s
53
- ]
54
- [parameters: {'agents_processed': 1, 'timesheets_created': 0, 'timesheets_updated': 1, 'assignments_processed': 1, 'expenses_processed': 3, 'summary_stats': {'total_agents': 1, 'total_tickets_assigned': 1, 'total_tickets_completed': 2, 'total_tickets_rejected': 0, 'total_tickets_cancelled': 0, 'total_expen ... (37 characters truncated) ... es': 2370.0, 'total_pending_expenses': 7650.0, 'total_expense_claims': 3, 'avg_tickets_per_agent': 2.0, 'avg_expenses_per_agent': Decimal('10020.00')}, 'anomalies': [], 'execution_time_ms': 288, 'query_time_ms': 71, 'run_id': 'ade18d69-39da-40da-92bf-5ff92beaf9ea'}]
55
- (Background on this error at: https://sqlalche.me/e/20/f405)
56
  Traceback (most recent call last):
57
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
58
- self.dialect.do_execute(
59
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
60
- cursor.execute(statement, parameters)
61
- psycopg2.ProgrammingError: can't adapt type 'dict'
62
-
63
- The above exception was the direct cause of the following exception:
64
-
65
- Traceback (most recent call last):
66
- File "/app/src/app/services/reconciliation/reconciliation_service.py", line 117, in reconcile_project_day
67
- self._complete_run(
68
- File "/app/src/app/services/reconciliation/reconciliation_service.py", line 482, in _complete_run
69
- self.db.execute(query, {
70
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
71
- return self._execute_internal(
72
- ^^^^^^^^^^^^^^^^^^^^^^^
73
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2199, in _execute_internal
74
- result = conn.execute(
75
- ^^^^^^^^^^^^^
76
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
77
- return meth(
78
- ^^^^^
79
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
80
- return connection._execute_clauseelement(
81
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
83
- ret = self._execute_context(
84
- ^^^^^^^^^^^^^^^^^^^^^^
85
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
86
- return self._exec_single_context(
87
  ^^^^^^^^^^^^^^^^^^^^^^^^^^
88
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
89
- self._handle_dbapi_exception(
90
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
91
- raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
92
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
93
- self.dialect.do_execute(
94
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
95
- cursor.execute(statement, parameters)
96
- sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) can't adapt type 'dict'
97
- [SQL:
98
- UPDATE reconciliation_runs
99
- SET
100
- status = 'completed',
101
- completed_at = NOW(),
102
- agents_processed = %(agents_processed)s,
103
- timesheets_created = %(timesheets_created)s,
104
- timesheets_updated = %(timesheets_updated)s,
105
- assignments_processed = %(assignments_processed)s,
106
- expenses_processed = %(expenses_processed)s,
107
- summary_stats = %(summary_stats)s,
108
- anomalies_detected = %(anomalies)s,
109
- execution_time_ms = %(execution_time_ms)s,
110
- query_time_ms = %(query_time_ms)s,
111
- updated_at = NOW()
112
- WHERE id = %(run_id)s
113
- ]
114
- [parameters: {'agents_processed': 1, 'timesheets_created': 0, 'timesheets_updated': 1, 'assignments_processed': 1, 'expenses_processed': 3, 'summary_stats': {'total_agents': 1, 'total_tickets_assigned': 1, 'total_tickets_completed': 2, 'total_tickets_rejected': 0, 'total_tickets_cancelled': 0, 'total_expen ... (37 characters truncated) ... es': 2370.0, 'total_pending_expenses': 7650.0, 'total_expense_claims': 3, 'avg_tickets_per_agent': 2.0, 'avg_expenses_per_agent': Decimal('10020.00')}, 'anomalies': [], 'execution_time_ms': 288, 'query_time_ms': 71, 'run_id': 'ade18d69-39da-40da-92bf-5ff92beaf9ea'}]
115
- (Background on this error at: https://sqlalche.me/e/20/f405)
116
- ERROR: 2025-12-10T22:00:00 - app.tasks.scheduler: Failed to validate project 0ade6bd1-e492-4e25-b681-59f42058d29a (Atomio Fttx): Reconciliation failed: (psycopg2.ProgrammingError) can't adapt type 'dict'
117
- [SQL:
118
- UPDATE reconciliation_runs
119
- SET
120
- status = 'completed',
121
- completed_at = NOW(),
122
- agents_processed = %(agents_processed)s,
123
- timesheets_created = %(timesheets_created)s,
124
- timesheets_updated = %(timesheets_updated)s,
125
- assignments_processed = %(assignments_processed)s,
126
- expenses_processed = %(expenses_processed)s,
127
- summary_stats = %(summary_stats)s,
128
- anomalies_detected = %(anomalies)s,
129
- execution_time_ms = %(execution_time_ms)s,
130
- query_time_ms = %(query_time_ms)s,
131
- updated_at = NOW()
132
- WHERE id = %(run_id)s
133
- ]
134
- [parameters: {'agents_processed': 1, 'timesheets_created': 0, 'timesheets_updated': 1, 'assignments_processed': 1, 'expenses_processed': 3, 'summary_stats': {'total_agents': 1, 'total_tickets_assigned': 1, 'total_tickets_completed': 2, 'total_tickets_rejected': 0, 'total_tickets_cancelled': 0, 'total_expen ... (37 characters truncated) ... es': 2370.0, 'total_pending_expenses': 7650.0, 'total_expense_claims': 3, 'avg_tickets_per_agent': 2.0, 'avg_expenses_per_agent': Decimal('10020.00')}, 'anomalies': [], 'execution_time_ms': 288, 'query_time_ms': 71, 'run_id': 'ade18d69-39da-40da-92bf-5ff92beaf9ea'}]
135
- (Background on this error at: https://sqlalche.me/e/20/f405)
136
- Traceback (most recent call last):
137
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
138
- self.dialect.do_execute(
139
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
140
- cursor.execute(statement, parameters)
141
- psycopg2.ProgrammingError: can't adapt type 'dict'
142
-
143
- The above exception was the direct cause of the following exception:
144
-
145
- Traceback (most recent call last):
146
- File "/app/src/app/services/reconciliation/reconciliation_service.py", line 117, in reconcile_project_day
147
- self._complete_run(
148
- File "/app/src/app/services/reconciliation/reconciliation_service.py", line 482, in _complete_run
149
- self.db.execute(query, {
150
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
151
- return self._execute_internal(
152
- ^^^^^^^^^^^^^^^^^^^^^^^
153
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2199, in _execute_internal
154
- result = conn.execute(
155
- ^^^^^^^^^^^^^
156
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
157
- return meth(
158
- ^^^^^
159
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
160
- return connection._execute_clauseelement(
161
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
162
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
163
- ret = self._execute_context(
164
- ^^^^^^^^^^^^^^^^^^^^^^
165
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
166
- return self._exec_single_context(
167
- ^^^^^^^^^^^^^^^^^^^^^^^^^^
168
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
169
- self._handle_dbapi_exception(
170
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
171
- raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
172
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
173
- self.dialect.do_execute(
174
- File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
175
- cursor.execute(statement, parameters)
176
- sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) can't adapt type 'dict'
177
- [SQL:
178
- UPDATE reconciliation_runs
179
- SET
180
- status = 'completed',
181
- completed_at = NOW(),
182
- agents_processed = %(agents_processed)s,
183
- timesheets_created = %(timesheets_created)s,
184
- timesheets_updated = %(timesheets_updated)s,
185
- assignments_processed = %(assignments_processed)s,
186
- expenses_processed = %(expenses_processed)s,
187
- summary_stats = %(summary_stats)s,
188
- anomalies_detected = %(anomalies)s,
189
- execution_time_ms = %(execution_time_ms)s,
190
- query_time_ms = %(query_time_ms)s,
191
- updated_at = NOW()
192
- WHERE id = %(run_id)s
193
- ]
194
- [parameters: {'agents_processed': 1, 'timesheets_created': 0, 'timesheets_updated': 1, 'assignments_processed': 1, 'expenses_processed': 3, 'summary_stats': {'total_agents': 1, 'total_tickets_assigned': 1, 'total_tickets_completed': 2, 'total_tickets_rejected': 0, 'total_tickets_cancelled': 0, 'total_expen ... (37 characters truncated) ... es': 2370.0, 'total_pending_expenses': 7650.0, 'total_expense_claims': 3, 'avg_tickets_per_agent': 2.0, 'avg_expenses_per_agent': Decimal('10020.00')}, 'anomalies': [], 'execution_time_ms': 288, 'query_time_ms': 71, 'run_id': 'ade18d69-39da-40da-92bf-5ff92beaf9ea'}]
195
- (Background on this error at: https://sqlalche.me/e/20/f405)
196
-
197
- The above exception was the direct cause of the following exception:
198
-
199
- Traceback (most recent call last):
200
- File "/app/src/app/tasks/scheduler.py", line 132, in validate_all_projects
201
- run_id = service.reconcile_project_day(
202
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
203
- File "/app/src/app/services/reconciliation/reconciliation_service.py", line 149, in reconcile_project_day
204
- raise ReconciliationError(f"Reconciliation failed: {str(e)}") from e
205
- app.services.reconciliation.reconciliation_service.ReconciliationError: Reconciliation failed: (psycopg2.ProgrammingError) can't adapt type 'dict'
206
- [SQL:
207
- UPDATE reconciliation_runs
208
- SET
209
- status = 'completed',
210
- completed_at = NOW(),
211
- agents_processed = %(agents_processed)s,
212
- timesheets_created = %(timesheets_created)s,
213
- timesheets_updated = %(timesheets_updated)s,
214
- assignments_processed = %(assignments_processed)s,
215
- expenses_processed = %(expenses_processed)s,
216
- summary_stats = %(summary_stats)s,
217
- anomalies_detected = %(anomalies)s,
218
- execution_time_ms = %(execution_time_ms)s,
219
- query_time_ms = %(query_time_ms)s,
220
- updated_at = NOW()
221
- WHERE id = %(run_id)s
222
- ]
223
- [parameters: {'agents_processed': 1, 'timesheets_created': 0, 'timesheets_updated': 1, 'assignments_processed': 1, 'expenses_processed': 3, 'summary_stats': {'total_agents': 1, 'total_tickets_assigned': 1, 'total_tickets_completed': 2, 'total_tickets_rejected': 0, 'total_tickets_cancelled': 0, 'total_expen ... (37 characters truncated) ... es': 2370.0, 'total_pending_expenses': 7650.0, 'total_expense_claims': 3, 'avg_tickets_per_agent': 2.0, 'avg_expenses_per_agent': Decimal('10020.00')}, 'anomalies': [], 'execution_time_ms': 288, 'query_time_ms': 71, 'run_id': 'ade18d69-39da-40da-92bf-5ff92beaf9ea'}]
224
- (Background on this error at: https://sqlalche.me/e/20/f405)
225
- INFO: 2025-12-10T22:00:00 - app.tasks.scheduler: Validation summary: 0/1 projects succeeded
226
- WARNING: 2025-12-10T22:00:00 - app.tasks.scheduler: Failed projects: ['Atomio Fttx']
227
- INFO: 2025-12-10T22:00:00 - app.tasks.scheduler: Scheduled validation completed for 2025-12-10
228
- INFO: 2025-12-10T22:00:00 - apscheduler.executors.default: Job "Daily Field Agent Reconciliation (trigger: cron[hour='22', minute='0'], next run at: 2025-12-11 22:00:00 UTC)" executed successfully
229
- INFO: 2025-12-10T22:00:00 - app.tasks.scheduler: Job daily_validation completed successfully
230
- INFO: 10.16.13.79:30150 - "GET / HTTP/1.1" 200 OK
231
- INFO: 10.16.13.79:44122 - "GET / HTTP/1.1" 200 OK
232
- INFO: 10.16.13.79:55137 - "GET /health HTTP/1.1" 200 OK
233
- INFO: 2025-12-11T06:07:25 - app.core.supabase_auth: Session refreshed successfully
234
- INFO: 2025-12-11T06:07:29 - app.api.v1.auth: βœ… Token refreshed successfully for: nadina73@nembors.com
235
- INFO: 10.16.37.13:60071 - "POST /api/v1/auth/refresh-token HTTP/1.1" 200 OK
236
- INFO: 2025-12-11T06:07:30 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
237
- INFO: 2025-12-11T06:07:30 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
238
- INFO: 10.16.13.79:32694 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
239
- INFO: 2025-12-11T06:07:30 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
240
- INFO: 2025-12-11T06:07:30 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
241
- INFO: 10.16.37.13:60071 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
242
- INFO: 2025-12-11T06:07:30 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
243
- INFO: 2025-12-11T06:07:30 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
244
- INFO: 10.16.13.79:32694 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
245
- INFO: 2025-12-11T06:07:31 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
246
- INFO: 2025-12-11T06:07:31 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
247
- INFO: 10.16.37.13:60071 - "GET /api/v1/analytics/user/overview HTTP/1.1" 200 OK
248
- INFO: 2025-12-11T06:07:32 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
249
- INFO: 2025-12-11T06:07:32 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
250
- INFO: 2025-12-11T06:07:32 - app.services.project_service: Listed 1 projects (total: 1) for user c5cf92be-4172-4fe2-af5c-f05d83b3a938
251
- INFO: 10.16.13.79:32694 - "GET /api/v1/projects?page=1&per_page=100 HTTP/1.1" 200 OK
252
- INFO: 10.16.13.79:50368 - "GET /health HTTP/1.1" 200 OK
253
- INFO: 10.16.13.79:63963 - "GET /health HTTP/1.1" 200 OK
254
- INFO: 10.16.13.79:14550 - "GET /health HTTP/1.1" 200 OK
255
- INFO: 10.16.37.13:47705 - "GET /health HTTP/1.1" 200 OK
256
- INFO: 2025-12-11T06:19:31 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
257
- INFO: 2025-12-11T06:19:31 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
258
- INFO: 10.16.13.79:29766 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
259
- INFO: 2025-12-11T06:19:32 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
260
- INFO: 2025-12-11T06:19:32 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
261
- INFO: 10.16.13.79:29766 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
262
- INFO: 2025-12-11T06:19:32 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
263
- INFO: 2025-12-11T06:19:32 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
264
- INFO: 10.16.37.13:47705 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
265
- INFO: 2025-12-11T06:19:32 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
266
- INFO: 2025-12-11T06:19:32 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
267
- INFO: 10.16.37.13:47705 - "GET /api/v1/analytics/user/overview HTTP/1.1" 200 OK
268
- INFO: 2025-12-11T06:19:33 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
269
- INFO: 2025-12-11T06:19:33 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
270
- INFO: 2025-12-11T06:19:33 - app.services.project_service: Listed 1 projects (total: 1) for user c5cf92be-4172-4fe2-af5c-f05d83b3a938
271
- INFO: 10.16.37.13:47705 - "GET /api/v1/projects?page=1&per_page=100 HTTP/1.1" 200 OK
272
- INFO: 10.16.37.13:10471 - "GET /health HTTP/1.1" 200 OK
273
- INFO: 2025-12-11T06:19:46 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
274
- INFO: 2025-12-11T06:19:46 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
275
- INFO: 2025-12-11T06:19:46 - app.services.audit_service: Audit log created: update on user_preferences by nadina73@nembors.com
276
- INFO: 2025-12-11T06:19:46 - app.api.v1.auth: Preferences updated for user: nadina73@nembors.com
277
- INFO: 10.16.13.79:25924 - "PUT /api/v1/auth/me/preferences HTTP/1.1" 200 OK
278
- INFO: 2025-12-11T06:19:47 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
279
- INFO: 2025-12-11T06:19:47 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
280
- INFO: 10.16.37.13:10471 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
281
- INFO: 2025-12-11T06:19:47 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
282
- INFO: 2025-12-11T06:19:47 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
283
- INFO: 2025-12-11T06:19:47 - app.services.dashboard_service: Dashboard cache MISS for project 0ade6bd1-e492-4e25-b681-59f42058d29a, user c5cf92be-4172-4fe2-af5c-f05d83b3a938 - building fresh data
284
- INFO: 2025-12-11T06:19:47 - app.services.dashboard_service: Built and cached dashboard for project 0ade6bd1-e492-4e25-b681-59f42058d29a
285
- INFO: 10.16.13.79:25924 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/dashboard HTTP/1.1" 200 OK
286
- INFO: 10.16.13.79:25924 - "GET /api/v1/notifications?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a&page_size=50&is_read=false HTTP/1.1" 200 OK
287
- INFO: 10.16.37.13:10471 - "GET /api/v1/tickets/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 200 OK
288
- INFO: 10.16.13.79:39294 - "GET /api/v1/contractor-invoices?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 200 OK
289
- INFO: 10.16.37.13:56789 - "GET /api/v1/contractor-invoices/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 200 OK
290
- INFO: 10.16.13.79:54124 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
291
- INFO: 10.16.13.79:54124 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
292
- INFO: 10.16.37.13:5569 - "GET /health HTTP/1.1" 200 OK
293
- INFO: 10.16.37.13:15098 - "GET /health HTTP/1.1" 200 OK
294
- INFO: 10.16.13.79:36865 - "GET /health HTTP/1.1" 200 OK
295
- INFO: 10.16.13.79:32070 - "GET /health HTTP/1.1" 200 OK
296
- INFO: 10.16.37.13:16706 - "GET /health HTTP/1.1" 200 OK
297
- INFO: 2025-12-11T06:23:08 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
298
- INFO: 2025-12-11T06:23:08 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
299
- INFO: 10.16.13.79:5103 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
300
- INFO: 2025-12-11T06:23:09 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
301
- INFO: 2025-12-11T06:23:09 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
302
- INFO: 10.16.13.79:5103 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
303
- INFO: 2025-12-11T06:23:09 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
304
- INFO: 2025-12-11T06:23:09 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
305
- INFO: 10.16.37.13:16706 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
306
- INFO: 10.16.37.13:18227 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
307
- INFO: 10.16.13.79:5103 - "GET /health HTTP/1.1" 200 OK
308
- INFO: 2025-12-11T06:23:16 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
309
- INFO: 2025-12-11T06:23:16 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
310
- INFO: 10.16.37.13:17230 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
311
- INFO: 10.16.13.79:33301 - "GET /health HTTP/1.1" 200 OK
312
- INFO: 2025-12-11T06:23:16 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
313
- INFO: 2025-12-11T06:23:16 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
314
- INFO: 10.16.37.13:17230 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
315
- INFO: 2025-12-11T06:23:16 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
316
- INFO: 2025-12-11T06:23:16 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
317
- INFO: 10.16.13.79:33301 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
318
- INFO: 10.16.37.13:17230 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
319
- INFO: 10.16.13.79:33301 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
320
- INFO: 10.16.37.13:30569 - "GET /health HTTP/1.1" 200 OK
321
- INFO: 2025-12-11T06:25:50 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
322
- INFO: 2025-12-11T06:25:50 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
323
- INFO: 10.16.13.79:29711 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
324
- INFO: 10.16.37.13:39487 - "GET /health HTTP/1.1" 200 OK
325
- INFO: 2025-12-11T06:25:51 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
326
- INFO: 2025-12-11T06:25:51 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
327
- INFO: 10.16.37.13:39487 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
328
- INFO: 2025-12-11T06:25:51 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
329
- INFO: 2025-12-11T06:25:51 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
330
- INFO: 10.16.13.79:29711 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
331
- INFO: 10.16.37.13:53350 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
332
- INFO: 2025-12-11T06:25:59 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
333
- INFO: 2025-12-11T06:25:59 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
334
- INFO: 10.16.37.13:52474 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
335
- INFO: 10.16.13.79:62628 - "GET /health HTTP/1.1" 200 OK
336
- INFO: 2025-12-11T06:25:59 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
337
- INFO: 2025-12-11T06:25:59 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
338
- INFO: 10.16.37.13:52474 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
339
- INFO: 2025-12-11T06:25:59 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
340
- INFO: 2025-12-11T06:25:59 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
341
- INFO: 10.16.13.79:62628 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
342
- INFO: 10.16.37.13:23782 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
343
- INFO: 10.16.37.13:16935 - "GET /health HTTP/1.1" 200 OK
344
- INFO: 2025-12-11T06:26:23 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
345
- INFO: 2025-12-11T06:26:23 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
346
- INFO: 10.16.13.79:7127 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
347
- INFO: 2025-12-11T06:26:23 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
348
- INFO: 2025-12-11T06:26:23 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
349
- INFO: 10.16.13.79:7127 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
350
- INFO: 2025-12-11T06:26:23 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
351
- INFO: 2025-12-11T06:26:23 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
352
- INFO: 10.16.37.13:16935 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
353
- INFO: 10.16.13.79:63911 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
354
- INFO: 10.16.37.13:62825 - "GET /health HTTP/1.1" 200 OK
355
- INFO: 10.16.37.13:62825 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
356
- INFO: 10.16.13.79:59155 - "GET /health HTTP/1.1" 200 OK
357
- INFO: 10.16.37.13:27183 - "GET /health HTTP/1.1" 200 OK
358
- INFO: 2025-12-11T06:27:46 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
359
- INFO: 2025-12-11T06:27:46 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
360
- INFO: 10.16.13.79:51476 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
361
- INFO: 2025-12-11T06:27:46 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
362
- INFO: 2025-12-11T06:27:46 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
363
- INFO: 10.16.13.79:51476 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
364
- INFO: 2025-12-11T06:27:46 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
365
- INFO: 2025-12-11T06:27:46 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
366
- INFO: 10.16.37.13:27183 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
367
- INFO: 10.16.37.13:64873 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
368
- INFO: 10.16.13.79:33275 - "GET /health HTTP/1.1" 200 OK
369
- INFO: 10.16.13.79:33275 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
370
- INFO: 10.16.37.13:23023 - "GET /health HTTP/1.1" 200 OK
371
- INFO: 2025-12-11T06:28:31 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
372
- INFO: 2025-12-11T06:28:31 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
373
- INFO: 10.16.13.79:31206 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
374
- INFO: 2025-12-11T06:28:31 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
375
- INFO: 2025-12-11T06:28:31 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
376
- INFO: 10.16.13.79:31206 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
377
- INFO: 2025-12-11T06:28:31 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
378
- INFO: 2025-12-11T06:28:31 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
379
- INFO: 10.16.37.13:23023 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
380
- INFO: 10.16.13.79:49321 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
381
- INFO: 10.16.37.13:23023 - "GET /health HTTP/1.1" 200 OK
382
- INFO: 10.16.37.13:22536 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
383
- INFO: 10.16.37.13:22536 - "GET /health HTTP/1.1" 200 OK
384
- INFO: 2025-12-11T06:28:37 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
385
- INFO: 2025-12-11T06:28:37 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
386
- INFO: 10.16.13.79:20306 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
387
- INFO: 2025-12-11T06:28:37 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
388
- INFO: 2025-12-11T06:28:37 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
389
- INFO: 10.16.13.79:20306 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
390
- INFO: 2025-12-11T06:28:38 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
391
- INFO: 2025-12-11T06:28:38 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
392
- INFO: 10.16.37.13:22536 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
393
- INFO: 10.16.13.79:35880 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
394
- INFO: 10.16.13.79:35880 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
395
- INFO: 10.16.37.13:4657 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/regions/4cd27765-5720-4cc0-872e-bf0da3cd1898 HTTP/1.1" 405 Method Not Allowed
396
- INFO: 10.16.13.79:11727 - "GET /health HTTP/1.1" 200 OK
397
- INFO: 10.16.37.13:10176 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/regions/4cd27765-5720-4cc0-872e-bf0da3cd1898 HTTP/1.1" 405 Method Not Allowed
398
- INFO: 10.16.37.13:19693 - "GET /health HTTP/1.1" 200 OK
 
1
+ ===== Application Startup at 2025-12-12 11:59:51 =====
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  Traceback (most recent call last):
4
+ File "/usr/local/bin/uvicorn", line 8, in <module>
5
+ sys.exit(main())
6
+ ^^^^^^
7
+ File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1485, in __call__
8
+ return self.main(*args, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ^^^^^^^^^^^^^^^^^^^^^^^^^^
10
+ File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1406, in main
11
+ rv = self.invoke(ctx)
12
+ ^^^^^^^^^^^^^^^^
13
+ File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1269, in invoke
14
+ return ctx.invoke(self.callback, **ctx.params)
15
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16
+ File "/usr/local/lib/python3.11/site-packages/click/core.py", line 824, in invoke
17
+ return callback(*args, **kwargs)
18
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
19
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/main.py", line 416, in main
20
+ run(
21
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/main.py", line 587, in run
22
+ server.run()
23
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 61, in run
24
+ return asyncio.run(self.serve(sockets=sockets))
25
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26
+ File "/usr/local/lib/python3.11/asyncio/runners.py", line 190, in run
27
+ return runner.run(main)
28
+ ^^^^^^^^^^^^^^^^
29
+ File "/usr/local/lib/python3.11/asyncio/runners.py", line 118, in run
30
+ return self._loop.run_until_complete(task)
31
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32
+ File "uvloop/loop.pyx", line 1518, in uvloop.loop.Loop.run_until_complete
33
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 68, in serve
34
+ config.load()
35
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/config.py", line 467, in load
36
+ self.loaded_app = import_from_string(self.app)
37
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/importer.py", line 21, in import_from_string
39
+ module = importlib.import_module(module_str)
40
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41
+ File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
42
+ return _bootstrap._gcd_import(name[level:], package, level)
43
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
+ File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
45
+ File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
46
+ File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
47
+ File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
48
+ File "<frozen importlib._bootstrap_external>", line 940, in exec_module
49
+ File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
50
+ File "/app/src/app/main.py", line 151, in <module>
51
+ from app.api.v1.router import api_router
52
+ File "/app/src/app/api/v1/router.py", line 5, in <module>
53
+ from app.api.v1 import (
54
+ File "/app/src/app/api/v1/payroll.py", line 599
55
+ await AuditService.log_action(
56
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
57
+ SyntaxError: 'await' outside async function
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/v1/customers.py CHANGED
@@ -97,18 +97,19 @@ async def create_customer(
97
  customer = CustomerService.create_customer(db, data, current_user)
98
 
99
  # Log audit trail
100
- await AuditService.log_action(
101
  db=db,
102
- user_id=current_user.id,
103
  action="create_customer",
104
- resource_type="customer",
105
- resource_id=customer.id,
106
- details={
 
 
 
107
  "customer_name": customer.customer_name,
108
  "phone": customer.phone_primary,
109
  "client_id": str(customer.client_id)
110
- },
111
- request=request
112
  )
113
 
114
  # Build response with nested data
@@ -319,17 +320,18 @@ async def update_customer(
319
  customer = CustomerService.update_customer(db, customer_id, data, current_user)
320
 
321
  # Log audit trail
322
- await AuditService.log_action(
323
  db=db,
324
- user_id=current_user.id,
325
  action="update_customer",
326
- resource_type="customer",
327
- resource_id=customer.id,
328
- details={
329
- "old_state": old_state,
330
- "changes": data.model_dump(exclude_unset=True)
331
- },
332
- request=request
 
 
333
  )
334
 
335
  # Build response with nested data
@@ -381,17 +383,18 @@ async def delete_customer(
381
  CustomerService.delete_customer(db, customer_id, current_user)
382
 
383
  # Log audit trail
384
- await AuditService.log_action(
385
  db=db,
386
- user_id=current_user.id,
387
  action="delete_customer",
388
- resource_type="customer",
389
- resource_id=customer_id,
390
- details={
 
 
 
391
  "customer_name": customer.customer_name,
392
  "phone": customer.phone_primary
393
- },
394
- request=request
395
  )
396
 
397
  except HTTPException:
 
97
  customer = CustomerService.create_customer(db, data, current_user)
98
 
99
  # Log audit trail
100
+ AuditService.log_action(
101
  db=db,
 
102
  action="create_customer",
103
+ entity_type="customer",
104
+ description=f"Created customer: {customer.customer_name}",
105
+ user=current_user,
106
+ entity_id=str(customer.id),
107
+ request=request,
108
+ additional_metadata={
109
  "customer_name": customer.customer_name,
110
  "phone": customer.phone_primary,
111
  "client_id": str(customer.client_id)
112
+ }
 
113
  )
114
 
115
  # Build response with nested data
 
320
  customer = CustomerService.update_customer(db, customer_id, data, current_user)
321
 
322
  # Log audit trail
323
+ AuditService.log_action(
324
  db=db,
 
325
  action="update_customer",
326
+ entity_type="customer",
327
+ description=f"Updated customer {customer.id}",
328
+ user=current_user,
329
+ entity_id=str(customer.id),
330
+ request=request,
331
+ changes={
332
+ "old": old_state,
333
+ "new": data.model_dump(exclude_unset=True)
334
+ }
335
  )
336
 
337
  # Build response with nested data
 
383
  CustomerService.delete_customer(db, customer_id, current_user)
384
 
385
  # Log audit trail
386
+ AuditService.log_action(
387
  db=db,
 
388
  action="delete_customer",
389
+ entity_type="customer",
390
+ description=f"Deleted customer: {customer.customer_name}",
391
+ user=current_user,
392
+ entity_id=str(customer_id),
393
+ request=request,
394
+ additional_metadata={
395
  "customer_name": customer.customer_name,
396
  "phone": customer.phone_primary
397
+ }
 
398
  )
399
 
400
  except HTTPException:
src/app/api/v1/payroll.py CHANGED
@@ -93,20 +93,21 @@ async def generate_payroll(
93
 
94
  if result.payroll:
95
  # Log audit trail
96
- await AuditService.log_action(
97
  db=db,
98
- user_id=current_user.id,
99
  action="generate_payroll",
100
- resource_type="payroll",
101
- resource_id=result.payroll.id,
102
- details={
 
 
 
103
  "user_id": str(data.user_id),
104
  "project_id": str(data.project_id),
105
  "period": f"{data.period_start_date} to {data.period_end_date}",
106
  "success": result.success,
107
  "skipped_reason": result.skipped_reason
108
- },
109
- ip_address=request.client.host if request.client else None
110
  )
111
 
112
  return result
@@ -169,13 +170,15 @@ async def generate_payroll_batch(
169
  )
170
 
171
  # Log audit trail
172
- await AuditService.log_action(
173
  db=db,
174
- user_id=current_user.id,
175
  action="generate_batch_payroll",
176
- resource_type="payroll",
177
- resource_id=None,
178
- details={
 
 
 
179
  "target_date": str(data.target_date),
180
  "project_id": str(data.project_id) if data.project_id else None,
181
  "total_processed": result.total_processed,
@@ -375,17 +378,18 @@ async def recalculate_payroll(
375
  result = PayrollService.recalculate_payroll(db, payroll_id, current_user)
376
 
377
  # Log audit trail
378
- await AuditService.log_action(
379
  db=db,
380
- user_id=current_user.id,
381
  action="recalculate_payroll",
382
- resource_type="payroll",
383
- resource_id=payroll_id,
384
- details={
 
 
 
385
  "payroll_id": str(payroll_id),
386
  "success": result.success
387
- },
388
- ip_address=request.client.host if request.client else None
389
  )
390
 
391
  return result
@@ -447,19 +451,20 @@ async def mark_payroll_as_paid(
447
  )
448
 
449
  # Log audit trail
450
- await AuditService.log_action(
451
  db=db,
452
- user_id=current_user.id,
453
  action="mark_payroll_paid",
454
- resource_type="payroll",
455
- resource_id=payroll_id,
456
- details={
 
 
 
457
  "payroll_id": str(payroll_id),
458
  "payment_method": data.payment_method,
459
  "payment_reference": data.payment_reference,
460
  "total_amount": str(payroll.total_amount)
461
- },
462
- ip_address=request.client.host if request.client else None
463
  )
464
 
465
  return payroll
@@ -480,7 +485,7 @@ async def mark_payroll_as_paid(
480
 
481
  @router.post("/export", status_code=status.HTTP_200_OK)
482
  @require_permission("manage_payroll")
483
- def export_payroll_for_payment(
484
  background_tasks: BackgroundTasks,
485
  from_date: date = Query(..., description="Period start date (inclusive)"),
486
  to_date: date = Query(..., description="Period end date (inclusive)"),
@@ -595,22 +600,23 @@ def export_payroll_for_payment(
595
  output.seek(0)
596
  filename = f"tende_pay_payroll_{from_date.isoformat()}_{to_date.isoformat()}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.csv"
597
 
598
- # Log audit trail (keep async for now - separate concern)
599
- await AuditService.log_action(
600
  db=db,
601
- user_id=current_user.id,
602
  action="export_payroll_for_payment",
603
- resource_type="payroll",
604
- resource_id=None,
605
- details={
 
 
 
606
  "from_date": from_date.isoformat(),
607
  "to_date": to_date.isoformat(),
608
  "project_id": str(project_id) if project_id else None,
609
  "user_id": str(user_id) if user_id else None,
610
  "exported_count": len(csv_rows),
611
  "warning_count": len(warnings)
612
- },
613
- ip_address=request.client.host if request and request.client else None
614
  )
615
 
616
  logger.info(
 
93
 
94
  if result.payroll:
95
  # Log audit trail
96
+ AuditService.log_action(
97
  db=db,
 
98
  action="generate_payroll",
99
+ entity_type="payroll",
100
+ description=f"Generated payroll for user {data.user_id} project {data.project_id} period {data.period_start_date} to {data.period_end_date}",
101
+ user=current_user,
102
+ entity_id=str(result.payroll.id),
103
+ request=request,
104
+ additional_metadata={
105
  "user_id": str(data.user_id),
106
  "project_id": str(data.project_id),
107
  "period": f"{data.period_start_date} to {data.period_end_date}",
108
  "success": result.success,
109
  "skipped_reason": result.skipped_reason
110
+ }
 
111
  )
112
 
113
  return result
 
170
  )
171
 
172
  # Log audit trail
173
+ AuditService.log_action(
174
  db=db,
 
175
  action="generate_batch_payroll",
176
+ entity_type="payroll",
177
+ description=f"Generated batch payroll for target date {data.target_date} project {data.project_id if data.project_id else 'all'}",
178
+ user=current_user,
179
+ entity_id=None,
180
+ request=request,
181
+ additional_metadata={
182
  "target_date": str(data.target_date),
183
  "project_id": str(data.project_id) if data.project_id else None,
184
  "total_processed": result.total_processed,
 
378
  result = PayrollService.recalculate_payroll(db, payroll_id, current_user)
379
 
380
  # Log audit trail
381
+ AuditService.log_action(
382
  db=db,
 
383
  action="recalculate_payroll",
384
+ entity_type="payroll",
385
+ description=f"Recalculated payroll {payroll_id}",
386
+ user=current_user,
387
+ entity_id=str(payroll_id),
388
+ request=request,
389
+ additional_metadata={
390
  "payroll_id": str(payroll_id),
391
  "success": result.success
392
+ }
 
393
  )
394
 
395
  return result
 
451
  )
452
 
453
  # Log audit trail
454
+ AuditService.log_action(
455
  db=db,
 
456
  action="mark_payroll_paid",
457
+ entity_type="payroll",
458
+ description=f"Marked payroll {payroll_id} as paid via {data.payment_method}",
459
+ user=current_user,
460
+ entity_id=str(payroll_id),
461
+ request=request,
462
+ additional_metadata={
463
  "payroll_id": str(payroll_id),
464
  "payment_method": data.payment_method,
465
  "payment_reference": data.payment_reference,
466
  "total_amount": str(payroll.total_amount)
467
+ }
 
468
  )
469
 
470
  return payroll
 
485
 
486
  @router.post("/export", status_code=status.HTTP_200_OK)
487
  @require_permission("manage_payroll")
488
+ async def export_payroll_for_payment(
489
  background_tasks: BackgroundTasks,
490
  from_date: date = Query(..., description="Period start date (inclusive)"),
491
  to_date: date = Query(..., description="Period end date (inclusive)"),
 
600
  output.seek(0)
601
  filename = f"tende_pay_payroll_{from_date.isoformat()}_{to_date.isoformat()}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.csv"
602
 
603
+ # Log audit trail
604
+ AuditService.log_action(
605
  db=db,
 
606
  action="export_payroll_for_payment",
607
+ entity_type="payroll",
608
+ description=f"Exported {len(csv_rows)} payroll records for payment (period: {from_date} to {to_date})",
609
+ user=current_user,
610
+ entity_id=None,
611
+ request=request,
612
+ additional_metadata={
613
  "from_date": from_date.isoformat(),
614
  "to_date": to_date.isoformat(),
615
  "project_id": str(project_id) if project_id else None,
616
  "user_id": str(user_id) if user_id else None,
617
  "exported_count": len(csv_rows),
618
  "warning_count": len(warnings)
619
+ }
 
620
  )
621
 
622
  logger.info(
src/app/api/v1/tasks.py CHANGED
@@ -131,19 +131,20 @@ async def create_task(
131
  task = TaskService.create_task(db, data, current_user, background_tasks)
132
 
133
  # Log audit trail
134
- await AuditService.log_action(
135
  db=db,
136
- user_id=current_user.id,
137
  action="create_task",
138
- resource_type="task",
139
- resource_id=task.id,
140
- details={
 
 
 
141
  "task_title": task.task_title,
142
  "project_id": str(task.project_id),
143
  "task_type": task.task_type,
144
  "status": task.status.value
145
- },
146
- request=request
147
  )
148
 
149
  # Build response with nested data
@@ -495,17 +496,18 @@ async def update_task(
495
  task = TaskService.update_task(db, task_id, data, current_user)
496
 
497
  # Log audit trail
498
- await AuditService.log_action(
499
  db=db,
500
- user_id=current_user.id,
501
  action="update_task",
502
- resource_type="task",
503
- resource_id=task.id,
504
- details={
505
- "old_state": old_state,
506
- "changes": data.model_dump(exclude_unset=True)
507
- },
508
- request=request
 
 
509
  )
510
 
511
  # Build response with nested data
@@ -562,18 +564,18 @@ async def update_task_status(
562
  task = TaskService.update_task_status(db, task_id, data, current_user)
563
 
564
  # Log audit trail
565
- await AuditService.log_action(
566
  db=db,
567
- user_id=current_user.id,
568
  action="update_task_status",
569
- resource_type="task",
570
- resource_id=task.id,
571
- details={
572
- "old_status": old_status.value,
573
- "new_status": task.status.value,
574
- "reason": data.reason
575
- },
576
- request=request
 
577
  )
578
 
579
  # Build response
@@ -617,14 +619,15 @@ async def start_task(
617
  task = TaskService.start_task(db, task_id, data, current_user)
618
 
619
  # Log audit trail
620
- await AuditService.log_action(
621
  db=db,
622
- user_id=current_user.id,
623
  action="start_task",
624
- resource_type="task",
625
- resource_id=task.id,
626
- details={"started_at": str(task.started_at)},
627
- request=request
 
 
628
  )
629
 
630
  # Build response
@@ -670,17 +673,18 @@ async def complete_task(
670
  task = TaskService.complete_task(db, task_id, data, current_user, background_tasks)
671
 
672
  # Log audit trail
673
- await AuditService.log_action(
674
  db=db,
675
- user_id=current_user.id,
676
  action="complete_task",
677
- resource_type="task",
678
- resource_id=task.id,
679
- details={
 
 
 
680
  "completed_at": str(task.completed_at),
681
  "notes": data.completion_notes
682
- },
683
- request=request
684
  )
685
 
686
  # Build response
@@ -726,14 +730,15 @@ async def cancel_task(
726
  task = TaskService.cancel_task(db, task_id, data, current_user, background_tasks)
727
 
728
  # Log audit trail
729
- await AuditService.log_action(
730
  db=db,
731
- user_id=current_user.id,
732
  action="cancel_task",
733
- resource_type="task",
734
- resource_id=task.id,
735
- details={"reason": data.cancellation_reason},
736
- request=request
 
 
737
  )
738
 
739
  # Build response
@@ -786,17 +791,18 @@ async def delete_task(
786
  TaskService.delete_task(db, task_id, current_user)
787
 
788
  # Log audit trail
789
- await AuditService.log_action(
790
  db=db,
791
- user_id=current_user.id,
792
  action="delete_task",
793
- resource_type="task",
794
- resource_id=task_id,
795
- details={
 
 
 
796
  "task_title": task.task_title,
797
  "project_id": str(task.project_id)
798
- },
799
- request=request
800
  )
801
 
802
  except HTTPException:
 
131
  task = TaskService.create_task(db, data, current_user, background_tasks)
132
 
133
  # Log audit trail
134
+ AuditService.log_action(
135
  db=db,
 
136
  action="create_task",
137
+ entity_type="task",
138
+ description=f"Created task: {task.task_title}",
139
+ user=current_user,
140
+ entity_id=str(task.id),
141
+ request=request,
142
+ additional_metadata={
143
  "task_title": task.task_title,
144
  "project_id": str(task.project_id),
145
  "task_type": task.task_type,
146
  "status": task.status.value
147
+ }
 
148
  )
149
 
150
  # Build response with nested data
 
496
  task = TaskService.update_task(db, task_id, data, current_user)
497
 
498
  # Log audit trail
499
+ AuditService.log_action(
500
  db=db,
 
501
  action="update_task",
502
+ entity_type="task",
503
+ description=f"Updated task {task.id}",
504
+ user=current_user,
505
+ entity_id=str(task.id),
506
+ request=request,
507
+ changes={
508
+ "old": old_state,
509
+ "new": data.model_dump(exclude_unset=True)
510
+ }
511
  )
512
 
513
  # Build response with nested data
 
564
  task = TaskService.update_task_status(db, task_id, data, current_user)
565
 
566
  # Log audit trail
567
+ AuditService.log_action(
568
  db=db,
 
569
  action="update_task_status",
570
+ entity_type="task",
571
+ description=f"Updated task status from {old_status.value} to {task.status.value}",
572
+ user=current_user,
573
+ entity_id=str(task.id),
574
+ request=request,
575
+ changes={
576
+ "old": {"status": old_status.value},
577
+ "new": {"status": task.status.value, "reason": data.reason}
578
+ }
579
  )
580
 
581
  # Build response
 
619
  task = TaskService.start_task(db, task_id, data, current_user)
620
 
621
  # Log audit trail
622
+ AuditService.log_action(
623
  db=db,
 
624
  action="start_task",
625
+ entity_type="task",
626
+ description=f"Started task {task.id}",
627
+ user=current_user,
628
+ entity_id=str(task.id),
629
+ request=request,
630
+ additional_metadata={"started_at": str(task.started_at)}
631
  )
632
 
633
  # Build response
 
673
  task = TaskService.complete_task(db, task_id, data, current_user, background_tasks)
674
 
675
  # Log audit trail
676
+ AuditService.log_action(
677
  db=db,
 
678
  action="complete_task",
679
+ entity_type="task",
680
+ description=f"Completed task {task.id}",
681
+ user=current_user,
682
+ entity_id=str(task.id),
683
+ request=request,
684
+ additional_metadata={
685
  "completed_at": str(task.completed_at),
686
  "notes": data.completion_notes
687
+ }
 
688
  )
689
 
690
  # Build response
 
730
  task = TaskService.cancel_task(db, task_id, data, current_user, background_tasks)
731
 
732
  # Log audit trail
733
+ AuditService.log_action(
734
  db=db,
 
735
  action="cancel_task",
736
+ entity_type="task",
737
+ description=f"Cancelled task {task.id}",
738
+ user=current_user,
739
+ entity_id=str(task.id),
740
+ request=request,
741
+ additional_metadata={"reason": data.cancellation_reason}
742
  )
743
 
744
  # Build response
 
791
  TaskService.delete_task(db, task_id, current_user)
792
 
793
  # Log audit trail
794
+ AuditService.log_action(
795
  db=db,
 
796
  action="delete_task",
797
+ entity_type="task",
798
+ description=f"Deleted task: {task.task_title}",
799
+ user=current_user,
800
+ entity_id=str(task_id),
801
+ request=request,
802
+ additional_metadata={
803
  "task_title": task.task_title,
804
  "project_id": str(task.project_id)
805
+ }
 
806
  )
807
 
808
  except HTTPException:
src/app/api/v1/timesheets.py CHANGED
@@ -69,18 +69,19 @@ async def create_timesheet(
69
  timesheet = TimesheetService.create_timesheet(db, data, current_user)
70
 
71
  # Log audit trail
72
- await AuditService.log_action(
73
  db=db,
74
- user_id=current_user.id,
75
  action="create_timesheet",
76
- resource_type="timesheet",
77
- resource_id=timesheet.id,
78
- details={
 
 
 
79
  "user_id": str(timesheet.user_id),
80
  "work_date": str(timesheet.work_date),
81
  "status": timesheet.status.value
82
- },
83
- request=request
84
  )
85
 
86
  # Build response with nested data
@@ -186,18 +187,19 @@ async def apply_for_leave(
186
  timesheet = TimesheetService.create_timesheet(db, timesheet_data, current_user)
187
 
188
  # Log audit trail
189
- await AuditService.log_action(
190
  db=db,
191
- user_id=current_user.id,
192
  action="apply_for_leave",
193
- resource_type="timesheet",
194
- resource_id=timesheet.id,
195
- details={
 
 
 
196
  "work_date": str(data.work_date),
197
- "status": data.status, # Already a string
198
  "leave_reason": data.leave_reason
199
- },
200
- request=request
201
  )
202
 
203
  # Build response
@@ -253,19 +255,20 @@ async def bulk_create_timesheets(
253
  timesheets = TimesheetService.bulk_create_timesheets(db, data, current_user)
254
 
255
  # Log audit trail
256
- await AuditService.log_action(
257
  db=db,
258
- user_id=current_user.id,
259
  action="bulk_create_timesheets",
260
- resource_type="timesheet",
261
- resource_id=None,
262
- details={
 
 
 
263
  "project_id": str(data.project_id),
264
  "work_date": str(data.work_date),
265
  "user_count": len(timesheets),
266
  "status": data.status.value
267
- },
268
- request=request
269
  )
270
 
271
  # Build response array
@@ -750,17 +753,18 @@ async def update_timesheet(
750
  timesheet = TimesheetService.update_timesheet(db, timesheet_id, data, current_user)
751
 
752
  # Log audit trail
753
- await AuditService.log_action(
754
  db=db,
755
- user_id=current_user.id,
756
  action="update_timesheet",
757
- resource_type="timesheet",
758
- resource_id=timesheet.id,
759
- details={
760
- "old_state": old_state,
761
- "changes": data.model_dump(exclude_unset=True)
762
- },
763
- request=request
 
 
764
  )
765
 
766
  # Build response with nested data
@@ -809,18 +813,19 @@ async def approve_leave(
809
  timesheet = TimesheetService.approve_leave(db, timesheet_id, data, current_user)
810
 
811
  # Log audit trail
812
- await AuditService.log_action(
813
  db=db,
814
- user_id=current_user.id,
815
  action="approve_leave",
816
- resource_type="timesheet",
817
- resource_id=timesheet.id,
818
- details={
 
 
 
819
  "user_id": str(timesheet.user_id),
820
  "work_date": str(timesheet.work_date),
821
  "status": timesheet.status.value
822
- },
823
- request=request
824
  )
825
 
826
  # Build response with nested data
@@ -871,18 +876,19 @@ async def delete_timesheet(
871
  TimesheetService.delete_timesheet(db, timesheet_id, current_user)
872
 
873
  # Log audit trail
874
- await AuditService.log_action(
875
  db=db,
876
- user_id=current_user.id,
877
  action="delete_timesheet",
878
- resource_type="timesheet",
879
- resource_id=timesheet_id,
880
- details={
 
 
 
881
  "user_id": str(timesheet.user_id),
882
  "work_date": str(timesheet.work_date),
883
  "status": timesheet.status.value
884
- },
885
- request=request
886
  )
887
 
888
  except HTTPException:
@@ -967,14 +973,15 @@ async def generate_timesheets(
967
  'method': 'single_user'
968
  }
969
 
970
- await AuditService.log_action(
971
  db=db,
972
- user_id=current_user.id,
973
  action="generate_timesheet_single",
974
- resource_type="timesheet",
975
- resource_id=timesheet.id if timesheet else None,
976
- details=stats,
977
- request=request
 
 
978
  )
979
 
980
  return stats
@@ -988,13 +995,15 @@ async def generate_timesheets(
988
  regenerate=regenerate
989
  )
990
 
991
- await AuditService.log_action(
992
  db=db,
993
- user_id=current_user.id,
994
  action="generate_timesheets_bulk",
995
- resource_type="timesheet",
996
- resource_id=None,
997
- details=stats,
 
 
 
998
  request=request
999
  )
1000
 
 
69
  timesheet = TimesheetService.create_timesheet(db, data, current_user)
70
 
71
  # Log audit trail
72
+ AuditService.log_action(
73
  db=db,
 
74
  action="create_timesheet",
75
+ entity_type="timesheet",
76
+ description=f"Created timesheet for user {timesheet.user_id} on {timesheet.work_date}",
77
+ user=current_user,
78
+ entity_id=str(timesheet.id),
79
+ request=request,
80
+ additional_metadata={
81
  "user_id": str(timesheet.user_id),
82
  "work_date": str(timesheet.work_date),
83
  "status": timesheet.status.value
84
+ }
 
85
  )
86
 
87
  # Build response with nested data
 
187
  timesheet = TimesheetService.create_timesheet(db, timesheet_data, current_user)
188
 
189
  # Log audit trail
190
+ AuditService.log_action(
191
  db=db,
 
192
  action="apply_for_leave",
193
+ entity_type="timesheet",
194
+ description=f"Applied for leave on {data.work_date}: {data.status}",
195
+ user=current_user,
196
+ entity_id=str(timesheet.id),
197
+ request=request,
198
+ additional_metadata={
199
  "work_date": str(data.work_date),
200
+ "status": data.status,
201
  "leave_reason": data.leave_reason
202
+ }
 
203
  )
204
 
205
  # Build response
 
255
  timesheets = TimesheetService.bulk_create_timesheets(db, data, current_user)
256
 
257
  # Log audit trail
258
+ AuditService.log_action(
259
  db=db,
 
260
  action="bulk_create_timesheets",
261
+ entity_type="timesheet",
262
+ description=f"Bulk created {len(timesheets)} timesheets for project {data.project_id} on {data.work_date}",
263
+ user=current_user,
264
+ entity_id=None,
265
+ request=request,
266
+ additional_metadata={
267
  "project_id": str(data.project_id),
268
  "work_date": str(data.work_date),
269
  "user_count": len(timesheets),
270
  "status": data.status.value
271
+ }
 
272
  )
273
 
274
  # Build response array
 
753
  timesheet = TimesheetService.update_timesheet(db, timesheet_id, data, current_user)
754
 
755
  # Log audit trail
756
+ AuditService.log_action(
757
  db=db,
 
758
  action="update_timesheet",
759
+ entity_type="timesheet",
760
+ description=f"Updated timesheet {timesheet.id}",
761
+ user=current_user,
762
+ entity_id=str(timesheet.id),
763
+ request=request,
764
+ changes={
765
+ "old": old_state,
766
+ "new": data.model_dump(exclude_unset=True)
767
+ }
768
  )
769
 
770
  # Build response with nested data
 
813
  timesheet = TimesheetService.approve_leave(db, timesheet_id, data, current_user)
814
 
815
  # Log audit trail
816
+ AuditService.log_action(
817
  db=db,
 
818
  action="approve_leave",
819
+ entity_type="timesheet",
820
+ description=f"Approved leave for user {timesheet.user_id} on {timesheet.work_date}",
821
+ user=current_user,
822
+ entity_id=str(timesheet.id),
823
+ request=request,
824
+ additional_metadata={
825
  "user_id": str(timesheet.user_id),
826
  "work_date": str(timesheet.work_date),
827
  "status": timesheet.status.value
828
+ }
 
829
  )
830
 
831
  # Build response with nested data
 
876
  TimesheetService.delete_timesheet(db, timesheet_id, current_user)
877
 
878
  # Log audit trail
879
+ AuditService.log_action(
880
  db=db,
 
881
  action="delete_timesheet",
882
+ entity_type="timesheet",
883
+ description=f"Deleted timesheet for user {timesheet.user_id} on {timesheet.work_date}",
884
+ user=current_user,
885
+ entity_id=str(timesheet_id),
886
+ request=request,
887
+ additional_metadata={
888
  "user_id": str(timesheet.user_id),
889
  "work_date": str(timesheet.work_date),
890
  "status": timesheet.status.value
891
+ }
 
892
  )
893
 
894
  except HTTPException:
 
973
  'method': 'single_user'
974
  }
975
 
976
+ AuditService.log_action(
977
  db=db,
 
978
  action="generate_timesheet_single",
979
+ entity_type="timesheet",
980
+ description=f"Generated timesheet for user {user_id} on {target_date}",
981
+ user=current_user,
982
+ entity_id=str(timesheet.id) if timesheet else None,
983
+ request=request,
984
+ additional_metadata=stats
985
  )
986
 
987
  return stats
 
995
  regenerate=regenerate
996
  )
997
 
998
+ AuditService.log_action(
999
  db=db,
 
1000
  action="generate_timesheets_bulk",
1001
+ entity_type="timesheet",
1002
+ description=f"Generated timesheets in bulk for {target_date}",
1003
+ user=current_user,
1004
+ entity_id=None,
1005
+ request=request,
1006
+ additional_metadata=stats
1007
  request=request
1008
  )
1009