kamau1 commited on
Commit
cb9c038
·
1 Parent(s): 8d499f1

fix: correct ProjectRegion import path in projects API module

Browse files
docs/api/invoicing/PUBLIC_INVOICE_ENDPOINTS.md ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Public Invoice Viewing Endpoints
2
+
3
+ These endpoints are **public** (no authentication required). Access is controlled via viewing token.
4
+
5
+ ## 1. View Invoice (Public)
6
+
7
+ **Endpoint:** `GET /api/v1/invoices/view`
8
+
9
+ **Authentication:** None (token-based access)
10
+
11
+ **Query Parameters:**
12
+ - `token` (required): Viewing token from invoice link
13
+ - `ticket_id` (optional): Filter to show only specific ticket details
14
+
15
+ **Example Requests:**
16
+
17
+ ```bash
18
+ # View full invoice
19
+ GET /api/v1/invoices/view?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM
20
+
21
+ # View specific ticket in invoice
22
+ GET /api/v1/invoices/view?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM&ticket_id=2de41ce7-dff1-4151-9710-87958d18b5c4
23
+ ```
24
+
25
+ **Response (Full Invoice):**
26
+ ```json
27
+ {
28
+ "invoice": {
29
+ "id": "81c96213-485f-4170-92a0-23c08332923b",
30
+ "invoice_number": "INV-TEL-2025-00001",
31
+ "invoice_title": "Invoice for 1 tickets",
32
+ "contractor_id": "1af9fb24-e5bb-40ac-a748-0997580b4c32",
33
+ "contractor_name": "Telkom Kenya",
34
+ "client_id": "a2455244-d87e-4279-9fca-dc067f06b5c3",
35
+ "client_name": "Airtel Networks Kenya",
36
+ "project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a",
37
+ "project_name": "Airtel Fiber Rollout 2025",
38
+ "issue_date": "2025-12-10",
39
+ "due_date": "2026-01-09",
40
+ "subtotal": 0.0,
41
+ "tax_rate": 0.0,
42
+ "tax_amount": 0.0,
43
+ "total_amount": 0.0,
44
+ "currency": "KES",
45
+ "status": "draft",
46
+ "line_items": [
47
+ {
48
+ "id": "uqtB0XJIJAviIUWq-Ez7dA",
49
+ "type": "ticket",
50
+ "ticket_id": "2de41ce7-dff1-4151-9710-87958d18b5c4",
51
+ "sales_order_number": "ORD-2025-015",
52
+ "description": "Installation - Elizabeth Muthoni",
53
+ "ticket_details": {
54
+ "id": "2de41ce7-dff1-4151-9710-87958d18b5c4",
55
+ "ticket_name": "Elizabeth Muthoni",
56
+ "ticket_type": "installation",
57
+ "work_description": "Install Premium Fiber 100Mbps",
58
+ "completed_at": "2025-12-10T12:36:25.444896+00:00",
59
+ "scheduled_date": "2025-12-11",
60
+ "work_location": {
61
+ "latitude": -1.219888,
62
+ "longitude": 36.877013
63
+ },
64
+ "completion_data": {
65
+ "odu_serial": "wetert",
66
+ "ont_serial": "ewrwet",
67
+ "odu_imei_number": "sdfgreh",
68
+ "activated_number": "12345678"
69
+ },
70
+ "images": [
71
+ {
72
+ "id": "07674999-1fcd-46e9-86ad-8dae1846a218",
73
+ "image_type": "completion",
74
+ "description": "[Airtel network] Completion photo",
75
+ "url": "https://res.cloudinary.com/...",
76
+ "captured_at": "2025-12-10T12:36:11.763055+00:00"
77
+ }
78
+ ],
79
+ "images_count": 4,
80
+ "status_history": [
81
+ {
82
+ "old_status": null,
83
+ "new_status": "open",
84
+ "changed_at": "2025-12-10T10:00:00+00:00",
85
+ "changed_by": "System",
86
+ "change_reason": "Ticket created from sales order"
87
+ },
88
+ {
89
+ "old_status": "open",
90
+ "new_status": "assigned",
91
+ "changed_at": "2025-12-10T10:30:00+00:00",
92
+ "changed_by": "John Dispatcher",
93
+ "change_reason": "Assigned to field agent"
94
+ }
95
+ ],
96
+ "sales_order": {
97
+ "order_number": "ORD-2025-015",
98
+ "customer_preferred_package": "Premium Fiber 100Mbps",
99
+ "package_price": 5000.0,
100
+ "installation_address_line1": "123 Main Street",
101
+ "preferred_visit_date": "2025-12-11",
102
+ "agent_name": "Jane Sales"
103
+ },
104
+ "region": {
105
+ "id": "abc123...",
106
+ "region_name": "Nairobi East",
107
+ "region_code": "NRB-E"
108
+ }
109
+ }
110
+ }
111
+ ]
112
+ },
113
+ "token_expires_at": "2026-01-09T20:08:01.097861+00:00",
114
+ "times_viewed": 23
115
+ }
116
+ ```
117
+
118
+ **Response (Single Ticket):**
119
+ ```json
120
+ {
121
+ "ticket": {
122
+ "id": "2de41ce7-dff1-4151-9710-87958d18b5c4",
123
+ "ticket_name": "Elizabeth Muthoni",
124
+ "ticket_type": "installation",
125
+ "completion_data": { ... },
126
+ "images": [ ... ],
127
+ "status_history": [ ... ],
128
+ "sales_order": { ... },
129
+ "region": { ... }
130
+ },
131
+ "invoice_number": "INV-TEL-2025-00001",
132
+ "invoice_id": "81c96213-485f-4170-92a0-23c08332923b",
133
+ "sales_order_number": "ORD-2025-015",
134
+ "token_expires_at": "2026-01-09T20:08:01.097861+00:00"
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 2. Download Invoice CSV (Public)
141
+
142
+ **Endpoint:** `GET /api/v1/invoices/view/csv`
143
+
144
+ **Authentication:** None (token-based access)
145
+
146
+ **Query Parameters:**
147
+ - `token` (required): Viewing token from invoice link
148
+
149
+ **Example Request:**
150
+
151
+ ```bash
152
+ GET /api/v1/invoices/view/csv?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM
153
+ ```
154
+
155
+ **Response:**
156
+ - Content-Type: `text/csv`
157
+ - Content-Disposition: `attachment; filename=invoice_INV-TEL-2025-00001.csv`
158
+ - CSV file with invoice and ticket details
159
+
160
+ **CSV Format:**
161
+ ```csv
162
+ Invoice Number,Issue Date,Due Date,Total Amount,Currency,Status
163
+ INV-TEL-2025-00001,2025-12-10,2026-01-09,0.00,KES,draft
164
+
165
+ Ticket ID,Ticket Name,Type,Description,Completed At,Sales Order,Images
166
+ 2de41ce7-dff1-4151-9710-87958d18b5c4,Elizabeth Muthoni,installation,Install Premium Fiber,2025-12-10T12:36:25,ORD-2025-015,"completion:https://..., completion:https://..."
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Error Responses
172
+
173
+ **404 Not Found:**
174
+ ```json
175
+ {
176
+ "detail": "Invoice not found or invalid token"
177
+ }
178
+ ```
179
+
180
+ **403 Forbidden:**
181
+ ```json
182
+ {
183
+ "detail": "Viewing token has expired"
184
+ }
185
+ ```
186
+
187
+ **400 Bad Request:**
188
+ ```json
189
+ {
190
+ "detail": "Invalid ticket_id format"
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Notes for Frontend
197
+
198
+ 1. **No Authentication Required**: These endpoints don't need Authorization header
199
+ 2. **Token in URL**: Token is passed as query parameter, safe for sharing
200
+ 3. **Token Expiration**: Default 30 days, check `token_expires_at` in response
201
+ 4. **View Tracking**: Each view increments `times_viewed` counter
202
+ 5. **CSV Download**: Use the `/csv` endpoint for direct download link
203
+ 6. **Image URLs**: All image URLs are public Cloudinary links (no signing needed)
204
+ 7. **Organization Names**: Invoice now includes `contractor_name`, `client_name`, and `project_name`
205
+ 8. **Status History**: Shows complete timeline of ticket status changes
206
+ 9. **Sales Order Details**: Includes package, address, and sales agent info
207
+ 10. **Region Info**: Shows which region/hub handled the ticket
docs/devlogs/browser/response.json CHANGED
@@ -1,59 +1,93 @@
1
  {
2
- "ticket": {
3
- "id": "2de41ce7-dff1-4151-9710-87958d18b5c4",
4
- "ticket_name": "Elizabeth Muthoni",
5
- "ticket_type": "installation",
6
- "work_description": "Install Premium Fiber 100Mbps for Elizabeth Muthoni",
7
- "completed_at": "2025-12-10T12:36:25.444896+00:00",
8
- "scheduled_date": "2025-12-11",
9
- "work_location": {
10
- "latitude": -1.219888,
11
- "longitude": 36.877013
12
- },
13
- "completion_data": {
14
- "odu_serial": "wetert",
15
- "ont_serial": "ewrwet",
16
- "odu_imei_number": "sdfgreh",
17
- "activated_number": "12345678"
18
- },
19
- "images": [
 
 
 
20
  {
21
- "id": "07674999-1fcd-46e9-86ad-8dae1846a218",
22
- "image_type": "completion",
23
- "description": "[Airtel network] Completion photo for ticket",
24
- "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370171/ticket_2de41ce7_ticket_photo_airtel_network_airtel_network_20251210_123610_screenshot_2025-08-25_090418.webp",
25
- "file_name": "ticket_2de41ce7_ticket_photo_airtel_network_airtel_network_20251210_123610_screenshot_2025-08-25_090418.png",
26
- "captured_at": "2025-12-10T12:36:11.763055+00:00"
27
- },
28
- {
29
- "id": "cbbec871-af75-4364-ab34-55272764a415",
30
- "image_type": "completion",
31
- "description": "[Speedtest] Completion photo for ticket",
32
- "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370172/ticket_2de41ce7_ticket_photo_speedtest_speedtest_20251210_123611_screenshot_2025-08-19_155834.webp",
33
- "file_name": "ticket_2de41ce7_ticket_photo_speedtest_speedtest_20251210_123611_screenshot_2025-08-19_155834.png",
34
- "captured_at": "2025-12-10T12:36:13.395865+00:00"
35
- },
36
- {
37
- "id": "ecfc24fc-6be0-4888-a65e-7acf83088c2e",
38
- "image_type": "completion",
39
- "description": "[ODU outdoor image] Completion photo for ticket",
40
- "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370173/ticket_2de41ce7_ticket_photo_odu_outdoor_image_odu_outdoor_image_20251210_123613_screenshot_2025-09-01_163321.webp",
41
- "file_name": "ticket_2de41ce7_ticket_photo_odu_outdoor_image_odu_outdoor_image_20251210_123613_screenshot_2025-09-01_163321.png",
42
- "captured_at": "2025-12-10T12:36:14.128316+00:00"
43
- },
44
- {
45
- "id": "e37b665d-fd6d-4c77-b39e-a6064cb49d64",
46
- "image_type": "completion",
47
- "description": "[JCC] Completion photo for ticket",
48
- "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370174/ticket_2de41ce7_ticket_photo_jcc_jcc_20251210_123614_screenshot_2025-09-04_093459.webp",
49
- "file_name": "ticket_2de41ce7_ticket_photo_jcc_jcc_20251210_123614_screenshot_2025-09-04_093459.png",
50
- "captured_at": "2025-12-10T12:36:14.781724+00:00"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
- ],
53
- "images_count": 4
54
  },
55
- "invoice_number": "INV-TEL-2025-00001",
56
- "invoice_id": "81c96213-485f-4170-92a0-23c08332923b",
57
- "sales_order_number": "ORD-2025-015",
58
- "token_expires_at": "2026-01-09T20:08:01.097861+00:00"
59
  }
 
1
  {
2
+ "invoice": {
3
+ "id": "81c96213-485f-4170-92a0-23c08332923b",
4
+ "invoice_number": "INV-TEL-2025-00001",
5
+ "invoice_title": "Invoice for 1 tickets",
6
+ "contractor_id": "1af9fb24-e5bb-40ac-a748-0997580b4c32",
7
+ "client_id": "a2455244-d87e-4279-9fca-dc067f06b5c3",
8
+ "project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a",
9
+ "issue_date": "2025-12-10",
10
+ "due_date": "2026-01-09",
11
+ "subtotal": 0.0,
12
+ "tax_rate": 0.0,
13
+ "tax_amount": 0.0,
14
+ "discount_amount": 0.0,
15
+ "total_amount": 0.0,
16
+ "amount_paid": 0.0,
17
+ "amount_due": 0.0,
18
+ "currency": "KES",
19
+ "status": "draft",
20
+ "notes": null,
21
+ "terms_and_conditions": null,
22
+ "line_items": [
23
  {
24
+ "id": "uqtB0XJIJAviIUWq-Ez7dA",
25
+ "type": "ticket",
26
+ "ticket_id": "2de41ce7-dff1-4151-9710-87958d18b5c4",
27
+ "sales_order_id": "a1d04741-b1b5-4a9e-baf6-d1e186886122",
28
+ "sales_order_number": "ORD-2025-015",
29
+ "description": "Installation - Elizabeth Muthoni",
30
+ "ticket_type": "installation",
31
+ "completed_at": "2025-12-10T12:36:25.444896+00:00",
32
+ "quantity": 1.0,
33
+ "unit_price": 0.0,
34
+ "total": 0.0,
35
+ "ticket_details": {
36
+ "id": "2de41ce7-dff1-4151-9710-87958d18b5c4",
37
+ "ticket_name": "Elizabeth Muthoni",
38
+ "ticket_type": "installation",
39
+ "work_description": "Install Premium Fiber 100Mbps for Elizabeth Muthoni",
40
+ "completed_at": "2025-12-10T12:36:25.444896+00:00",
41
+ "scheduled_date": "2025-12-11",
42
+ "work_location": {
43
+ "latitude": -1.219888,
44
+ "longitude": 36.877013
45
+ },
46
+ "completion_data": {
47
+ "odu_serial": "wetert",
48
+ "ont_serial": "ewrwet",
49
+ "odu_imei_number": "sdfgreh",
50
+ "activated_number": "12345678"
51
+ },
52
+ "images": [
53
+ {
54
+ "id": "07674999-1fcd-46e9-86ad-8dae1846a218",
55
+ "image_type": "completion",
56
+ "description": "[Airtel network] Completion photo for ticket",
57
+ "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370171/ticket_2de41ce7_ticket_photo_airtel_network_airtel_network_20251210_123610_screenshot_2025-08-25_090418.webp",
58
+ "file_name": "ticket_2de41ce7_ticket_photo_airtel_network_airtel_network_20251210_123610_screenshot_2025-08-25_090418.png",
59
+ "captured_at": "2025-12-10T12:36:11.763055+00:00"
60
+ },
61
+ {
62
+ "id": "cbbec871-af75-4364-ab34-55272764a415",
63
+ "image_type": "completion",
64
+ "description": "[Speedtest] Completion photo for ticket",
65
+ "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370172/ticket_2de41ce7_ticket_photo_speedtest_speedtest_20251210_123611_screenshot_2025-08-19_155834.webp",
66
+ "file_name": "ticket_2de41ce7_ticket_photo_speedtest_speedtest_20251210_123611_screenshot_2025-08-19_155834.png",
67
+ "captured_at": "2025-12-10T12:36:13.395865+00:00"
68
+ },
69
+ {
70
+ "id": "ecfc24fc-6be0-4888-a65e-7acf83088c2e",
71
+ "image_type": "completion",
72
+ "description": "[ODU outdoor image] Completion photo for ticket",
73
+ "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370173/ticket_2de41ce7_ticket_photo_odu_outdoor_image_odu_outdoor_image_20251210_123613_screenshot_2025-09-01_163321.webp",
74
+ "file_name": "ticket_2de41ce7_ticket_photo_odu_outdoor_image_odu_outdoor_image_20251210_123613_screenshot_2025-09-01_163321.png",
75
+ "captured_at": "2025-12-10T12:36:14.128316+00:00"
76
+ },
77
+ {
78
+ "id": "e37b665d-fd6d-4c77-b39e-a6064cb49d64",
79
+ "image_type": "completion",
80
+ "description": "[JCC] Completion photo for ticket",
81
+ "url": "https://res.cloudinary.com/dnhajmziu/image/upload/v1765370174/ticket_2de41ce7_ticket_photo_jcc_jcc_20251210_123614_screenshot_2025-09-04_093459.webp",
82
+ "file_name": "ticket_2de41ce7_ticket_photo_jcc_jcc_20251210_123614_screenshot_2025-09-04_093459.png",
83
+ "captured_at": "2025-12-10T12:36:14.781724+00:00"
84
+ }
85
+ ],
86
+ "images_count": 4
87
+ }
88
  }
89
+ ]
 
90
  },
91
+ "token_expires_at": "2026-01-09T20:08:01.097861+00:00",
92
+ "times_viewed": 23
 
 
93
  }
docs/devlogs/server/builderrors.txt CHANGED
@@ -1,60 +1,135 @@
1
- ===== Application Startup at 2025-12-11 07:23:36 =====
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 24, in import_from_string
39
- raise exc from None
40
- File "/usr/local/lib/python3.11/site-packages/uvicorn/importer.py", line 21, in import_from_string
41
- module = importlib.import_module(module_str)
42
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43
- File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
44
- return _bootstrap._gcd_import(name[level:], package, level)
45
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46
- File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
47
- File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
48
- File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
49
- File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
50
- File "<frozen importlib._bootstrap_external>", line 940, in exec_module
51
- File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
52
- File "/app/src/app/main.py", line 168, in <module>
53
- from app.api.v1.router import api_router
54
- File "/app/src/app/api/v1/router.py", line 5, in <module>
55
- from app.api.v1 import (
56
- File "/app/src/app/api/v1/invoice_viewing.py", line 12, in <module>
57
- from app.services.invoice_viewing_service import InvoiceViewingService
58
- File "/app/src/app/services/invoice_viewing_service.py", line 22, in <module>
59
  from app.models.project_region import ProjectRegion
60
- ModuleNotFoundError: No module named 'app.models.project_region'
 
 
 
1
+ ===== Application Startup at 2025-12-11 07:27:07 =====
2
 
3
+ INFO: Started server process [7]
4
+ INFO: Waiting for application startup.
5
+ INFO: 2025-12-11T07:27:20 - app.main: ============================================================
6
+ INFO: 2025-12-11T07:27:20 - app.main: 🚀 SwiftOps API v1.0.0 | PRODUCTION
7
+ INFO: 2025-12-11T07:27:20 - app.main: 📊 Dashboard: Enabled
8
+ INFO: 2025-12-11T07:27:20 - app.main: ============================================================
9
+ INFO: 2025-12-11T07:27:20 - app.main: 📦 Database:
10
+ INFO: 2025-12-11T07:27:20 - app.main: ✓ Connected | 47 tables | 6 users
11
+ INFO: 2025-12-11T07:27:20 - app.main: 💾 Cache & Sessions:
12
+ INFO: 2025-12-11T07:27:21 - app.services.otp_service: ✅ OTP Service initialized with Redis storage
13
+ INFO: 2025-12-11T07:27:22 - app.main: ✓ Redis: Connected
14
+ INFO: 2025-12-11T07:27:22 - app.main: 🔌 External Services:
15
+ INFO: 2025-12-11T07:27:22 - app.main: ✓ Cloudinary: Connected
16
+ INFO: 2025-12-11T07:27:22 - app.main: ✓ Resend: Configured
17
+ INFO: 2025-12-11T07:27:22 - app.main: ○ WASender: Disconnected
18
+ INFO: 2025-12-11T07:27:22 - app.main: ✓ Supabase: Connected | 6 buckets
19
+ INFO: 2025-12-11T07:27:22 - app.main: ⏰ Scheduler:
20
+ INFO: 2025-12-11T07:27:22 - apscheduler.scheduler: Adding job tentatively -- it will be properly scheduled when the scheduler starts
21
+ INFO: 2025-12-11T07:27:22 - apscheduler.scheduler: Added job "Daily Field Agent Reconciliation" to job store "default"
22
+ INFO: 2025-12-11T07:27:22 - apscheduler.scheduler: Scheduler started
23
+ INFO: 2025-12-11T07:27:22 - app.tasks.scheduler: Reconciliation scheduler started (runs at 10 PM Africa/Nairobi)
24
+ INFO: 2025-12-11T07:27:22 - app.main: ✓ Daily reconciliation scheduler started (runs at midnight)
25
+ INFO: 2025-12-11T07:27:22 - app.main: ============================================================
26
+ INFO: 2025-12-11T07:27:22 - app.main: ✅ Startup complete | Ready to serve requests
27
+ INFO: 2025-12-11T07:27:22 - 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: 10.16.37.13:61090 - "GET /health HTTP/1.1" 200 OK
31
+ INFO: 10.16.13.79:3361 - "GET /health HTTP/1.1" 200 OK
32
+ INFO: 10.16.37.13:38247 - "GET /health HTTP/1.1" 200 OK
33
+ INFO: 10.16.13.79:5741 - "GET /health HTTP/1.1" 200 OK
34
+ INFO: 10.16.13.79:5741 - "GET /api/v1/invoices/view?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM HTTP/1.1" 200 OK
35
+ INFO: 10.16.37.13:22507 - "GET /api/v1/invoices/view?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM&ticket_id=2de41ce7-dff1-4151-9710-87958d18b5c4 HTTP/1.1" 200 OK
36
+ INFO: 10.16.13.79:6339 - "GET /health HTTP/1.1" 200 OK
37
+ INFO: 10.16.37.13:44755 - "GET /health HTTP/1.1" 200 OK
38
+ INFO: 10.16.37.13:52588 - "GET /health HTTP/1.1" 200 OK
39
+ INFO: 10.16.37.13:52588 - "GET /health HTTP/1.1" 200 OK
40
+ INFO: 10.16.13.79:1046 - "GET /health HTTP/1.1" 200 OK
41
+ INFO: 10.16.13.79:33463 - "GET /health HTTP/1.1" 200 OK
42
+ INFO: 10.16.37.13:14826 - "GET /api/v1/invoices/view?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM HTTP/1.1" 200 OK
43
+ INFO: 10.16.13.79:33463 - "GET / HTTP/1.1" 200 OK
44
+ INFO: 10.16.37.13:14826 - "GET /api/v1/invoices/view?token=Vuds_hyGrwV7qRNL56QdPD_1zQNE3_NDvtdatIe2MiM&ticket_id=2de41ce7-dff1-4151-9710-87958d18b5c4 HTTP/1.1" 200 OK
45
+ INFO: 10.16.37.13:41221 - "GET /api/v1/invoices/81c96213-485f-4170-92a0-23c08332923b/export/csv HTTP/1.1" 403 Forbidden
46
+ INFO: 10.16.37.13:24952 - "GET /health HTTP/1.1" 200 OK
47
+ INFO: 10.16.37.13:27416 - "GET /health HTTP/1.1" 200 OK
48
+ INFO: 10.16.37.13:6790 - "GET /health HTTP/1.1" 200 OK
49
+ INFO: 10.16.37.13:24737 - "GET /health HTTP/1.1" 200 OK
50
+ INFO: 2025-12-11T07:37:10 - app.core.supabase_auth: User signed in successfully: nadina73@nembors.com
51
+ INFO: 2025-12-11T07:37:11 - app.services.audit_service: Audit log created: login on auth by nadina73@nembors.com
52
+ INFO: 2025-12-11T07:37:11 - app.api.v1.auth: User logged in successfully: nadina73@nembors.com
53
+ INFO: 10.16.37.13:40121 - "POST /api/v1/auth/login HTTP/1.1" 200 OK
54
+ INFO: 2025-12-11T07:37:11 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
55
+ INFO: 2025-12-11T07:37:11 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
56
+ INFO: 10.16.13.79:61613 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
57
+ INFO: 2025-12-11T07:37:12 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
58
+ INFO: 2025-12-11T07:37:12 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
59
+ INFO: 10.16.37.13:40121 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
60
+ INFO: 2025-12-11T07:37:12 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
61
+ INFO: 2025-12-11T07:37:12 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
62
+ INFO: 10.16.13.79:61613 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
63
+ INFO: 2025-12-11T07:37:14 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
64
+ INFO: 2025-12-11T07:37:14 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
65
+ INFO: 10.16.13.79:61613 - "GET /api/v1/analytics/user/overview HTTP/1.1" 200 OK
66
+ INFO: 2025-12-11T07:37:15 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
67
+ INFO: 2025-12-11T07:37:15 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
68
+ INFO: 2025-12-11T07:37:15 - app.services.project_service: Listed 1 projects (total: 1) for user c5cf92be-4172-4fe2-af5c-f05d83b3a938
69
+ INFO: 10.16.37.13:40121 - "GET /api/v1/projects?page=1&per_page=100 HTTP/1.1" 200 OK
70
+ INFO: 2025-12-11T07:37:18 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
71
+ INFO: 2025-12-11T07:37:18 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
72
+ INFO: 2025-12-11T07:37:18 - app.services.audit_service: Audit log created: update on user_preferences by nadina73@nembors.com
73
+ INFO: 2025-12-11T07:37:18 - app.api.v1.auth: Preferences updated for user: nadina73@nembors.com
74
+ INFO: 10.16.13.79:24575 - "PUT /api/v1/auth/me/preferences HTTP/1.1" 200 OK
75
+ INFO: 2025-12-11T07:37:19 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
76
+ INFO: 2025-12-11T07:37:19 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
77
+ INFO: 10.16.37.13:22676 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
78
+ INFO: 2025-12-11T07:37:19 - app.api.deps: Checking active user: c5cf92be-4172-4fe2-af5c-f05d83b3a938, is_active: True, type: <class 'bool'>
79
+ INFO: 2025-12-11T07:37:19 - app.api.deps: User c5cf92be-4172-4fe2-af5c-f05d83b3a938 is active - proceeding
80
+ INFO: 2025-12-11T07:37:19 - app.services.dashboard_service: Dashboard cache MISS for project 0ade6bd1-e492-4e25-b681-59f42058d29a, user c5cf92be-4172-4fe2-af5c-f05d83b3a938 - building fresh data
81
+ INFO: 2025-12-11T07:37:19 - app.services.dashboard_service: Built and cached dashboard for project 0ade6bd1-e492-4e25-b681-59f42058d29a
82
+ INFO: 10.16.13.79:24575 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/dashboard HTTP/1.1" 200 OK
83
+ INFO: 10.16.13.79:24575 - "GET /api/v1/notifications?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a&page_size=50&is_read=false HTTP/1.1" 200 OK
84
+ INFO: 10.16.37.13:22676 - "GET /api/v1/tickets/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 200 OK
85
+ INFO: 10.16.37.13:22676 - "GET /api/v1/contractor-invoices/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 200 OK
86
+ INFO: 10.16.13.79:24575 - "GET /api/v1/contractor-invoices?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a HTTP/1.1" 200 OK
87
+ INFO: 10.16.37.13:22676 - "GET /api/v1/contractor-invoices/81c96213-485f-4170-92a0-23c08332923b HTTP/1.1" 200 OK
88
+ INFO: 10.16.37.13:31765 - "GET /api/v1/tickets/2de41ce7-dff1-4151-9710-87958d18b5c4/detail HTTP/1.1" 200 OK
89
+ INFO: 10.16.13.79:34462 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/regions/4cd27765-5720-4cc0-872e-bf0da3cd1898 HTTP/1.1" 500 Internal Server Error
90
+ ERROR: Exception in ASGI application
91
  Traceback (most recent call last):
92
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
93
+ result = await app( # type: ignore[func-returns-value]
94
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95
+ File "/usr/local/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
96
+ return await self.app(scope, receive, send)
97
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
98
+ File "/usr/local/lib/python3.11/site-packages/fastapi/applications.py", line 1106, in __call__
99
+ await super().__call__(scope, receive, send)
100
+ File "/usr/local/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__
101
+ await self.middleware_stack(scope, receive, send)
102
+ File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in __call__
103
+ raise exc
104
+ File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in __call__
105
+ await self.app(scope, receive, _send)
106
+ File "/usr/local/lib/python3.11/site-packages/starlette/middleware/cors.py", line 91, in __call__
107
+ await self.simple_response(scope, receive, send, request_headers=headers)
108
+ File "/usr/local/lib/python3.11/site-packages/starlette/middleware/cors.py", line 146, in simple_response
109
+ await self.app(scope, receive, send)
110
+ File "/usr/local/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
111
+ raise exc
112
+ File "/usr/local/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
113
+ await self.app(scope, receive, sender)
114
+ File "/usr/local/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
115
+ raise e
116
+ File "/usr/local/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
117
+ await self.app(scope, receive, send)
118
+ File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
119
+ await route.handle(scope, receive, send)
120
+ File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle
121
+ await self.app(scope, receive, send)
122
+ File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 66, in app
123
+ response = await func(request)
124
+ ^^^^^^^^^^^^^^^^^^^
125
+ File "/usr/local/lib/python3.11/site-packages/fastapi/routing.py", line 274, in app
126
+ raw_response = await run_endpoint_function(
127
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128
+ File "/usr/local/lib/python3.11/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
129
+ return await dependant.call(**values)
130
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131
+ File "/app/src/app/api/v1/projects.py", line 1740, in get_project_region
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  from app.models.project_region import ProjectRegion
133
+ ModuleNotFoundError: No module named 'app.models.project_region'
134
+ INFO: 10.16.37.13:50538 - "GET /health HTTP/1.1" 200 OK
135
+ INFO: 10.16.37.13:13519 - "GET /health HTTP/1.1" 200 OK
src/app/api/v1/projects.py CHANGED
@@ -1737,7 +1737,7 @@ async def get_project_region(
1737
  - PM, Dispatcher, Field Agent: Can view regions in their projects
1738
  - Platform Admin: Can view any region
1739
  """
1740
- from app.models.project_region import ProjectRegion
1741
 
1742
  # Get region
1743
  region = db.query(ProjectRegion).filter(
 
1737
  - PM, Dispatcher, Field Agent: Can view regions in their projects
1738
  - Platform Admin: Can view any region
1739
  """
1740
+ from app.models.project import ProjectRegion
1741
 
1742
  # Get region
1743
  region = db.query(ProjectRegion).filter(
src/app/services/invoice_viewing_service.py CHANGED
@@ -180,6 +180,11 @@ class InvoiceViewingService:
180
 
181
  enriched_line_items.append(enriched_item)
182
 
 
 
 
 
 
183
  # Build response
184
  return {
185
  "invoice": {
@@ -187,8 +192,11 @@ class InvoiceViewingService:
187
  "invoice_number": invoice.invoice_number,
188
  "invoice_title": invoice.invoice_title,
189
  "contractor_id": str(invoice.contractor_id),
 
190
  "client_id": str(invoice.client_id),
 
191
  "project_id": str(invoice.project_id) if invoice.project_id else None,
 
192
  "issue_date": invoice.issue_date.isoformat(),
193
  "due_date": invoice.due_date.isoformat(),
194
  "subtotal": float(invoice.subtotal),
 
180
 
181
  enriched_line_items.append(enriched_item)
182
 
183
+ # Get organization names
184
+ contractor_name = invoice.contractor.name if invoice.contractor else None
185
+ client_name = invoice.client.name if invoice.client else None
186
+ project_name = invoice.project.title if invoice.project else None
187
+
188
  # Build response
189
  return {
190
  "invoice": {
 
192
  "invoice_number": invoice.invoice_number,
193
  "invoice_title": invoice.invoice_title,
194
  "contractor_id": str(invoice.contractor_id),
195
+ "contractor_name": contractor_name,
196
  "client_id": str(invoice.client_id),
197
+ "client_name": client_name,
198
  "project_id": str(invoice.project_id) if invoice.project_id else None,
199
+ "project_name": project_name,
200
  "issue_date": invoice.issue_date.isoformat(),
201
  "due_date": invoice.due_date.isoformat(),
202
  "subtotal": float(invoice.subtotal),