kamau1 commited on
Commit
a30132c
Β·
1 Parent(s): 8a46bf1

fix: correct remaining schema mismatches in expense and finance queries

Browse files

- Fix TicketExpense.approval_status is_approved (all occurrences in dashboard_service.py)
- Fix TicketExpense.amount total_cost (correct column name)
- Fix TransactionType enum value: expense outflow
- Fix ProjectFinance.payment_status status (correct column name)
- Fix expense submitted_by to use incurred_by_user relationship
- Remove non-existent AuditService.get_project_audit_logs call (return empty array for now)

docs/devlogs/server/runtimeerror.txt CHANGED
@@ -1,55 +1,144 @@
1
- ===== Application Startup at 2025-11-23 17:05:47 =====
2
 
3
  INFO: Started server process [7]
4
  INFO: Waiting for application startup.
5
- INFO: 2025-11-23T17:06:00 - app.main: ============================================================
6
- INFO: 2025-11-23T17:06:00 - app.main: πŸš€ SwiftOps API v1.0.0 | PRODUCTION
7
- INFO: 2025-11-23T17:06:00 - app.main: πŸ“Š Dashboard: Enabled
8
- INFO: 2025-11-23T17:06:00 - app.main: ============================================================
9
- INFO: 2025-11-23T17:06:00 - app.main: πŸ“¦ Database:
10
- INFO: 2025-11-23T17:06:01 - app.main: βœ“ Connected | 44 tables | 5 users
11
- INFO: 2025-11-23T17:06:01 - app.main: πŸ’Ύ Cache & Sessions:
12
- INFO: 2025-11-23T17:06:01 - app.services.otp_service: βœ… OTP Service initialized with Redis storage
13
- INFO: 2025-11-23T17:06:02 - app.main: βœ“ Redis: Connected
14
- INFO: 2025-11-23T17:06:02 - app.main: πŸ”Œ External Services:
15
- INFO: 2025-11-23T17:06:03 - app.main: βœ“ Cloudinary: Connected
16
- INFO: 2025-11-23T17:06:03 - app.main: βœ“ Resend: Configured
17
- INFO: 2025-11-23T17:06:03 - app.main: βœ“ WASender: Connected
18
- INFO: 2025-11-23T17:06:03 - app.main: βœ“ Supabase: Connected | 6 buckets
19
- INFO: 2025-11-23T17:06:03 - app.main: ============================================================
20
- INFO: 2025-11-23T17:06:03 - app.main: βœ… Startup complete | Ready to serve requests
21
- INFO: 2025-11-23T17:06:03 - app.main: ============================================================
22
  INFO: Application startup complete.
23
  INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
24
- INFO: 10.16.18.108:1832 - "GET /health HTTP/1.1" 200 OK
25
- INFO: 10.16.8.187:8284 - "GET /health HTTP/1.1" 200 OK
26
- INFO: 10.16.18.114:35720 - "GET /health HTTP/1.1" 200 OK
27
- INFO: 10.16.8.187:40971 - "GET / HTTP/1.1" 200 OK
28
- INFO: 10.16.18.108:34611 - "GET /health HTTP/1.1" 200 OK
29
- INFO: 10.16.44.15:60934 - "GET /health HTTP/1.1" 200 OK
30
- INFO: 10.16.8.187:38139 - "GET /health HTTP/1.1" 200 OK
31
- INFO: 10.16.8.187:38139 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
32
- INFO: 2025-11-23T17:10:54 - app.services.dashboard_service: Dashboard cache MISS for project 0ade6bd1-e492-4e25-b681-59f42058d29a, user c5cf92be-4172-4fe2-af5c-f05d83b3a938 - building fresh data
33
- ERROR: 2025-11-23T17:10:54 - app.services.dashboard_service: Error getting expense stats: type object 'TicketExpense' has no attribute 'approval_status'
34
- ERROR: 2025-11-23T17:10:54 - app.services.dashboard_service: Error getting team stats: 'ProjectRole' object has no attribute 'role'
35
- ERROR: 2025-11-23T17:10:54 - app.services.dashboard_service: Error getting finance stats: (psycopg2.errors.InvalidTextRepresentation) invalid input value for enum transactiontype: "income"
36
- LINE 3: ...29a'::UUID AND project_finance.transaction_type = 'income' A...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  ^
38
 
39
  [SQL: SELECT sum(project_finance.amount) AS sum_1
40
  FROM project_finance
41
  WHERE project_finance.project_id = %(project_id_1)s::UUID AND project_finance.transaction_type = %(transaction_type_1)s AND project_finance.deleted_at IS NULL]
