PupaClic commited on
Commit
d58df14
Β·
1 Parent(s): 624809f

docs: remove architecture documentation and refactor service layer

Browse files

- Remove ARCHITECTURE.md documentation file
- Update README.md with current project information
- Refactor nosql.py database connection handling
- Update merchant_schema.py with improved validation logic
- Enhance employee_service.py with better error handling
- Improve merchant_service.py service methods
- Optimize sales_order_service.py query performance
- Update db_init.py database initialization utilities
- Consolidate documentation into codebase comments for maintainability

ARCHITECTURE.md DELETED
@@ -1,366 +0,0 @@
1
- # Merchant Module Architecture
2
-
3
- ## System Architecture
4
-
5
- ```
6
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
7
- β”‚ FastAPI Application β”‚
8
- β”‚ (main.py) β”‚
9
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
10
- β”‚
11
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
12
- β”‚ β”‚ β”‚
13
- β–Ό β–Ό β–Ό
14
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
15
- β”‚ Routers β”‚ β”‚ Health β”‚ β”‚ CORS β”‚
16
- β”‚ (merchant) β”‚ β”‚ Check β”‚ β”‚Middlewareβ”‚
17
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
18
- β”‚
19
- β–Ό
20
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
21
- β”‚ Merchant Router β”‚
22
- β”‚ - POST /merchants β”‚
23
- β”‚ - GET /merchants/{id} β”‚
24
- β”‚ - PUT /merchants/{id} β”‚
25
- β”‚ - DELETE /merchants/{id} β”‚
26
- β”‚ - GET /merchants β”‚
27
- β”‚ - GET /merchants/{id}/children β”‚
28
- β”‚ - GET /merchants/{id}/hierarchy β”‚
29
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
30
- β”‚
31
- β–Ό
32
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
33
- β”‚ Merchant Service Layer β”‚
34
- β”‚ - create_merchant() β”‚
35
- β”‚ - get_merchant() β”‚
36
- β”‚ - update_merchant() β”‚
37
- β”‚ - delete_merchant() β”‚
38
- β”‚ - list_merchants() β”‚
39
- β”‚ - validate_parent_merchant() β”‚
40
- β”‚ - is_merchant_code_unique() β”‚
41
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
42
- β”‚
43
- β–Ό
44
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
45
- β”‚ Pydantic Schemas β”‚
46
- β”‚ - MerchantCreate (validation) β”‚
47
- β”‚ - MerchantUpdate (partial) β”‚
48
- β”‚ - MerchantResponse (output) β”‚
49
- β”‚ - ContactModel β”‚
50
- β”‚ - GeoLocationModel β”‚
51
- β”‚ - KYCModel β”‚
52
- β”‚ - SettingsModel β”‚
53
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
54
- β”‚
55
- β–Ό
56
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
57
- β”‚ Database Models β”‚
58
- β”‚ - MerchantModel (MongoDB document) β”‚
59
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
60
- β”‚
61
- β–Ό
62
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
63
- β”‚ MongoDB (Motor/PyMongo) β”‚
64
- β”‚ Collection: scm_merchants β”‚
65
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
66
- ```
67
-
68
- ## Merchant Hierarchy
69
-
70
- ```
71
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
72
- β”‚ National CNF β”‚
73
- β”‚ - Root level (no parent) β”‚
74
- β”‚ - KYC: GST, PAN, Bank Account, IFSC β”‚
75
- β”‚ - Controls national distribution β”‚
76
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
77
- β”‚
78
- β”‚ parent_merchant_id
79
- β–Ό
80
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
81
- β”‚ CNF β”‚
82
- β”‚ - Regional level οΏ½οΏ½οΏ½
83
- β”‚ - KYC: GST, PAN, Bank Account, IFSC β”‚
84
- β”‚ - Parent: National CNF β”‚
85
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
86
- β”‚
87
- β”‚ parent_merchant_id
88
- β–Ό
89
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
90
- β”‚ Distributor β”‚
91
- β”‚ - Local distribution β”‚
92
- β”‚ - KYC: GST, PAN, Bank Account, IFSC β”‚
93
- β”‚ - Parent: CNF β”‚
94
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
95
- β”‚
96
- β”‚ parent_merchant_id
97
- β–Ό
98
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
99
- β”‚ Salon β”‚
100
- β”‚ - End retailer β”‚
101
- β”‚ - KYC: PAN (mandatory), GST (optional) β”‚
102
- β”‚ - Geo-location required β”‚
103
- β”‚ - Parent: Distributor β”‚
104
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
105
- ```
106
-
107
- ## Request Flow
108
-
109
- ```
110
- Client Request
111
- β”‚
112
- β–Ό
113
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
114
- β”‚ FastAPI Router β”‚ ◄── Pydantic validation
115
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
116
- β”‚
117
- β–Ό
118
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
119
- β”‚ Merchant Service β”‚
120
- β”‚ 1. Validate merchant code uniqueness β”‚
121
- β”‚ 2. Validate parent exists & active β”‚
122
- β”‚ 3. Check parent-child type match β”‚
123
- β”‚ 4. Validate business rules β”‚
124
- β”‚ 5. Generate merchant ID β”‚
125
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
126
- β”‚
127
- β–Ό
128
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
129
- β”‚ MongoDB β”‚ ◄── Insert/Update/Query
130
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
131
- β”‚
132
- β–Ό
133
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
134
- β”‚ Response β”‚ ◄── MerchantResponse schema
135
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
136
- ```
137
-
138
- ## Validation Pipeline
139
-
140
- ```
141
- Incoming Request Data
142
- β”‚
143
- β–Ό
144
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
145
- β”‚ 1. Pydantic Schema Validation β”‚
146
- β”‚ - Field types β”‚
147
- β”‚ - Required fields β”‚
148
- β”‚ - String lengths β”‚
149
- β”‚ - Regex patterns β”‚
150
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
151
- β”‚
152
- β–Ό
153
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
154
- β”‚ 2. Field-level Validators β”‚
155
- β”‚ - Phone: E.164 format β”‚
156
- β”‚ - Email: lowercase normalization β”‚
157
- β”‚ - Pincode: Indian 6-digit β”‚
158
- β”‚ - GST: 15-char pattern β”‚
159
- β”‚ - PAN: 10-char pattern β”‚
160
- β”‚ - IFSC: 11-char pattern β”‚
161
- β”‚ - URLs: HTTPS only β”‚
162
- β”‚ - Lat/Lng: Range validation β”‚
163
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
164
- β”‚
165
- β–Ό
166
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
167
- β”‚ 3. Root Validators (Cross-field) β”‚
168
- β”‚ - Parent requirement by type β”‚
169
- β”‚ - KYC completeness by type β”‚
170
- β”‚ - Geo-location for salons β”‚
171
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
172
- β”‚
173
- β–Ό
174
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
175
- β”‚ 4. Business Logic Validation β”‚
176
- β”‚ - Parent exists & active β”‚
177
- β”‚ - Parent type matches hierarchy β”‚
178
- β”‚ - Merchant code unique in scope β”‚
179
- β”‚ - Credit limit within max β”‚
180
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
181
- β”‚
182
- β–Ό
183
- Valid Data β†’ Database
184
- ```
185
-
186
- ## Merchant Status Lifecycle
187
-
188
- ```
189
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”
190
- β”‚ DRAFT β”‚ ◄── Initial creation
191
- β””β”€β”€β”€β”¬β”€β”€β”€β”˜
192
- β”‚ Submit for approval
193
- β–Ό
194
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
195
- β”‚ SUBMITTED β”‚
196
- β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
197
- β”‚
198
- β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
199
- β”‚ β”‚
200
- β–Ό β–Ό
201
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
202
- β”‚ APPROVED β”‚ β”‚ REJECTED β”‚ ◄── Denied/Deleted
203
- β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
204
- β”‚
205
- β”‚ Activate
206
- β–Ό
207
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
208
- β”‚ ACTIVE β”‚ ◄──┐
209
- β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜ β”‚ Reactivate
210
- β”‚ β”‚
211
- β”‚ Suspend β”‚
212
- β–Ό β”‚
213
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
214
- β”‚ SUSPENDED β”‚β”€β”˜
215
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
216
- ```
217
-
218
- ## Data Model Relations
219
-
220
- ```
221
- MerchantModel
222
- β”œβ”€β”€ merchant_id (PK)
223
- β”œβ”€β”€ merchant_type (Enum)
224
- β”œβ”€β”€ parent_merchant_id (FK β†’ merchant_id)
225
- β”œβ”€β”€ merchant_code (Unique within parent)
226
- β”œβ”€β”€ merchant_name
227
- β”œβ”€β”€ contact (Embedded)
228
- β”‚ β”œβ”€β”€ phone
229
- β”‚ β”œβ”€β”€ email
230
- β”‚ β”œβ”€β”€ address_line1
231
- β”‚ β”œβ”€β”€ address_line2
232
- β”‚ β”œβ”€β”€ city
233
- β”‚ β”œβ”€β”€ state
234
- β”‚ β”œβ”€β”€ pincode
235
- β”‚ └── country
236
- β”œβ”€β”€ geo_location (Embedded, required for salon)
237
- β”‚ β”œβ”€β”€ lat
238
- β”‚ └── lng
239
- β”œβ”€β”€ kyc (Embedded)
240
- β”‚ β”œβ”€β”€ gst_number
241
- β”‚ β”œβ”€β”€ pan_number
242
- β”‚ β”œβ”€β”€ business_certificate_url
243
- β”‚ β”œβ”€β”€ bank_account_number
244
- β”‚ β”œβ”€β”€ bank_ifsc
245
- β”‚ └── cancelled_cheque_url
246
- β”œβ”€β”€ settings (Embedded)
247
- β”‚ β”œβ”€β”€ pricing_inherited
248
- β”‚ β”œβ”€β”€ inventory_inherited
249
- β”‚ β”œβ”€β”€ auto_allocate_stock
250
- β”‚ β”œβ”€β”€ allowed_payment_modes (Array)
251
- β”‚ └── credit_limit
252
- β”œβ”€β”€ status (Enum)
253
- β”œβ”€β”€ created_by
254
- β”œβ”€β”€ created_at
255
- β”œβ”€β”€ updated_at
256
- └── metadata (Flexible JSON)
257
- ```
258
-
259
- ## API Endpoint Tree
260
-
261
- ```
262
- /
263
- β”œβ”€β”€ /health GET
264
- └── /merchants
265
- β”œβ”€β”€ / POST (Create)
266
- β”œβ”€β”€ / GET (List with filters)
267
- β”œβ”€β”€ /{merchant_id} GET (Get one)
268
- β”œβ”€β”€ /{merchant_id} PUT (Update)
269
- β”œβ”€β”€ /{merchant_id} DELETE (Soft delete)
270
- β”œβ”€β”€ /{merchant_id}/children GET (List children)
271
- └── /{merchant_id}/hierarchy GET (Get full path)
272
- ```
273
-
274
- ## Module Dependencies
275
-
276
- ```
277
- main.py
278
- β”‚
279
- β”œβ”€β”€ app.routers.merchant_router
280
- β”‚ β”‚
281
- β”‚ └── app.services.merchant_service
282
- β”‚ β”‚
283
- β”‚ β”œβ”€β”€ app.schemas.merchant_schema
284
- β”‚ β”œβ”€β”€ app.models.merchant_model
285
- β”‚ β”œβ”€β”€ app.constants.merchant_types
286
- β”‚ β”œβ”€β”€ app.constants.collections
287
- β”‚ └── app.nosql (MongoDB)
288
- β”‚
289
- β”œβ”€β”€ app.core.config
290
- β”œβ”€β”€ app.core.logging_config
291
- └── app.nosql
292
- └── MongoDB Connection
293
- ```
294
-
295
- ## File Structure Tree
296
-
297
- ```
298
- cuatrolabs-scm-ms/
299
- β”œβ”€β”€ app/
300
- β”‚ β”œβ”€β”€ __init__.py
301
- β”‚ β”œβ”€β”€ cache.py
302
- β”‚ β”œβ”€β”€ nosql.py
303
- β”‚ β”œβ”€β”€ constants/
304
- β”‚ β”‚ β”œβ”€β”€ __init__.py ✨
305
- β”‚ β”‚ β”œβ”€β”€ collections.py
306
- β”‚ β”‚ β”œβ”€β”€ merchant_types.py ✨
307
- β”‚ β”‚ β”œβ”€β”€ roles.py
308
- β”‚ β”‚ └── validation.py ✨ NEW
309
- β”‚ β”œβ”€β”€ core/
310
- β”‚ β”‚ β”œβ”€β”€ config.py
311
- β”‚ β”‚ └── logging_config.py
312
- β”‚ β”œβ”€β”€ models/
313
- β”‚ β”‚ β”œβ”€β”€ merchant_model.py ✨
314
- β”‚ β”‚ β”œβ”€β”€ employee_model.py
315
- β”‚ β”‚ β”œβ”€β”€ role_model.py
316
- β”‚ β”‚ └── auth_log_model.py
317
- β”‚ β”œβ”€β”€ schemas/
318
- β”‚ β”‚ β”œβ”€β”€ __init__.py ✨
319
- β”‚ β”‚ └── merchant_schema.py ✨ NEW
320
- β”‚ β”œβ”€β”€ services/
321
- β”‚ β”‚ β”œβ”€β”€ __init__.py ✨
322
- β”‚ β”‚ └── merchant_service.py ✨ NEW
323
- β”‚ └── routers/
324
- β”‚ β”œβ”€β”€ __init__.py ✨
325
- β”‚ └── merchant_router.py ✨ NEW
326
- β”œβ”€β”€ tests/
327
- β”‚ β”œβ”€β”€ conftest.py
328
- β”‚ β”œβ”€β”€ test_properties_data_models.py
329
- β”‚ └── test_merchant_schemas.py ✨ NEW
330
- β”œβ”€β”€ examples/
331
- β”‚ └── quick_start.py ✨ NEW
332
- β”œβ”€β”€ main.py ✨ NEW
333
- β”œβ”€β”€ requirements.txt
334
- β”œβ”€β”€ README.md
335
- β”œβ”€β”€ MERCHANT_MODULE.md ✨ NEW
336
- └── ENHANCEMENT_SUMMARY.md ✨ NEW
337
-
338
- ✨ = Enhanced/Created
339
- ```
340
-
341
- ## Technology Stack
342
-
343
- ```
344
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
345
- β”‚ Application Layer β”‚
346
- β”‚ - FastAPI (Web Framework) β”‚
347
- β”‚ - Pydantic (Validation) β”‚
348
- β”‚ - Python 3.9+ β”‚
349
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
350
- β”‚
351
- β–Ό
352
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
353
- β”‚ Database Layer β”‚
354
- β”‚ - MongoDB (NoSQL) β”‚
355
- β”‚ - Motor (Async Driver) β”‚
356
- β”‚ - PyMongo β”‚
357
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
358
- β”‚
359
- β–Ό
360
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
361
- β”‚ Supporting Services β”‚
362
- β”‚ - Redis (Cache) - existing β”‚
363
- β”‚ - insightfy-utils (Logging) β”‚
364
- β”‚ - Uvicorn (ASGI Server) β”‚
365
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
366
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,98 +1,263 @@
1
  ---
2
- title: Insightify ANS
3
- emoji: πŸ‘€
4
  colorFrom: yellow
5
  colorTo: green
6
  sdk: docker
7
  pinned: false
8
- short_description: Analytics Notification Services
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
12
 
 
13
 
14
- # Analytics and Notification Service (ANS)
15
 
16
- The Analytics and Notification Service (ANS) is a microservice that provides business intelligence, analytics, and notification capabilities for the Insightfy Bloom platform.
 
 
 
 
 
17
 
18
- ## Features
 
 
 
 
 
 
 
19
 
20
- - **Analytics Dashboard**: Real-time business metrics and KPIs
21
- - **Reporting**: Generate various business reports
22
- - **Data Aggregation**: Collect and process data from other microservices
23
- - **Notifications**: Send alerts and notifications based on business rules
24
- - **Performance Monitoring**: Track business performance indicators
 
25
 
26
- ## Architecture
 
 
 
 
27
 
28
- ANS integrates with other microservices:
29
- - **MPMS**: Product and merchant data
30
- - **RMS**: Resource and employee data
31
- - **TMS**: Transaction and sales data
32
- - **CRM**: Customer relationship data
33
 
34
- ## Database Connections
 
 
 
 
 
 
35
 
36
- - **PostgreSQL**: Primary database for analytics data
37
- - **MongoDB**: Document storage for flexible analytics
38
- - **Redis**: Caching and session management
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  ## API Endpoints
41
 
42
- ### Health Checks
43
  - `GET /health` - Service health status
44
- - `GET /ready` - Readiness check
45
- - `GET /live` - Liveness check
46
 
47
- ### Analytics
48
- - `GET /api/v1/analytics/dashboard` - Dashboard data
49
- - `GET /api/v1/analytics/reports` - Generate reports
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- ## Environment Variables
 
 
 
 
 
 
 
 
52
 
53
  Copy `.env.example` to `.env` and configure:
54
 
55
  ```bash