42
- [parameters: {'project_id_1': '0ade6bd1-e492-4e25-b681-59f42058d29a', 'transaction_type_1': 'income'}]
43
  (Background on this error at: https://sqlalche.me/e/20/9h9h)
44
- ERROR: 2025-11-23T17:10:54 - app.services.dashboard_service: Error getting region stats: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block
45
 
46
  [SQL: SELECT project_regions.project_id AS project_regions_project_id, project_regions.region_name AS project_regions_region_name, project_regions.region_code AS project_regions_region_code, project_regions.description AS project_regions_description, project_regions.country AS project_regions_country, project_regions.region AS project_regions_region, project_regions.city AS project_regions_city, project_regions.address_line1 AS project_regions_address_line1, project_regions.address_line2 AS project_regions_address_line2, project_regions.maps_link AS project_regions_maps_link, project_regions.latitude AS project_regions_latitude, project_regions.longitude AS project_regions_longitude, project_regions.manager_id AS project_regions_manager_id, project_regions.hub_contact_persons AS project_regions_hub_contact_persons, project_regions.is_active AS project_regions_is_active, project_regions.notes AS project_regions_notes, project_regions.additional_metadata AS project_regions_additional_metadata, project_regions.id AS project_regions_id, project_regions.created_at AS project_regions_created_at, project_regions.updated_at AS project_regions_updated_at, project_regions.deleted_at AS project_regions_deleted_at
47
  FROM project_regions
48
  WHERE %(param_1)s::UUID = project_regions.project_id]
49
  [parameters: {'param_1': UUID('0ade6bd1-e492-4e25-b681-59f42058d29a')}]
50
  (Background on this error at: https://sqlalche.me/e/20/2j85)
51
- ERROR: 2025-11-23T17:10:54 - app.services.dashboard_service: Error getting notification stats: NotificationService.get_notification_stats() missing 1 required positional argument: 'user_id'
52
- ERROR: 2025-11-23T17:10:54 - app.services.dashboard_service: Error getting customer stats: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block
 
 
 
 
 
 
 
53
 
54
  [SQL: SELECT projects.client_id AS projects_client_id, projects.contractor_id AS projects_contractor_id, projects.title AS projects_title, projects.description AS projects_description, projects.project_type AS projects_project_type, projects.service_type AS projects_service_type, projects.primary_manager_id AS projects_primary_manager_id, projects.status AS projects_status, projects.planned_start_date AS projects_planned_start_date, projects.planned_end_date AS projects_planned_end_date, projects.actual_start_date AS projects_actual_start_date, projects.actual_end_date AS projects_actual_end_date, projects.is_closed AS projects_is_closed, projects.closed_at AS projects_closed_at, projects.closed_by_user_id AS projects_closed_by_user_id, projects.platform_billing_plan AS projects_platform_billing_plan, projects.is_billable AS projects_is_billable, projects.budget AS projects_budget, projects.inventory_requirements AS projects_inventory_requirements, projects.photo_requirements AS projects_photo_requirements, projects.activation_requirements AS projects_activation_requirements, projects.additional_metadata AS projects_additional_metadata, projects.id AS projects_id, projects.created_at AS projects_created_at, projects.updated_at AS projects_updated_at, projects.deleted_at AS projects_deleted_at, clients_1.name AS clients_1_name, clients_1.swiftops_code AS clients_1_swiftops_code, clients_1.description AS clients_1_description, clients_1.industry AS clients_1_industry, clients_1.main_email AS clients_1_main_email, clients_1.main_phone AS clients_1_main_phone, clients_1.website AS clients_1_website, clients_1.is_active AS clients_1_is_active, clients_1.default_sla_days AS clients_1_default_sla_days, clients_1.additional_metadata AS clients_1_additional_metadata, clients_1.id AS clients_1_id, clients_1.created_at AS clients_1_created_at, clients_1.updated_at AS clients_1_updated_at, clients_1.deleted_at AS clients_1_deleted_at, contractors_1.name AS contractors_1_name, contractors_1.swiftops_code AS contractors_1_swiftops_code, contractors_1.description AS contractors_1_description, contractors_1.website AS contractors_1_website, contractors_1.main_email AS contractors_1_main_email, contractors_1.main_phone AS contractors_1_main_phone, contractors_1.is_active AS contractors_1_is_active, contractors_1.competencies AS contractors_1_competencies, contractors_1.onboarding_status AS contractors_1_onboarding_status, contractors_1.onboarding_completed_at AS contractors_1_onboarding_completed_at, contractors_1.additional_metadata AS contractors_1_additional_metadata, contractors_1.id AS contractors_1_id, contractors_1.created_at AS contractors_1_created_at, contractors_1.updated_at AS contractors_1_updated_at, contractors_1.deleted_at AS contractors_1_deleted_at
55
  FROM projects LEFT OUTER JOIN clients AS clients_1 ON clients_1.id = projects.client_id LEFT OUTER JOIN contractors AS contractors_1 ON contractors_1.id = projects.contractor_id
@@ -57,16 +146,15 @@ WHERE projects.id = %(id_1)s::UUID
57
  LIMIT %(param_1)s]
58
  [parameters: {'id_1': '0ade6bd1-e492-4e25-b681-59f42058d29a', 'param_1': 1}]
59
  (Background on this error at: https://sqlalche.me/e/20/2j85)
60
- INFO: 2025-11-23T17:10:54 - app.services.dashboard_service: Built and cached dashboard for project 0ade6bd1-e492-4e25-b681-59f42058d29a
61
- INFO: 10.16.8.187:38139 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/dashboard HTTP/1.1" 200 OK
62
- ERROR: 2025-11-23T17:10:54 - app.api.v1.projects: Failed to get activity feed: type object 'TicketExpense' has no attribute 'approval_status'
63
  Traceback (most recent call last):
64
- File "/app/src/app/api/v1/projects.py", line 433, in get_project_activity_feed
65
- TicketExpense.approval_status == "pending",
66
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67
- AttributeError: type object 'TicketExpense' has no attribute 'approval_status'
68
- INFO: 10.16.3.82:40756 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/activity-feed?limit=20 HTTP/1.1" 500 Internal Server Error
69
- INFO: 10.16.3.82:40756 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
70
- INFO: 10.16.3.82:34466 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
71
- INFO: 10.16.18.108:30026 - "GET /api/v1/sales-orders/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 422 Unprocessable Entity
72
-
 
1
+ ===== Application Startup at 2025-11-23 17:18:06 =====
2
 
3
  INFO: Started server process [7]
4
  INFO: Waiting for application startup.
5
+ INFO: 2025-11-23T17:18:17 - app.main: ============================================================
6
+ INFO: 2025-11-23T17:18:17 - app.main: πŸš€ SwiftOps API v1.0.0 | PRODUCTION
7
+ INFO: 2025-11-23T17:18:17 - app.main: πŸ“Š Dashboard: Enabled
8
+ INFO: 2025-11-23T17:18:17 - app.main: ============================================================
9
+ INFO: 2025-11-23T17:18:17 - app.main: πŸ“¦ Database:
10
+ INFO: 2025-11-23T17:18:17 - app.main: βœ“ Connected | 44 tables | 5 users
11
+ INFO: 2025-11-23T17:18:17 - app.main: πŸ’Ύ Cache & Sessions:
12
+ INFO: 2025-11-23T17:18:18 - app.services.otp_service: βœ… OTP Service initialized with Redis storage
13
+ INFO: 2025-11-23T17:18:19 - app.main: βœ“ Redis: Connected
14
+ INFO: 2025-11-23T17:18:19 - app.main: πŸ”Œ External Services:
15
+ INFO: 2025-11-23T17:18:19 - app.main: βœ“ Cloudinary: Connected
16
+ INFO: 2025-11-23T17:18:19 - app.main: βœ“ Resend: Configured
17
+ INFO: 2025-11-23T17:18:19 - app.main: βœ“ WASender: Connected
18
+ INFO: 2025-11-23T17:18:19 - app.main: βœ“ Supabase: Connected | 6 buckets
19
+ INFO: 2025-11-23T17:18:19 - app.main: ============================================================
20
+ INFO: 2025-11-23T17:18:19 - app.main: βœ… Startup complete | Ready to serve requests
21
+ INFO: 2025-11-23T17:18:19 - app.main: ============================================================
22
  INFO: Application startup complete.
23
  INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
24
+ INFO: 10.16.44.15:57240 - "GET /health HTTP/1.1" 200 OK
25
+ INFO: 10.16.44.194:65259 - "GET /health HTTP/1.1" 200 OK
26
+ INFO: 10.16.18.114:9929 - "GET /health HTTP/1.1" 200 OK
27
+ INFO: 10.16.8.187:4558 - "GET /health HTTP/1.1" 200 OK
28
+ INFO: 10.16.44.194:47348 - "GET /health HTTP/1.1" 200 OK
29
+ INFO: 10.16.8.187:45851 - "GET /health HTTP/1.1" 200 OK
30
+ INFO: 10.16.8.187:19482 - "GET /health HTTP/1.1" 200 OK
31
+ INFO: 10.16.18.108:27606 - "GET /health HTTP/1.1" 200 OK
32
+ INFO: 10.16.44.15:46356 - "GET /health HTTP/1.1" 200 OK
33
+ INFO: 10.16.44.194:9992 - "GET /health HTTP/1.1" 200 OK
34
+ INFO: 10.16.44.15:15164 - "GET /health HTTP/1.1" 200 OK
35
+ INFO: 10.16.44.15:35302 - "GET /health HTTP/1.1" 200 OK
36
+ INFO: 10.16.8.187:43110 - "GET /health HTTP/1.1" 200 OK
37
+ INFO: 10.16.44.15:49352 - "GET /health HTTP/1.1" 200 OK
38
+ INFO: 10.16.8.187:35378 - "GET /health HTTP/1.1" 200 OK
39
+ INFO: 10.16.44.15:46932 - "GET /health HTTP/1.1" 200 OK
40
+ INFO: 10.16.18.108:21391 - "GET /health HTTP/1.1" 200 OK
41
+ INFO: 10.16.44.15:48557 - "GET /health HTTP/1.1" 200 OK
42
+ INFO: 10.16.8.187:41461 - "GET /health HTTP/1.1" 200 OK
43
+ INFO: 10.16.8.187:60338 - "GET /health HTTP/1.1" 200 OK
44
+ INFO: 10.16.18.108:62992 - "GET /health HTTP/1.1" 200 OK
45
+ INFO: 10.16.44.15:23733 - "GET /health HTTP/1.1" 200 OK
46
+ INFO: 10.16.8.187:12377 - "GET /health HTTP/1.1" 200 OK
47
+ INFO: 10.16.8.187:1732 - "GET /health HTTP/1.1" 200 OK
48
+ INFO: 10.16.18.114:7806 - "GET /health HTTP/1.1" 200 OK
49
+ INFO: 10.16.44.15:52164 - "GET /health HTTP/1.1" 200 OK
50
+ INFO: 10.16.44.15:30175 - "GET /health HTTP/1.1" 200 OK
51
+ INFO: 10.16.8.187:40284 - "GET /health HTTP/1.1" 200 OK
52
+ INFO: 10.16.3.82:9377 - "GET /health HTTP/1.1" 200 OK
53
+ INFO: 10.16.18.108:9174 - "GET /health HTTP/1.1" 200 OK
54
+ INFO: 10.16.18.108:56707 - "GET /openapi.json HTTP/1.1" 200 OK
55
+ INFO: 10.16.8.187:11644 - "GET /health HTTP/1.1" 200 OK
56
+ INFO: 10.16.18.108:54072 - "GET /docs/oauth2-redirect HTTP/1.1" 200 OK
57
+ INFO: 10.16.18.114:16277 - "GET /health HTTP/1.1" 200 OK
58
+ INFO: 10.16.8.187:46521 - "GET / HTTP/1.1" 200 OK
59
+ INFO: 10.16.18.108:39295 - "GET /health HTTP/1.1" 200 OK
60
+ INFO: 10.16.8.187:31543 - "GET /health HTTP/1.1" 200 OK
61
+ INFO: 10.16.8.187:43253 - "GET /health HTTP/1.1" 200 OK
62
+ INFO: 10.16.18.108:54374 - "GET /health HTTP/1.1" 200 OK
63
+ INFO: 10.16.8.187:45055 - "GET /health HTTP/1.1" 200 OK
64
+ INFO: 10.16.44.15:4560 - "GET /health HTTP/1.1" 200 OK
65
+ INFO: 10.16.18.114:61951 - "GET /health HTTP/1.1" 200 OK
66
+ INFO: 2025-11-23T17:56:11 - app.core.supabase_auth: Session refreshed successfully
67
+ INFO: 2025-11-23T17:56:12 - app.api.v1.auth: βœ… Token refreshed successfully for: nadina73@nembors.com
68
+ INFO: 10.16.44.15:53589 - "POST /api/v1/auth/refresh-token HTTP/1.1" 200 OK
69
+ INFO: 10.16.8.187:40607 - "GET /health HTTP/1.1" 200 OK
70
+ INFO: 10.16.18.108:24246 - "GET /health HTTP/1.1" 200 OK
71
+ INFO: 10.16.18.114:6757 - "GET /health HTTP/1.1" 200 OK
72
+ INFO: 10.16.44.15:31521 - "GET /health HTTP/1.1" 200 OK
73
+ INFO: 10.16.18.108:11136 - "GET /health HTTP/1.1" 200 OK
74
+ INFO: 10.16.18.114:20601 - "GET /health HTTP/1.1" 200 OK
75
+ INFO: 10.16.18.108:18117 - "GET /health HTTP/1.1" 200 OK
76
+ INFO: 10.16.44.15:31169 - "GET /health HTTP/1.1" 200 OK
77
+ INFO: 10.16.18.114:52413 - "GET /health HTTP/1.1" 200 OK
78
+ INFO: 10.16.3.82:51708 - "GET /health HTTP/1.1" 200 OK
79
+ INFO: 10.16.44.15:3667 - "GET /health HTTP/1.1" 200 OK
80
+ INFO: 10.16.8.187:17161 - "GET /health HTTP/1.1" 200 OK
81
+ INFO: 10.16.8.187:61963 - "GET /health HTTP/1.1" 200 OK
82
+ INFO: 10.16.8.187:34117 - "GET /health HTTP/1.1" 200 OK
83
+ INFO: 10.16.8.187:22265 - "GET /health HTTP/1.1" 200 OK
84
+ INFO: 10.16.3.82:13771 - "GET /health HTTP/1.1" 200 OK
85
+ INFO: 10.16.44.15:42328 - "GET /health HTTP/1.1" 200 OK
86
+ INFO: 10.16.44.15:56864 - "GET /health HTTP/1.1" 200 OK
87
+ INFO: 10.16.44.15:4202 - "GET /health HTTP/1.1" 200 OK
88
+ INFO: 10.16.44.15:43432 - "GET /health HTTP/1.1" 200 OK
89
+ INFO: 10.16.8.187:24825 - "GET /health HTTP/1.1" 200 OK
90
+ INFO: 10.16.18.108:48625 - "GET /health HTTP/1.1" 200 OK
91
+ INFO: 10.16.18.114:4508 - "GET /health HTTP/1.1" 200 OK
92
+ INFO: 10.16.18.114:51820 - "GET /health HTTP/1.1" 200 OK
93
+ INFO: 10.16.44.15:14659 - "GET /health HTTP/1.1" 200 OK
94
+ INFO: 10.16.18.114:58566 - "GET /health HTTP/1.1" 200 OK
95
+ INFO: 10.16.8.187:38317 - "GET /health HTTP/1.1" 200 OK
96
+ INFO: 10.16.8.187:24074 - "GET /health HTTP/1.1" 200 OK
97
+ INFO: 10.16.8.187:47359 - "GET /health HTTP/1.1" 200 OK
98
+ INFO: 10.16.18.114:5625 - "GET /health HTTP/1.1" 200 OK
99
+ INFO: 10.16.3.82:25228 - "GET /health HTTP/1.1" 200 OK
100
+ INFO: 10.16.18.114:52163 - "GET /health HTTP/1.1" 200 OK
101
+ INFO: 10.16.44.15:39028 - "GET /health HTTP/1.1" 200 OK
102
+ INFO: 10.16.3.82:14559 - "GET /health HTTP/1.1" 200 OK
103
+ INFO: 10.16.18.114:65083 - "GET /health HTTP/1.1" 200 OK
104
+ INFO: 10.16.44.15:33946 - "GET /health HTTP/1.1" 200 OK
105
+ INFO: 10.16.8.187:7000 - "GET /health HTTP/1.1" 200 OK
106
+ INFO: 10.16.44.15:19312 - "GET /health HTTP/1.1" 200 OK
107
+ INFO: 10.16.8.187:37786 - "GET /health HTTP/1.1" 200 OK
108
+ INFO: 10.16.18.114:53216 - "GET /health HTTP/1.1" 200 OK
109
+ INFO: 10.16.8.187:11931 - "GET /health HTTP/1.1" 200 OK
110
+ INFO: 10.16.8.187:61812 - "GET /health HTTP/1.1" 200 OK
111
+ INFO: 10.16.8.187:36766 - "GET /health HTTP/1.1" 200 OK
112
+ INFO: 10.16.18.114:28444 - "GET /health HTTP/1.1" 200 OK
113
+ INFO: 10.16.8.187:4985 - "GET /health HTTP/1.1" 200 OK
114
+ INFO: 10.16.18.108:42889 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
115
+ INFO: 2025-11-23T18:45:01 - app.services.dashboard_service: Dashboard cache MISS for project 0ade6bd1-e492-4e25-b681-59f42058d29a, user c5cf92be-4172-4fe2-af5c-f05d83b3a938 - building fresh data
116
+ ERROR: 2025-11-23T18:45:01 - app.services.dashboard_service: Error getting expense stats: type object 'TicketExpense' has no attribute 'approval_status'
117
+ ERROR: 2025-11-23T18:45:01 - app.services.dashboard_service: Error getting finance stats: (psycopg2.errors.InvalidTextRepresentation) invalid input value for enum transactiontype: "expense"
118
+ LINE 3: ...29a'::UUID AND project_finance.transaction_type = 'expense' ...
119
  ^
120
 
121
  [SQL: SELECT sum(project_finance.amount) AS sum_1
122
  FROM project_finance
123
  WHERE project_finance.project_id = %(project_id_1)s::UUID AND project_finance.transaction_type = %(transaction_type_1)s AND project_finance.deleted_at IS NULL]
124
+ [parameters: {'project_id_1': '0ade6bd1-e492-4e25-b681-59f42058d29a', 'transaction_type_1': 'expense'}]
125
  (Background on this error at: https://sqlalche.me/e/20/9h9h)
126
+ ERROR: 2025-11-23T18:45:01 - app.services.dashboard_service: Error getting region stats: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block
127
 
128
  [SQL: SELECT project_regions.project_id AS project_regions_project_id, project_regions.region_name AS project_regions_region_name, project_regions.region_code AS project_regions_region_code, project_regions.description AS project_regions_description, project_regions.country AS project_regions_country, project_regions.region AS project_regions_region, project_regions.city AS project_regions_city, project_regions.address_line1 AS project_regions_address_line1, project_regions.address_line2 AS project_regions_address_line2, project_regions.maps_link AS project_regions_maps_link, project_regions.latitude AS project_regions_latitude, project_regions.longitude AS project_regions_longitude, project_regions.manager_id AS project_regions_manager_id, project_regions.hub_contact_persons AS project_regions_hub_contact_persons, project_regions.is_active AS project_regions_is_active, project_regions.notes AS project_regions_notes, project_regions.additional_metadata AS project_regions_additional_metadata, project_regions.id AS project_regions_id, project_regions.created_at AS project_regions_created_at, project_regions.updated_at AS project_regions_updated_at, project_regions.deleted_at AS project_regions_deleted_at
129
  FROM project_regions
130
  WHERE %(param_1)s::UUID = project_regions.project_id]
131
  [parameters: {'param_1': UUID('0ade6bd1-e492-4e25-b681-59f42058d29a')}]
132
  (Background on this error at: https://sqlalche.me/e/20/2j85)
133
+ ERROR: 2025-11-23T18:45:01 - app.services.dashboard_service: Error getting notification stats: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block
134
+
135
+ [SQL: SELECT count(*) AS count_1
136
+ FROM (SELECT notifications.id AS notifications_id, notifications.user_id AS notifications_user_id, notifications.source_type AS notifications_source_type, notifications.source_id AS notifications_source_id, notifications.title AS notifications_title, notifications.message AS notifications_message, notifications.notification_type AS notifications_notification_type, notifications.channel AS notifications_channel, notifications.status AS notifications_status, notifications.sent_at AS notifications_sent_at, notifications.delivered_at AS notifications_delivered_at, notifications.read_at AS notifications_read_at, notifications.failed_at AS notifications_failed_at, notifications.failure_reason AS notifications_failure_reason, notifications.additional_metadata AS notifications_additional_metadata, notifications.created_at AS notifications_created_at, notifications.deleted_at AS notifications_deleted_at
137
+ FROM notifications
138
+ WHERE notifications.user_id = %(user_id_1)s::UUID) AS anon_1]
139
+ [parameters: {'user_id_1': UUID('c5cf92be-4172-4fe2-af5c-f05d83b3a938')}]
140
+ (Background on this error at: https://sqlalche.me/e/20/2j85)
141
+ ERROR: 2025-11-23T18:45:01 - app.services.dashboard_service: Error getting customer stats: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block
142
 
143
  [SQL: SELECT projects.client_id AS projects_client_id, projects.contractor_id AS projects_contractor_id, projects.title AS projects_title, projects.description AS projects_description, projects.project_type AS projects_project_type, projects.service_type AS projects_service_type, projects.primary_manager_id AS projects_primary_manager_id, projects.status AS projects_status, projects.planned_start_date AS projects_planned_start_date, projects.planned_end_date AS projects_planned_end_date, projects.actual_start_date AS projects_actual_start_date, projects.actual_end_date AS projects_actual_end_date, projects.is_closed AS projects_is_closed, projects.closed_at AS projects_closed_at, projects.closed_by_user_id AS projects_closed_by_user_id, projects.platform_billing_plan AS projects_platform_billing_plan, projects.is_billable AS projects_is_billable, projects.budget AS projects_budget, projects.inventory_requirements AS projects_inventory_requirements, projects.photo_requirements AS projects_photo_requirements, projects.activation_requirements AS projects_activation_requirements, projects.additional_metadata AS projects_additional_metadata, projects.id AS projects_id, projects.created_at AS projects_created_at, projects.updated_at AS projects_updated_at, projects.deleted_at AS projects_deleted_at, clients_1.name AS clients_1_name, clients_1.swiftops_code AS clients_1_swiftops_code, clients_1.description AS clients_1_description, clients_1.industry AS clients_1_industry, clients_1.main_email AS clients_1_main_email, clients_1.main_phone AS clients_1_main_phone, clients_1.website AS clients_1_website, clients_1.is_active AS clients_1_is_active, clients_1.default_sla_days AS clients_1_default_sla_days, clients_1.additional_metadata AS clients_1_additional_metadata, clients_1.id AS clients_1_id, clients_1.created_at AS clients_1_created_at, clients_1.updated_at AS clients_1_updated_at, clients_1.deleted_at AS clients_1_deleted_at, contractors_1.name AS contractors_1_name, contractors_1.swiftops_code AS contractors_1_swiftops_code, contractors_1.description AS contractors_1_description, contractors_1.website AS contractors_1_website, contractors_1.main_email AS contractors_1_main_email, contractors_1.main_phone AS contractors_1_main_phone, contractors_1.is_active AS contractors_1_is_active, contractors_1.competencies AS contractors_1_competencies, contractors_1.onboarding_status AS contractors_1_onboarding_status, contractors_1.onboarding_completed_at AS contractors_1_onboarding_completed_at, contractors_1.additional_metadata AS contractors_1_additional_metadata, contractors_1.id AS contractors_1_id, contractors_1.created_at AS contractors_1_created_at, contractors_1.updated_at AS contractors_1_updated_at, contractors_1.deleted_at AS contractors_1_deleted_at
144
  FROM projects LEFT OUTER JOIN clients AS clients_1 ON clients_1.id = projects.client_id LEFT OUTER JOIN contractors AS contractors_1 ON contractors_1.id = projects.contractor_id
 
146
  LIMIT %(param_1)s]
147
  [parameters: {'id_1': '0ade6bd1-e492-4e25-b681-59f42058d29a', 'param_1': 1}]
148
  (Background on this error at: https://sqlalche.me/e/20/2j85)
149
+ INFO: 2025-11-23T18:45:01 - app.services.dashboard_service: Built and cached dashboard for project 0ade6bd1-e492-4e25-b681-59f42058d29a
150
+ INFO: 10.16.8.187:4985 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/dashboard HTTP/1.1" 200 OK
151
+ ERROR: 2025-11-23T18:45:01 - app.api.v1.projects: Failed to get activity feed: type object 'AuditService' has no attribute 'get_project_audit_logs'
152
  Traceback (most recent call last):
153
+ File "/app/src/app/api/v1/projects.py", line 448, in get_project_activity_feed
154
+ recent_logs = AuditService.get_project_audit_logs(
155
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156
+ AttributeError: type object 'AuditService' has no attribute 'get_project_audit_logs'
157
+ INFO: 10.16.8.187:60299 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
158
+ INFO: 10.16.8.187:64179 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
159
+ INFO: 10.16.18.108:42889 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/activity-feed?limit=20 HTTP/1.1" 500 Internal Server Error
160
+ INFO: 10.16.8.187:64179 - "GET /api/v1/sales-orders/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 422 Unprocessable Entity
 
src/app/api/v1/projects.py CHANGED
@@ -438,28 +438,16 @@ async def get_project_activity_feed(
438
  pending_actions.append({
439
  "type": "expense_approval",
440
  "id": str(exp.id),
441
- "description": f"Expense: {exp.category} - ${exp.amount:.2f}",
442
- "submitted_by": exp.submitted_by_user.name if exp.submitted_by_user else "Unknown",
443
  "submitted_at": exp.created_at.isoformat() + "Z" if exp.created_at else None,
444
  "action_required": "approve_or_reject"
445
  })
446
 
447
  # Get recent activity from audit logs
448
- recent_logs = AuditService.get_project_audit_logs(
449
- db=db,
450
- project_id=project_id,
451
- limit=limit
452
- )
453
-
454
- recent_activity = [
455
- {
456
- "type": log.action,
457
- "description": log.description or f"{log.action} on {log.entity_type}",
458
- "user": log.user.name if log.user else "System",
459
- "timestamp": log.timestamp.isoformat() + "Z" if log.timestamp else None
460
- }
461
- for log in recent_logs
462
- ]
463
 
464
  return {
465
  "unread_notifications": {
 
438
  pending_actions.append({
439
  "type": "expense_approval",
440
  "id": str(exp.id),
441
+ "description": f"Expense: {exp.category} - ${exp.total_cost:.2f}",
442
+ "submitted_by": exp.incurred_by_user.name if exp.incurred_by_user else "Unknown",
443
  "submitted_at": exp.created_at.isoformat() + "Z" if exp.created_at else None,
444
  "action_required": "approve_or_reject"
445
  })
446
 
447
  # Get recent activity from audit logs
448
+ # Note: For now, return empty array as AuditService doesn't have get_project_audit_logs method
449
+ # This can be implemented later with proper query filtering by project-related entities
450
+ recent_activity = []
 
 
 
 
 
 
 
 
 
 
 
 
451
 
452
  return {
453
  "unread_notifications": {
src/app/services/dashboard_service.py CHANGED
@@ -298,7 +298,7 @@ class DashboardService:
298
  ).filter(
299
  Ticket.project_id == project_id,
300
  TicketExpense.deleted_at.is_(None),
301
- TicketExpense.approval_status == "pending"
302
  ).count()
303
 
304
  approved = db.query(TicketExpense).join(
@@ -306,19 +306,15 @@ class DashboardService:
306
  ).filter(
307
  Ticket.project_id == project_id,
308
  TicketExpense.deleted_at.is_(None),
309
- TicketExpense.approval_status == "approved"
310
  ).count()
311
 
312
- rejected = db.query(TicketExpense).join(
313
- Ticket, TicketExpense.ticket_id == Ticket.id
314
- ).filter(
315
- Ticket.project_id == project_id,
316
- TicketExpense.deleted_at.is_(None),
317
- TicketExpense.approval_status == "rejected"
318
- ).count()
319
 
320
  # Sum amounts
321
- total_amount_result = db.query(func.sum(TicketExpense.amount)).join(
322
  Ticket, TicketExpense.ticket_id == Ticket.id
323
  ).filter(
324
  Ticket.project_id == project_id,
@@ -326,7 +322,7 @@ class DashboardService:
326
  ).scalar()
327
  total_amount = float(total_amount_result) if total_amount_result else 0.0
328
 
329
- this_month_result = db.query(func.sum(TicketExpense.amount)).join(
330
  Ticket, TicketExpense.ticket_id == Ticket.id
331
  ).filter(
332
  Ticket.project_id == project_id,
@@ -392,18 +388,18 @@ class DashboardService:
392
  ).scalar()
393
  total_revenue = float(revenue_result) if revenue_result else 0.0
394
 
395
- # Expenses (expense transactions)
396
  expense_result = db.query(func.sum(ProjectFinance.amount)).filter(
397
  ProjectFinance.project_id == project_id,
398
- ProjectFinance.transaction_type == "expense",
399
  ProjectFinance.deleted_at.is_(None)
400
  ).scalar()
401
  total_expenses = float(expense_result) if expense_result else 0.0
402
 
403
- # Pending payments
404
  pending_result = db.query(func.sum(ProjectFinance.amount)).filter(
405
  ProjectFinance.project_id == project_id,
406
- ProjectFinance.payment_status == "pending",
407
  ProjectFinance.deleted_at.is_(None)
408
  ).scalar()
409
  pending_payments = float(pending_result) if pending_result else 0.0
 
298
  ).filter(
299
  Ticket.project_id == project_id,
300
  TicketExpense.deleted_at.is_(None),
301
+ TicketExpense.is_approved == False
302
  ).count()
303
 
304
  approved = db.query(TicketExpense).join(
 
306
  ).filter(
307
  Ticket.project_id == project_id,
308
  TicketExpense.deleted_at.is_(None),
309
+ TicketExpense.is_approved == True
310
  ).count()
311
 
312
+ # Note: TicketExpense doesn't have a rejected state, only is_approved boolean
313
+ # Rejected count would need a separate rejection_reason check
314
+ rejected = 0
 
 
 
 
315
 
316
  # Sum amounts
317
+ total_amount_result = db.query(func.sum(TicketExpense.total_cost)).join(
318
  Ticket, TicketExpense.ticket_id == Ticket.id
319
  ).filter(
320
  Ticket.project_id == project_id,
 
322
  ).scalar()
323
  total_amount = float(total_amount_result) if total_amount_result else 0.0
324
 
325
+ this_month_result = db.query(func.sum(TicketExpense.total_cost)).join(
326
  Ticket, TicketExpense.ticket_id == Ticket.id
327
  ).filter(
328
  Ticket.project_id == project_id,
 
388
  ).scalar()
389
  total_revenue = float(revenue_result) if revenue_result else 0.0
390
 
391
+ # Expenses (outflow transactions)
392
  expense_result = db.query(func.sum(ProjectFinance.amount)).filter(
393
  ProjectFinance.project_id == project_id,
394
+ ProjectFinance.transaction_type == "outflow",
395
  ProjectFinance.deleted_at.is_(None)
396
  ).scalar()
397
  total_expenses = float(expense_result) if expense_result else 0.0
398
 
399
+ # Pending payments (status field, not payment_status)
400
  pending_result = db.query(func.sum(ProjectFinance.amount)).filter(
401
  ProjectFinance.project_id == project_id,
402
+ ProjectFinance.status == "pending",
403
  ProjectFinance.deleted_at.is_(None)
404
  ).scalar()
405
  pending_payments = float(pending_result) if pending_result else 0.0