56
- # Database Configuration
57
- DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/ans_db
58
- REDIS_URL=redis://localhost:6379/0
59
- MONGODB_URL=mongodb://localhost:27017/ans_db
60
-
61
- # Service URLs
62
- MPMS_BASE_URL=http://localhost:8001
63
- RMS_BASE_URL=http://localhost:8002
64
- TMS_BASE_URL=http://localhost:8003
65
- CRM_BASE_URL=http://localhost:8004
66
-
67
- # Service Configuration
68
- SERVICE_NAME=ans
69
- SERVICE_PORT=8005
70
- LOG_LEVEL=INFO
 
 
 
 
 
 
 
 
 
71
 
72
- # JWT Configuration
73
- JWT_SECRET_KEY=your-secret-key
74
- JWT_ALGORITHM=HS256
 
 
 
 
 
 
 
 
 
 
 
 
75
  ```
76
 
77
- ## Running the Service
 
 
 
 
 
 
 
 
 
 
 
78
 
 
 
 
 
 
 
 
79
  ```bash
80
- # Install dependencies
81
  pip install -r requirements.txt
 
 
 
 
 
 
 
82
 
83
- # Run the service
84
- uvicorn app.main:app --host 0.0.0.0 --port 8005 --reload
 
85
  ```
86
 
87
- ## Development
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- The service follows the standard microservice architecture:
90
 
91
- - `app/routers/` - API route handlers
92
- - `app/services/` - Business logic
93
- - `app/repositories/` - Data access layer
94
- - `app/models/` - Database models
95
- - `app/schemas/` - Pydantic schemas
96
- - `app/clients/` - Inter-service communication
97
- - `app/dependencies/` - FastAPI dependencies (auth, etc.)
98
- - `app/utils/` - Utility functions
 
1
  ---
2
+ title: Cuatro Labs SCM
3
+ emoji: 🏭
4
  colorFrom: yellow
5
  colorTo: green
6
  sdk: docker
7
  pinned: false
8
+ short_description: Supply Chain Management - Merchant & Employee Management
9
  ---
10
 
11
+ # Supply Chain Management (SCM) Microservice
12
 
13
+ A comprehensive FastAPI-based microservice for managing merchants, employees, and sales operations within the Insightfy Bloom platform. This service provides robust APIs for organizational hierarchy management, employee tracking, and sales order processing.
14
 
15
+ ## Features
16
 
17
+ ### Merchant Management
18
+ - Create, read, update, and delete merchant records
19
+ - Support for multiple merchant types (Distributor, Retailer, Wholesaler, Manufacturer)
20
+ - Hierarchical merchant relationships (parent-child structure)
21
+ - Query merchants by hierarchy and relationships
22
+ - Flexible filtering and pagination
23
 
24
+ ### Employee Management
25
+ - Complete employee lifecycle management (CRUD operations)
26
+ - Employee types: Sales Representative, Manager, Admin, Warehouse Staff, Delivery Personnel
27
+ - Organizational hierarchy with reporting structures
28
+ - Employee status management (Active, Inactive, On Leave, Terminated)
29
+ - Location consent tracking
30
+ - Employee code-based lookups
31
+ - Management chain and direct reports queries
32
 
33
+ ### Sales Order Management
34
+ - Create and manage sales orders
35
+ - Order status tracking (Draft, Confirmed, Processing, Shipped, Delivered, Cancelled)
36
+ - Invoice generation
37
+ - Sales analytics and widgets
38
+ - Advanced filtering and search capabilities
39
 
40
+ ### Authentication & Security
41
+ - JWT-based authentication
42
+ - OTP verification via SMS (Twilio) and Email (SMTP)
43
+ - Role-based access control
44
+ - Secure password hashing with bcrypt
45
 
46
+ ## Architecture
 
 
 
 
47
 
48
+ ### Technology Stack
49
+ - **Framework**: FastAPI 0.104.1
50
+ - **Database**: MongoDB (via Motor async driver)
51
+ - **Cache**: Redis
52
+ - **Authentication**: JWT (python-jose)
53
+ - **Notifications**: Twilio (SMS), SMTP (Email)
54
+ - **Server**: Uvicorn with async support
55
 
56
+ ### Project Structure
57
+ ```
58
+ app/
59
+ β”œβ”€β”€ routers/ # API route handlers
60
+ β”‚ β”œβ”€β”€ employee_router.py
61
+ β”‚ β”œβ”€β”€ merchant_router.py
62
+ β”‚ └── sales_order_router.py
63
+ β”œβ”€β”€ services/ # Business logic layer
64
+ β”œβ”€β”€ models/ # Database models
65
+ β”œβ”€β”€ schemas/ # Pydantic validation schemas
66
+ β”œβ”€β”€ constants/ # Application constants
67
+ β”‚ β”œβ”€β”€ collections.py
68
+ β”‚ β”œβ”€β”€ employee_types.py
69
+ β”‚ β”œβ”€β”€ merchant_types.py
70
+ β”‚ β”œβ”€β”€ roles.py
71
+ β”‚ └── validation.py
72
+ β”œβ”€β”€ core/ # Core configuration
73
+ β”‚ β”œβ”€β”€ config.py
74
+ β”‚ └── logging_config.py
75
+ β”œβ”€β”€ utils/ # Utility functions
76
+ β”œβ”€β”€ dependencies/ # FastAPI dependencies
77
+ β”œβ”€β”€ main.py # Application entry point
78
+ β”œβ”€β”€ nosql.py # MongoDB connection
79
+ └── cache.py # Redis cache management
80
+ ```
81
 
82
  ## API Endpoints
83
 
84
+ ### Health Check
85
  - `GET /health` - Service health status
 
 
86
 
87
+ ### Merchant Endpoints (`/api/v1/merchants`)
88
+ - `POST /api/v1/merchants` - Create new merchant
89
+ - `GET /api/v1/merchants/{merchant_id}` - Get merchant by ID
90
+ - `PUT /api/v1/merchants/{merchant_id}` - Update merchant
91
+ - `DELETE /api/v1/merchants/{merchant_id}` - Delete merchant
92
+ - `GET /api/v1/merchants` - List merchants with filters
93
+ - `GET /api/v1/merchants/{merchant_id}/children` - Get child merchants
94
+ - `GET /api/v1/merchants/{merchant_id}/hierarchy` - Get merchant hierarchy
95
+
96
+ ### Employee Endpoints (`/api/v1/employees`)
97
+ - `POST /api/v1/employees` - Create new employee
98
+ - `GET /api/v1/employees/{user_id}` - Get employee by ID
99
+ - `GET /api/v1/employees/code/{employee_code}` - Get employee by code
100
+ - `PUT /api/v1/employees/{user_id}` - Update employee
101
+ - `DELETE /api/v1/employees/{user_id}` - Delete employee
102
+ - `GET /api/v1/employees` - List employees with filters
103
+ - `GET /api/v1/employees/{user_id}/reports` - Get direct reports
104
+ - `GET /api/v1/employees/{user_id}/hierarchy` - Get management chain
105
+ - `PATCH /api/v1/employees/{user_id}/status` - Update employee status
106
+ - `PATCH /api/v1/employees/{user_id}/location-consent` - Update location consent
107
 
108
+ ### Sales Order Endpoints (`/api/v1/sales`)
109
+ - `POST /api/v1/sales/order` - Create sales order
110
+ - `GET /api/v1/sales/order/{sales_order_id}` - Get sales order
111
+ - `PUT /api/v1/sales/order/{sales_order_id}` - Update sales order
112
+ - `POST /api/v1/sales/order/list` - List sales orders with filters
113
+ - `POST /api/v1/sales/order/{sales_order_id}/invoice` - Generate invoice
114
+ - `GET /api/v1/sales/info/widgets` - Get sales analytics widgets
115
+
116
+ ## Environment Configuration
117
 
118
  Copy `.env.example` to `.env` and configure:
119
 
120
  ```bash
121
+ # Application
122
+ APP_NAME=SCM Microservice
123
+ APP_VERSION=1.0.0
124
+ DEBUG=false
125
+
126
+ # MongoDB
127
+ MONGODB_URI=mongodb://localhost:27017
128
+ MONGODB_DB_NAME=scm_db
129
+
130
+ # Redis
131
+ REDIS_HOST=localhost
132
+ REDIS_PORT=6379
133
+ REDIS_PASSWORD=
134
+ REDIS_DB=0
135
+
136
+ # JWT Authentication
137
+ SECRET_KEY=your-secret-key-change-in-production
138
+ ALGORITHM=HS256
139
+ TOKEN_EXPIRATION_HOURS=8
140
+
141
+ # OTP Configuration
142
+ OTP_TTL_SECONDS=600
143
+ OTP_RATE_LIMIT_MAX=10
144
+ OTP_RATE_LIMIT_WINDOW=600
145
 
146
+ # Twilio (SMS)
147
+ TWILIO_ACCOUNT_SID=your_twilio_account_sid
148
+ TWILIO_AUTH_TOKEN=your_twilio_auth_token
149
+ TWILIO_PHONE_NUMBER=+1234567890
150
+
151
+ # SMTP (Email)
152
+ SMTP_HOST=smtp.gmail.com
153
+ SMTP_PORT=587
154
+ SMTP_USERNAME=your_email@gmail.com
155
+ SMTP_PASSWORD=your_app_password
156
+ SMTP_FROM_EMAIL=noreply@scm.com
157
+ SMTP_USE_TLS=true
158
+
159
+ # Logging
160
+ LOG_LEVEL=INFO
161
  ```
162
 
163
+ ## Getting Started
164
+
165
+ ### Prerequisites
166
+ - Python 3.11+
167
+ - MongoDB
168
+ - Redis
169
+ - Twilio account (for SMS)
170
+ - SMTP server access (for email)
171
+
172
+ ### Local Development
173
+
174
+ 1. Clone the repository and navigate to the project directory
175
 
176
+ 2. Create and activate a virtual environment:
177
+ ```bash
178
+ python -m venv venv
179
+ source venv/bin/activate # On Windows: venv\Scripts\activate
180
+ ```
181
+
182
+ 3. Install dependencies:
183
  ```bash
 
184
  pip install -r requirements.txt
185
+ ```
186
+
187
+ 4. Configure environment variables:
188
+ ```bash
189
+ cp .env.example .env
190
+ # Edit .env with your configuration
191
+ ```
192
 
193
+ 5. Run the service:
194
+ ```bash
195
+ uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
196
  ```
197
 
198
+ 6. Access the API documentation:
199
+ - Swagger UI: http://localhost:8000/docs
200
+ - ReDoc: http://localhost:8000/redoc
201
+
202
+ ### Docker Deployment
203
+
204
+ Build and run with Docker:
205
+
206
+ ```bash
207
+ # Build the image
208
+ docker build -t scm-microservice .
209
+
210
+ # Run the container
211
+ docker run -p 7860:7860 --env-file .env scm-microservice
212
+ ```
213
+
214
+ The service will be available at http://localhost:7860
215
+
216
+ ## Testing
217
+
218
+ Run the test suite:
219
+
220
+ ```bash
221
+ # Run all tests
222
+ pytest
223
+
224
+ # Run with coverage
225
+ pytest --cov=app tests/
226
+
227
+ # Run specific test file
228
+ pytest tests/test_employee_schemas.py
229
+ ```
230
+
231
+ Test files include:
232
+ - `tests/test_employee_schemas.py` - Employee schema validation tests
233
+ - `tests/test_merchant_schemas.py` - Merchant schema validation tests
234
+ - `tests/test_properties_data_models.py` - Property-based testing with Hypothesis
235
+
236
+ ## Examples
237
+
238
+ Check the `examples/` directory for usage examples:
239
+ - `quick_start.py` - Basic API usage
240
+ - `sales_order_example.py` - Sales order workflow
241
+
242
+ ## API Documentation
243
+
244
+ Once the service is running, interactive API documentation is available at:
245
+ - **Swagger UI**: `/docs`
246
+ - **ReDoc**: `/redoc`
247
+
248
+ ## Contributing
249
+
250
+ This service follows standard FastAPI best practices:
251
+ - Async/await for all I/O operations
252
+ - Pydantic models for request/response validation
253
+ - Dependency injection for shared resources
254
+ - Structured logging with python-json-logger
255
+ - Property-based testing with Hypothesis
256
+
257
+ ## License
258
+
259
+ Part of the Insightfy Bloom platform by Cuatro Labs.
260
 
261
+ ## Support
262
 
263
+ For issues and questions, please refer to the project documentation or contact the development team.
 
 
 
 
 
 
 
app/nosql.py CHANGED
@@ -2,50 +2,81 @@
2
  MongoDB connection and database instance.
3
  Provides a singleton database connection for the application.
4
  """
5
- from motor.motor_asyncio import AsyncIOMotorClient
6
  from insightfy_utils.logging import get_logger
7
  from app.core.config import settings
8
 
9
  logger = get_logger(__name__)
10
 
11
- # MongoDB client and database instances
12
- client: AsyncIOMotorClient = None
13
- db = None
14
 
 
 
 
 
15
 
16
- async def connect_to_mongo():
17
- """
18
- Establish connection to MongoDB.
19
- Called during application startup.
20
- """
21
- global client, db
22
- try:
23
- logger.info("Connecting to MongoDB", extra={
24
- "uri": settings.MONGODB_URI,
25
- "database": settings.MONGODB_DB_NAME
26
- })
27
-
28
- client = AsyncIOMotorClient(settings.MONGODB_URI)
29
- db = client[settings.MONGODB_DB_NAME]
30
-
31
- # Test the connection
32
- await client.admin.command('ping')
33
 
34
- logger.info("Successfully connected to MongoDB", extra={
35
- "database": settings.MONGODB_DB_NAME
36
- })
37
- except Exception as e:
38
- logger.error("Failed to connect to MongoDB", exc_info=e)
39
- raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
 
42
  async def close_mongo_connection():
43
- """
44
- Close MongoDB connection.
45
- Called during application shutdown.
46
- """
47
- global client
48
- if client:
49
- logger.info("Closing MongoDB connection")
50
- client.close()
51
- logger.info("MongoDB connection closed")
 
2
  MongoDB connection and database instance.
3
  Provides a singleton database connection for the application.
4
  """
5
+ from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
6
  from insightfy_utils.logging import get_logger
7
  from app.core.config import settings
8
 
9
  logger = get_logger(__name__)
10
 
 
 
 
11
 
12
+ class DatabaseConnection:
13
+ """Singleton class to manage MongoDB connection"""
14
+ _client: AsyncIOMotorClient = None
15
+ _db: AsyncIOMotorDatabase = None
16
 
17
+ @classmethod
18
+ def get_database(cls) -> AsyncIOMotorDatabase:
19
+ """
20
+ Get the database instance.
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ Returns:
23
+ MongoDB database instance
24
+
25
+ Raises:
26
+ RuntimeError if database is not connected
27
+ """
28
+ if cls._db is None:
29
+ raise RuntimeError("Database not connected. Call connect_to_mongo() first.")
30
+ return cls._db
31
+
32
+ @classmethod
33
+ async def connect(cls):
34
+ """
35
+ Establish connection to MongoDB.
36
+ Called during application startup.
37
+ """
38
+ try:
39
+ logger.info("Connecting to MongoDB", extra={
40
+ "uri": settings.MONGODB_URI,
41
+ "database": settings.MONGODB_DB_NAME
42
+ })
43
+
44
+ cls._client = AsyncIOMotorClient(settings.MONGODB_URI)
45
+ cls._db = cls._client[settings.MONGODB_DB_NAME]
46
+
47
+ # Test the connection
48
+ await cls._client.admin.command('ping')
49
+
50
+ logger.info("Successfully connected to MongoDB", extra={
51
+ "database": settings.MONGODB_DB_NAME
52
+ })
53
+ except Exception as e:
54
+ logger.error("Failed to connect to MongoDB", exc_info=e)
55
+ raise
56
+
57
+ @classmethod
58
+ async def close(cls):
59
+ """
60
+ Close MongoDB connection.
61
+ Called during application shutdown.
62
+ """
63
+ if cls._client:
64
+ logger.info("Closing MongoDB connection")
65
+ cls._client.close()
66
+ logger.info("MongoDB connection closed")
67
+
68
+
69
+ # Public API
70
+ async def connect_to_mongo():
71
+ """Establish connection to MongoDB"""
72
+ await DatabaseConnection.connect()
73
 
74
 
75
  async def close_mongo_connection():
76
+ """Close MongoDB connection"""
77
+ await DatabaseConnection.close()
78
+
79
+
80
+ def get_database() -> AsyncIOMotorDatabase:
81
+ """Get the database instance"""
82
+ return DatabaseConnection.get_database()
 
 
app/schemas/merchant_schema.py CHANGED
@@ -39,8 +39,9 @@ class ContactModel(BaseModel):
39
  return v.lower().strip() if v else v
40
 
41
  @field_validator("pincode")
42
- def pincode_for_india(cls, v: str, values):
43
- country = (values.get("country") or "").lower()
 
44
  if country in ("india", "in"):
45
  if not PINCODE_INDIA_REGEX.match(v):
46
  raise ValueError("pincode must be a valid 6-digit Indian pincode")
 
39
  return v.lower().strip() if v else v
40
 
41
  @field_validator("pincode")
42
+ def pincode_for_india(cls, v: str, info):
43
+ # In Pydantic v2, we need to access data from info.data
44
+ country = (info.data.get("country") or "").lower() if info.data else ""
45
  if country in ("india", "in"):
46
  if not PINCODE_INDIA_REGEX.match(v):
47
  raise ValueError("pincode must be a valid 6-digit Indian pincode")
app/services/employee_service.py CHANGED
@@ -7,7 +7,7 @@ from fastapi import HTTPException, status
7
  from insightfy_utils.logging import get_logger
8
  import secrets
9
 
10
- from app.nosql import db
11
  from app.constants.collections import SCM_EMPLOYEES_COLLECTION
12
  from app.constants.employee_types import (
13
  Designation,
@@ -45,7 +45,7 @@ class EmployeeService:
45
  Employee document or None if not found
46
  """
47
  try:
48
- employee = await db[SCM_EMPLOYEES_COLLECTION].find_one({"user_id": user_id})
49
  return employee
50
  except Exception as e:
51
  logger.error(f"Error fetching employee {user_id}", exc_info=e)
@@ -66,7 +66,7 @@ class EmployeeService:
66
  True if unique, False otherwise
67
  """
68
  try:
69
- existing = await db[SCM_EMPLOYEES_COLLECTION].find_one(
70
  {"employee_code": code.upper()}
71
  )
72
  return existing is None
@@ -102,7 +102,7 @@ class EmployeeService:
102
  if exclude_user_id:
103
  query["user_id"] = {"$ne": exclude_user_id}
104
 
105
- existing = await db[SCM_EMPLOYEES_COLLECTION].find_one(query)
106
  return existing is None
107
  except Exception as e:
108
  logger.error(f"Error checking email uniqueness", exc_info=e)
@@ -136,7 +136,7 @@ class EmployeeService:
136
  if exclude_user_id:
137
  query["user_id"] = {"$ne": exclude_user_id}
138
 
139
- existing = await db[SCM_EMPLOYEES_COLLECTION].find_one(query)
140
  return existing is None
141
  except Exception as e:
142
  logger.error(f"Error checking phone uniqueness", exc_info=e)
@@ -279,7 +279,7 @@ class EmployeeService:
279
  if "id_docs" in employee_dict and employee_dict["id_docs"]:
280
  employee_dict["id_docs"] = [dict(doc) for doc in employee_dict["id_docs"]]
281
 
282
- await db[SCM_EMPLOYEES_COLLECTION].insert_one(employee_dict)
283
 
284
  logger.info(
285
  f"Created employee {user_id}",
@@ -391,7 +391,7 @@ class EmployeeService:
391
  update_data["id_docs"] = [dict(doc) for doc in update_data["id_docs"]]
392
 
393
  # Update in database
394
- result = await db[SCM_EMPLOYEES_COLLECTION].update_one(
395
  {"user_id": user_id},
396
  {"$set": update_data}
397
  )
@@ -457,7 +457,7 @@ class EmployeeService:
457
  HTTPException if not found
458
  """
459
  try:
460
- employee = await db[SCM_EMPLOYEES_COLLECTION].find_one(
461
  {"employee_code": employee_code.upper()}
462
  )
463
  if not employee:
@@ -511,7 +511,7 @@ class EmployeeService:
511
  query["region"] = region
512
 
513
  # Fetch employees
514
- cursor = db[SCM_EMPLOYEES_COLLECTION].find(query).skip(skip).limit(limit)
515
  employees = await cursor.to_list(length=limit)
516
 
517
  return [EmployeeResponse(**emp) for emp in employees]
@@ -547,7 +547,7 @@ class EmployeeService:
547
  )
548
 
549
  # Check for active direct reports
550
- active_reports = await db[SCM_EMPLOYEES_COLLECTION].find_one({
551
  "manager_id": user_id,
552
  "status": {"$in": [
553
  EmployeeStatus.ACTIVE.value,
@@ -564,7 +564,7 @@ class EmployeeService:
564
 
565
  try:
566
  # Soft delete - set status to terminated
567
- await db[SCM_EMPLOYEES_COLLECTION].update_one(
568
  {"user_id": user_id},
569
  {
570
  "$set": {
 
7
  from insightfy_utils.logging import get_logger
8
  import secrets
9
 
10
+ from app.nosql import get_database
11
  from app.constants.collections import SCM_EMPLOYEES_COLLECTION
12
  from app.constants.employee_types import (
13
  Designation,
 
45
  Employee document or None if not found
46
  """
47
  try:
48
+ employee = await get_database()[SCM_EMPLOYEES_COLLECTION].find_one({"user_id": user_id})
49
  return employee
50
  except Exception as e:
51
  logger.error(f"Error fetching employee {user_id}", exc_info=e)
 
66
  True if unique, False otherwise
67
  """
68
  try:
69
+ existing = await get_database()[SCM_EMPLOYEES_COLLECTION].find_one(
70
  {"employee_code": code.upper()}
71
  )
72
  return existing is None
 
102
  if exclude_user_id:
103
  query["user_id"] = {"$ne": exclude_user_id}
104
 
105
+ existing = await get_database()[SCM_EMPLOYEES_COLLECTION].find_one(query)
106
  return existing is None
107
  except Exception as e:
108
  logger.error(f"Error checking email uniqueness", exc_info=e)
 
136
  if exclude_user_id:
137
  query["user_id"] = {"$ne": exclude_user_id}
138
 
139
+ existing = await get_database()[SCM_EMPLOYEES_COLLECTION].find_one(query)
140
  return existing is None
141
  except Exception as e:
142
  logger.error(f"Error checking phone uniqueness", exc_info=e)
 
279
  if "id_docs" in employee_dict and employee_dict["id_docs"]:
280
  employee_dict["id_docs"] = [dict(doc) for doc in employee_dict["id_docs"]]
281
 
282
+ await get_database()[SCM_EMPLOYEES_COLLECTION].insert_one(employee_dict)
283
 
284
  logger.info(
285
  f"Created employee {user_id}",
 
391
  update_data["id_docs"] = [dict(doc) for doc in update_data["id_docs"]]
392
 
393
  # Update in database
394
+ result = await get_database()[SCM_EMPLOYEES_COLLECTION].update_one(
395
  {"user_id": user_id},
396
  {"$set": update_data}
397
  )
 
457
  HTTPException if not found
458
  """
459
  try:
460
+ employee = await get_database()[SCM_EMPLOYEES_COLLECTION].find_one(
461
  {"employee_code": employee_code.upper()}
462
  )
463
  if not employee:
 
511
  query["region"] = region
512
 
513
  # Fetch employees
514
+ cursor = get_database()[SCM_EMPLOYEES_COLLECTION].find(query).skip(skip).limit(limit)
515
  employees = await cursor.to_list(length=limit)
516
 
517
  return [EmployeeResponse(**emp) for emp in employees]
 
547
  )
548
 
549
  # Check for active direct reports
550
+ active_reports = await get_database()[SCM_EMPLOYEES_COLLECTION].find_one({
551
  "manager_id": user_id,
552
  "status": {"$in": [
553
  EmployeeStatus.ACTIVE.value,
 
564
 
565
  try:
566
  # Soft delete - set status to terminated
567
+ await get_database()[SCM_EMPLOYEES_COLLECTION].update_one(
568
  {"user_id": user_id},
569
  {
570
  "$set": {
app/services/merchant_service.py CHANGED
@@ -7,7 +7,7 @@ from fastapi import HTTPException, status
7
  from insightfy_utils.logging import get_logger
8
  import secrets
9
 
10
- from app.nosql import db
11
  from app.constants.collections import SCM_MERCHANTS_COLLECTION
12
  from app.constants.merchant_types import MerchantType, MerchantStatus
13
  from app.models.merchant_model import MerchantModel
@@ -42,7 +42,7 @@ class MerchantService:
42
  Merchant document or None if not found
43
  """
44
  try:
45
- merchant = await db[SCM_MERCHANTS_COLLECTION].find_one({"merchant_id": merchant_id})
46
  return merchant
47
  except Exception as e:
48
  logger.error(f"Error fetching merchant {merchant_id}", exc_info=e)
@@ -68,7 +68,7 @@ class MerchantService:
68
  if parent_id:
69
  query["parent_merchant_id"] = parent_id
70
 
71
- existing = await db[SCM_MERCHANTS_COLLECTION].find_one(query)
72
  return existing is None
73
  except Exception as e:
74
  logger.error(f"Error checking merchant code uniqueness", exc_info=e)
@@ -187,7 +187,7 @@ class MerchantService:
187
  merchant_dict["created_at"] = merchant_dict["created_at"].isoformat()
188
  merchant_dict["updated_at"] = merchant_dict["updated_at"].isoformat()
189
 
190
- await db[SCM_MERCHANTS_COLLECTION].insert_one(merchant_dict)
191
 
192
  logger.info(
193
  f"Created merchant {merchant_id}",
@@ -257,7 +257,7 @@ class MerchantService:
257
 
258
  try:
259
  # Update in database
260
- result = await db[SCM_MERCHANTS_COLLECTION].update_one(
261
  {"merchant_id": merchant_id},
262
  {"$set": update_data}
263
  )
@@ -336,7 +336,7 @@ class MerchantService:
336
  query["status"] = status_filter.value
337
 
338
  # Fetch merchants
339
- cursor = db[SCM_MERCHANTS_COLLECTION].find(query).skip(skip).limit(limit)
340
  merchants = await cursor.to_list(length=limit)
341
 
342
  return [MerchantResponse(**m) for m in merchants]
@@ -371,7 +371,7 @@ class MerchantService:
371
  )
372
 
373
  # Check for active children
374
- children = await db[SCM_MERCHANTS_COLLECTION].find_one({
375
  "parent_merchant_id": merchant_id,
376
  "status": {"$in": [MerchantStatus.ACTIVE.value, MerchantStatus.APPROVED.value]}
377
  })
@@ -384,7 +384,7 @@ class MerchantService:
384
 
385
  try:
386
  # Soft delete - set status to rejected
387
- await db[SCM_MERCHANTS_COLLECTION].update_one(
388
  {"merchant_id": merchant_id},
389
  {
390
  "$set": {
 
7
  from insightfy_utils.logging import get_logger
8
  import secrets
9
 
10
+ from app.nosql import get_database
11
  from app.constants.collections import SCM_MERCHANTS_COLLECTION
12
  from app.constants.merchant_types import MerchantType, MerchantStatus
13
  from app.models.merchant_model import MerchantModel
 
42
  Merchant document or None if not found
43
  """
44
  try:
45
+ merchant = await get_database()[SCM_MERCHANTS_COLLECTION].find_one({"merchant_id": merchant_id})
46
  return merchant
47
  except Exception as e:
48
  logger.error(f"Error fetching merchant {merchant_id}", exc_info=e)
 
68
  if parent_id:
69
  query["parent_merchant_id"] = parent_id
70
 
71
+ existing = await get_database()[SCM_MERCHANTS_COLLECTION].find_one(query)
72
  return existing is None
73
  except Exception as e:
74
  logger.error(f"Error checking merchant code uniqueness", exc_info=e)
 
187
  merchant_dict["created_at"] = merchant_dict["created_at"].isoformat()
188
  merchant_dict["updated_at"] = merchant_dict["updated_at"].isoformat()
189
 
190
+ await get_database()[SCM_MERCHANTS_COLLECTION].insert_one(merchant_dict)
191
 
192
  logger.info(
193
  f"Created merchant {merchant_id}",
 
257
 
258
  try:
259
  # Update in database
260
+ result = await get_database()[SCM_MERCHANTS_COLLECTION].update_one(
261
  {"merchant_id": merchant_id},
262
  {"$set": update_data}
263
  )
 
336
  query["status"] = status_filter.value
337
 
338
  # Fetch merchants
339
+ cursor = get_database()[SCM_MERCHANTS_COLLECTION].find(query).skip(skip).limit(limit)
340
  merchants = await cursor.to_list(length=limit)
341
 
342
  return [MerchantResponse(**m) for m in merchants]
 
371
  )
372
 
373
  # Check for active children
374
+ children = await get_database()[SCM_MERCHANTS_COLLECTION].find_one({
375
  "parent_merchant_id": merchant_id,
376
  "status": {"$in": [MerchantStatus.ACTIVE.value, MerchantStatus.APPROVED.value]}
377
  })
 
384
 
385
  try:
386
  # Soft delete - set status to rejected
387
+ await get_database()[SCM_MERCHANTS_COLLECTION].update_one(
388
  {"merchant_id": merchant_id},
389
  {
390
  "$set": {
app/services/sales_order_service.py CHANGED
@@ -8,7 +8,7 @@ from fastapi import HTTPException, status
8
  from insightfy_utils.logging import get_logger
9
  import secrets
10
 
11
- from app.nosql import db
12
  from app.constants.collections import SCM_SALES_ORDERS_COLLECTION
13
  from app.schemas.sales_order_schema import (
14
  SalesOrderCreate,
@@ -130,7 +130,7 @@ class SalesOrderService:
130
  order_number = payload.order_number or generate_order_number(payload.branch_id)
131
 
132
  # Check order number uniqueness
133
- existing = await db[SCM_SALES_ORDERS_COLLECTION].find_one(
134
  {"order_number": order_number}
135
  )
136
  if existing:
@@ -238,7 +238,7 @@ class SalesOrderService:
238
  }
239
 
240
  try:
241
- await db[SCM_SALES_ORDERS_COLLECTION].insert_one(sales_order)
242
 
243
  logger.info(
244
  f"Created sales order {sales_order_id}",
@@ -262,7 +262,7 @@ class SalesOrderService:
262
  @staticmethod
263
  async def get_sales_order(sales_order_id: str) -> SalesOrderResponse:
264
  """Get sales order by ID."""
265
- sales_order = await db[SCM_SALES_ORDERS_COLLECTION].find_one(
266
  {"sales_order_id": sales_order_id}
267
  )
268
 
@@ -311,10 +311,10 @@ class SalesOrderService:
311
 
312
  try:
313
  # Get total count
314
- total = await db[SCM_SALES_ORDERS_COLLECTION].count_documents(query)
315
 
316
  # Get documents
317
- cursor = db[SCM_SALES_ORDERS_COLLECTION].find(query).sort(sort_field, sort_order).skip(skip).limit(limit)
318
  documents = await cursor.to_list(length=limit)
319
 
320
  return {
@@ -340,7 +340,7 @@ class SalesOrderService:
340
  ) -> SalesOrderResponse:
341
  """Update a sales order."""
342
  # Check exists
343
- existing = await db[SCM_SALES_ORDERS_COLLECTION].find_one(
344
  {"sales_order_id": sales_order_id}
345
  )
346
  if not existing:
@@ -377,7 +377,7 @@ class SalesOrderService:
377
  update_data["updated_at"] = datetime.utcnow().isoformat()
378
 
379
  try:
380
- await db[SCM_SALES_ORDERS_COLLECTION].update_one(
381
  {"sales_order_id": sales_order_id},
382
  {"$set": update_data}
383
  )
@@ -409,7 +409,7 @@ class SalesOrderService:
409
  request: InvoiceGenerateRequest
410
  ) -> InvoiceGenerateResponse:
411
  """Generate invoice for a sales order."""
412
- sales_order = await db[SCM_SALES_ORDERS_COLLECTION].find_one(
413
  {"sales_order_id": sales_order_id}
414
  )
415
 
@@ -430,7 +430,7 @@ class SalesOrderService:
430
  invoice_pdf_url = f"https://cdn.example.com/invoices/{invoice_number}.pdf"
431
 
432
  try:
433
- await db[SCM_SALES_ORDERS_COLLECTION].update_one(
434
  {"sales_order_id": sales_order_id},
435
  {
436
  "$set": {
@@ -475,17 +475,17 @@ class SalesOrderService:
475
  {"$match": {"merchant_id": merchant_id}},
476
  {"$group": {"_id": None, "total": {"$sum": "$summary.grand_total"}}}
477
  ]
478
- total_sales_result = await db[SCM_SALES_ORDERS_COLLECTION].aggregate(pipeline_total).to_list(1)
479
  total_sales = total_sales_result[0]["total"] if total_sales_result else 0
480
 
481
  # Pending orders
482
- pending_count = await db[SCM_SALES_ORDERS_COLLECTION].count_documents({
483
  "merchant_id": merchant_id,
484
  "status": {"$in": [SalesOrderStatus.CONFIRMED.value, SalesOrderStatus.PROCESSING.value]}
485
  })
486
 
487
  # Fulfilled orders
488
- fulfilled_count = await db[SCM_SALES_ORDERS_COLLECTION].count_documents({
489
  "merchant_id": merchant_id,
490
  "status": SalesOrderStatus.FULFILLED.value
491
  })
@@ -500,7 +500,7 @@ class SalesOrderService:
500
  },
501
  {"$group": {"_id": None, "revenue": {"$sum": "$summary.grand_total"}}}
502
  ]
503
- revenue_result = await db[SCM_SALES_ORDERS_COLLECTION].aggregate(pipeline_revenue).to_list(1)
504
  revenue = revenue_result[0]["revenue"] if revenue_result else 0
505
 
506
  return SalesWidgetsResponse(
 
8
  from insightfy_utils.logging import get_logger
9
  import secrets
10
 
11
+ from app.nosql import get_database
12
  from app.constants.collections import SCM_SALES_ORDERS_COLLECTION
13
  from app.schemas.sales_order_schema import (
14
  SalesOrderCreate,
 
130
  order_number = payload.order_number or generate_order_number(payload.branch_id)
131
 
132
  # Check order number uniqueness
133
+ existing = await get_database()[SCM_SALES_ORDERS_COLLECTION].find_one(
134
  {"order_number": order_number}
135
  )
136
  if existing:
 
238
  }
239
 
240
  try:
241
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].insert_one(sales_order)
242
 
243
  logger.info(
244
  f"Created sales order {sales_order_id}",
 
262
  @staticmethod
263
  async def get_sales_order(sales_order_id: str) -> SalesOrderResponse:
264
  """Get sales order by ID."""
265
+ sales_order = await get_database()[SCM_SALES_ORDERS_COLLECTION].find_one(
266
  {"sales_order_id": sales_order_id}
267
  )
268
 
 
311
 
312
  try:
313
  # Get total count
314
+ total = await get_database()[SCM_SALES_ORDERS_COLLECTION].count_documents(query)
315
 
316
  # Get documents
317
+ cursor = get_database()[SCM_SALES_ORDERS_COLLECTION].find(query).sort(sort_field, sort_order).skip(skip).limit(limit)
318
  documents = await cursor.to_list(length=limit)
319
 
320
  return {
 
340
  ) -> SalesOrderResponse:
341
  """Update a sales order."""
342
  # Check exists
343
+ existing = await get_database()[SCM_SALES_ORDERS_COLLECTION].find_one(
344
  {"sales_order_id": sales_order_id}
345
  )
346
  if not existing:
 
377
  update_data["updated_at"] = datetime.utcnow().isoformat()
378
 
379
  try:
380
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].update_one(
381
  {"sales_order_id": sales_order_id},
382
  {"$set": update_data}
383
  )
 
409
  request: InvoiceGenerateRequest
410
  ) -> InvoiceGenerateResponse:
411
  """Generate invoice for a sales order."""
412
+ sales_order = await get_database()[SCM_SALES_ORDERS_COLLECTION].find_one(
413
  {"sales_order_id": sales_order_id}
414
  )
415
 
 
430
  invoice_pdf_url = f"https://cdn.example.com/invoices/{invoice_number}.pdf"
431
 
432
  try:
433
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].update_one(
434
  {"sales_order_id": sales_order_id},
435
  {
436
  "$set": {
 
475
  {"$match": {"merchant_id": merchant_id}},
476
  {"$group": {"_id": None, "total": {"$sum": "$summary.grand_total"}}}
477
  ]
478
+ total_sales_result = await get_database()[SCM_SALES_ORDERS_COLLECTION].aggregate(pipeline_total).to_list(1)
479
  total_sales = total_sales_result[0]["total"] if total_sales_result else 0
480
 
481
  # Pending orders
482
+ pending_count = await get_database()[SCM_SALES_ORDERS_COLLECTION].count_documents({
483
  "merchant_id": merchant_id,
484
  "status": {"$in": [SalesOrderStatus.CONFIRMED.value, SalesOrderStatus.PROCESSING.value]}
485
  })
486
 
487
  # Fulfilled orders
488
+ fulfilled_count = await get_database()[SCM_SALES_ORDERS_COLLECTION].count_documents({
489
  "merchant_id": merchant_id,
490
  "status": SalesOrderStatus.FULFILLED.value
491
  })
 
500
  },
501
  {"$group": {"_id": None, "revenue": {"$sum": "$summary.grand_total"}}}
502
  ]
503
+ revenue_result = await get_database()[SCM_SALES_ORDERS_COLLECTION].aggregate(pipeline_revenue).to_list(1)
504
  revenue = revenue_result[0]["revenue"] if revenue_result else 0
505
 
506
  return SalesWidgetsResponse(
app/utils/db_init.py CHANGED
@@ -3,7 +3,7 @@ Database initialization utilities.
3
  Creates indexes and sets up collections for the SCM microservice.
4
  """
5
  from insightfy_utils.logging import get_logger
6
- from app.nosql import db
7
  from app.constants.collections import (
8
  SCM_MERCHANTS_COLLECTION,
9
  SCM_EMPLOYEES_COLLECTION,
@@ -26,58 +26,58 @@ async def create_indexes():
26
  logger.info("Creating database indexes")
27
 
28
  # Merchants collection indexes
29
- await db[SCM_MERCHANTS_COLLECTION].create_index("merchant_id", unique=True)
30
- await db[SCM_MERCHANTS_COLLECTION].create_index("business_name", unique=True)
31
- await db[SCM_MERCHANTS_COLLECTION].create_index("merchant_type")
32
- await db[SCM_MERCHANTS_COLLECTION].create_index("contact_email")
33
  logger.info(f"Created indexes for {SCM_MERCHANTS_COLLECTION}")
34
 
35
  # Employees collection indexes
36
- await db[SCM_EMPLOYEES_COLLECTION].create_index("associate_id", unique=True)
37
- await db[SCM_EMPLOYEES_COLLECTION].create_index("email", unique=True)
38
- await db[SCM_EMPLOYEES_COLLECTION].create_index("mobile", unique=True)
39
- await db[SCM_EMPLOYEES_COLLECTION].create_index("merchant_id")
40
- await db[SCM_EMPLOYEES_COLLECTION].create_index([
41
  ("merchant_id", 1),
42
  ("role_id", 1)
43
  ])
44
  logger.info(f"Created indexes for {SCM_EMPLOYEES_COLLECTION}")
45
 
46
  # Roles collection indexes
47
- await db[SCM_ROLES_COLLECTION].create_index([
48
  ("merchant_id", 1),
49
  ("role_id", 1)
50
  ], unique=True)
51
- await db[SCM_ROLES_COLLECTION].create_index("merchant_id")
52
  logger.info(f"Created indexes for {SCM_ROLES_COLLECTION}")
53
 
54
  # Auth logs collection indexes
55
- await db[SCM_AUTH_LOGS_COLLECTION].create_index([
56
  ("merchant_id", 1),
57
  ("timestamp", -1)
58
  ])
59
- await db[SCM_AUTH_LOGS_COLLECTION].create_index([
60
  ("associate_id", 1),
61
  ("timestamp", -1)
62
  ])
63
- await db[SCM_AUTH_LOGS_COLLECTION].create_index("event_type")
64
 
65
  # TTL index for 90-day retention (7776000 seconds)
66
- await db[SCM_AUTH_LOGS_COLLECTION].create_index(
67
  "timestamp",
68
  expireAfterSeconds=7776000
69
  )
70
  logger.info(f"Created indexes for {SCM_AUTH_LOGS_COLLECTION}")
71
 
72
  # Sales orders collection indexes
73
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("sales_order_id", unique=True)
74
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("order_number", unique=True)
75
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("merchant_id")
76
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("branch_id")
77
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("status")
78
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("order_date")
79
- await db[SCM_SALES_ORDERS_COLLECTION].create_index("customer.customer_id")
80
- await db[SCM_SALES_ORDERS_COLLECTION].create_index([
81
  ("merchant_id", 1),
82
  ("order_date", -1)
83
  ])
@@ -97,11 +97,11 @@ async def drop_indexes():
97
  try:
98
  logger.warning("Dropping all database indexes")
99
 
100
- await db[SCM_MERCHANTS_COLLECTION].drop_indexes()
101
- await db[SCM_EMPLOYEES_COLLECTION].drop_indexes()
102
- await db[SCM_ROLES_COLLECTION].drop_indexes()
103
- await db[SCM_AUTH_LOGS_COLLECTION].drop_indexes()
104
- await db[SCM_SALES_ORDERS_COLLECTION].drop_indexes()
105
 
106
  logger.info("All database indexes dropped")
107
 
 
3
  Creates indexes and sets up collections for the SCM microservice.
4
  """
5
  from insightfy_utils.logging import get_logger
6
+ from app.nosql import get_database
7
  from app.constants.collections import (
8
  SCM_MERCHANTS_COLLECTION,
9
  SCM_EMPLOYEES_COLLECTION,
 
26
  logger.info("Creating database indexes")
27
 
28
  # Merchants collection indexes
29
+ await get_database()[SCM_MERCHANTS_COLLECTION].create_index("merchant_id", unique=True)
30
+ await get_database()[SCM_MERCHANTS_COLLECTION].create_index("business_name", unique=True)
31
+ await get_database()[SCM_MERCHANTS_COLLECTION].create_index("merchant_type")
32
+ await get_database()[SCM_MERCHANTS_COLLECTION].create_index("contact_email")
33
  logger.info(f"Created indexes for {SCM_MERCHANTS_COLLECTION}")
34
 
35
  # Employees collection indexes
36
+ await get_database()[SCM_EMPLOYEES_COLLECTION].create_index("associate_id", unique=True)
37
+ await get_database()[SCM_EMPLOYEES_COLLECTION].create_index("email", unique=True)
38
+ await get_database()[SCM_EMPLOYEES_COLLECTION].create_index("mobile", unique=True)
39
+ await get_database()[SCM_EMPLOYEES_COLLECTION].create_index("merchant_id")
40
+ await get_database()[SCM_EMPLOYEES_COLLECTION].create_index([
41
  ("merchant_id", 1),
42
  ("role_id", 1)
43
  ])
44
  logger.info(f"Created indexes for {SCM_EMPLOYEES_COLLECTION}")
45
 
46
  # Roles collection indexes
47
+ await get_database()[SCM_ROLES_COLLECTION].create_index([
48
  ("merchant_id", 1),
49
  ("role_id", 1)
50
  ], unique=True)
51
+ await get_database()[SCM_ROLES_COLLECTION].create_index("merchant_id")
52
  logger.info(f"Created indexes for {SCM_ROLES_COLLECTION}")
53
 
54
  # Auth logs collection indexes
55
+ await get_database()[SCM_AUTH_LOGS_COLLECTION].create_index([
56
  ("merchant_id", 1),
57
  ("timestamp", -1)
58
  ])
59
+ await get_database()[SCM_AUTH_LOGS_COLLECTION].create_index([
60
  ("associate_id", 1),
61
  ("timestamp", -1)
62
  ])
63
+ await get_database()[SCM_AUTH_LOGS_COLLECTION].create_index("event_type")
64
 
65
  # TTL index for 90-day retention (7776000 seconds)
66
+ await get_database()[SCM_AUTH_LOGS_COLLECTION].create_index(
67
  "timestamp",
68
  expireAfterSeconds=7776000
69
  )
70
  logger.info(f"Created indexes for {SCM_AUTH_LOGS_COLLECTION}")
71
 
72
  # Sales orders collection indexes
73
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("sales_order_id", unique=True)
74
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("order_number", unique=True)
75
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("merchant_id")
76
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("branch_id")
77
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("status")
78
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("order_date")
79
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index("customer.customer_id")
80
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].create_index([
81
  ("merchant_id", 1),
82
  ("order_date", -1)
83
  ])
 
97
  try:
98
  logger.warning("Dropping all database indexes")
99
 
100
+ await get_database()[SCM_MERCHANTS_COLLECTION].drop_indexes()
101
+ await get_database()[SCM_EMPLOYEES_COLLECTION].drop_indexes()
102
+ await get_database()[SCM_ROLES_COLLECTION].drop_indexes()
103
+ await get_database()[SCM_AUTH_LOGS_COLLECTION].drop_indexes()
104
+ await get_database()[SCM_SALES_ORDERS_COLLECTION].drop_indexes()
105
 
106
  logger.info("All database indexes dropped")
107