web-apihelper-quart 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
Binary file
@@ -0,0 +1,389 @@
1
+ # app.py
2
+ import asyncio
3
+ import pyodbc
4
+ from quart import Quart, request, jsonify
5
+ from quart_cors import cors
6
+
7
+ app = Quart(__name__)
8
+ app = cors(app, allow_origin="*")
9
+ def get_connection_string():
10
+ server = r'.\SQLEXPRESS'
11
+ database = 'Session1'
12
+ installed = pyodbc.drivers()
13
+ preferred = [
14
+ '{ODBC Driver 18 for SQL Server}',
15
+ '{ODBC Driver 17 for SQL Server}',
16
+ '{SQL Server Native Client 11.0}',
17
+ '{SQL Server}'
18
+ ]
19
+ driver = next((d for d in preferred if d in installed or d.strip('{}') in installed), '{SQL Server}')
20
+ return f"Driver={driver};Server={server};Database={database};Trusted_Connection=yes;Encrypt=no;"
21
+
22
+ DB_CONNECTION_STRING = get_connection_string()
23
+
24
+
25
+ async def get_db_connection():
26
+ loop = asyncio.get_running_loop()
27
+ return await loop.run_in_executor(None, lambda: pyodbc.connect(DB_CONNECTION_STRING))
28
+
29
+
30
+ def model_to_dict(cursor, row):
31
+ d = {}
32
+ for idx, col in enumerate(cursor.description):
33
+ d[col[0]] = row[idx]
34
+ return d
35
+
36
+
37
+ async def fetch_all(query, params=None):
38
+ conn = await get_db_connection()
39
+ cursor = conn.cursor()
40
+ try:
41
+ cursor.execute(query, params or ())
42
+ rows = cursor.fetchall()
43
+ return [model_to_dict(cursor, row) for row in rows]
44
+ finally:
45
+ cursor.close()
46
+ conn.close()
47
+
48
+
49
+ async def fetch_one(query, params=None):
50
+ conn = await get_db_connection()
51
+ cursor = conn.cursor()
52
+ try:
53
+ cursor.execute(query, params or ())
54
+ row = cursor.fetchone()
55
+ return model_to_dict(cursor, row) if row else None
56
+ finally:
57
+ cursor.close()
58
+ conn.close()
59
+
60
+
61
+ async def execute_query(query, params=None, fetch_last_id=False):
62
+ conn = await get_db_connection()
63
+ cursor = conn.cursor()
64
+ try:
65
+ cursor.execute(query, params or ())
66
+ if fetch_last_id:
67
+ cursor.execute("SELECT @@IDENTITY AS last_id;")
68
+ last_id = cursor.fetchone()[0]
69
+ conn.commit()
70
+ return last_id
71
+ conn.commit()
72
+ return True
73
+ except Exception as e:
74
+ conn.rollback()
75
+ print(f"Database Error: {e}")
76
+ return None
77
+ finally:
78
+ cursor.close()
79
+ conn.close()
80
+
81
+
82
+ async def generate_asset_sn(department_id, asset_group_id):
83
+ dd = str(department_id).zfill(2)
84
+ gg = str(asset_group_id).zfill(2)
85
+
86
+ prefix = f"{dd}/{gg}/"
87
+
88
+ query = """
89
+ SELECT TOP 1 AssetSN
90
+ FROM Assets
91
+ WHERE AssetSN LIKE ?
92
+ ORDER BY AssetSN DESC \
93
+ """
94
+ last_sn_row = await fetch_one(query, (f"{prefix}%",))
95
+
96
+ if last_sn_row and last_sn_row['AssetSN']:
97
+ last_number_part = last_sn_row['AssetSN'].split('/')[-1]
98
+ next_number = int(last_number_part) + 1
99
+ else:
100
+ next_number = 1
101
+
102
+ nnnn = str(next_number).zfill(4)
103
+
104
+ return f"{prefix}{nnnn}"
105
+
106
+ @app.route("/departments", methods=["GET"])
107
+ async def get_departments():
108
+ departments = await fetch_all("SELECT ID, Name FROM Departments ORDER BY Name")
109
+ return jsonify(departments)
110
+
111
+
112
+ @app.route("/asset-groups", methods=["GET"])
113
+ async def get_asset_groups():
114
+ asset_groups = await fetch_all("SELECT ID, Name FROM AssetGroups ORDER BY Name")
115
+ return jsonify(asset_groups)
116
+
117
+
118
+ @app.route("/locations", methods=["GET"])
119
+ async def get_locations():
120
+ department_id = request.args.get('department_id', type=int)
121
+ if department_id:
122
+ query = """
123
+ SELECT l.ID, l.Name
124
+ FROM Locations l
125
+ JOIN DepartmentLocations dl ON l.ID = dl.LocationID
126
+ WHERE dl.DepartmentID = ?
127
+ ORDER BY l.Name
128
+ """
129
+ locations = await fetch_all(query, (department_id,))
130
+ else:
131
+ query = "SELECT ID, Name FROM Locations ORDER BY Name"
132
+ locations = await fetch_all(query)
133
+ return jsonify(locations)
134
+
135
+
136
+ @app.route("/employees", methods=["GET"])
137
+ async def get_employees():
138
+ query = "SELECT ID, FirstName + ' ' + LastName AS FullName FROM Employees ORDER BY FullName"
139
+ employees = await fetch_all(query)
140
+ return jsonify(employees)
141
+
142
+
143
+ @app.route("/assets", methods=["GET"])
144
+ async def get_assets():
145
+ base_query = """
146
+ SELECT a.ID, \
147
+ a.AssetSN, \
148
+ a.AssetName, \
149
+ d.Name as DepartmentName
150
+ FROM Assets a
151
+ JOIN DepartmentLocations dl ON a.DepartmentLocationID = dl.ID
152
+ JOIN Departments d ON dl.DepartmentID = d.ID
153
+ JOIN AssetGroups ag ON a.AssetGroupID = ag.ID \
154
+ """
155
+
156
+ conditions = []
157
+ params = []
158
+
159
+ search = request.args.get('search')
160
+ if search:
161
+ conditions.append("(a.AssetName LIKE ? OR a.AssetSN LIKE ?)")
162
+ params.extend([f"%{search}%", f"%{search}%"])
163
+
164
+ department_id = request.args.get('department_id', type=int)
165
+ if department_id:
166
+ conditions.append("d.ID = ?")
167
+ params.append(department_id)
168
+
169
+ asset_group_id = request.args.get('asset_group_id', type=int)
170
+ if asset_group_id:
171
+ conditions.append("ag.ID = ?")
172
+ params.append(asset_group_id)
173
+
174
+ start_date = request.args.get('warranty_start_date')
175
+ end_date = request.args.get('warranty_end_date')
176
+ if start_date and end_date:
177
+ conditions.append("a.WarrantyDate BETWEEN ? AND ?")
178
+ params.extend([start_date, end_date])
179
+
180
+ if conditions:
181
+ base_query += " WHERE " + " AND ".join(conditions)
182
+
183
+ base_query += " ORDER BY a.ID DESC"
184
+
185
+ assets = await fetch_all(base_query, tuple(params))
186
+ return jsonify(assets)
187
+
188
+
189
+ @app.route("/assets/<int:asset_id>", methods=["GET"])
190
+ async def get_asset_details(asset_id):
191
+ query = """
192
+ SELECT a.ID, \
193
+ a.AssetName, \
194
+ a.AssetSN, \
195
+ a.Description, \
196
+ a.WarrantyDate, \
197
+ dl.DepartmentID, \
198
+ dl.LocationID, \
199
+ a.AssetGroupID, \
200
+ a.EmployeeID AS AccountablePartyID
201
+ FROM Assets a
202
+ JOIN DepartmentLocations dl ON a.DepartmentLocationID = dl.ID
203
+ WHERE a.ID = ? \
204
+ """
205
+ asset = await fetch_one(query, (asset_id,))
206
+
207
+ if not asset:
208
+ return jsonify({"error": "Asset not found"}), 404
209
+
210
+ photos_query = "SELECT ID, AssetPhoto FROM AssetPhotos WHERE AssetID = ?"
211
+ photos = await fetch_all(photos_query, (asset_id,))
212
+ asset['Photos'] = photos
213
+
214
+ return jsonify(asset)
215
+
216
+ @app.route("/assets", methods=["POST"])
217
+ async def create_asset():
218
+ data = await request.get_json()
219
+ if not data:
220
+ return jsonify({"error": "No data provided"}), 400
221
+
222
+ required_fields = ['AssetName', 'DepartmentID', 'LocationID', 'AssetGroupID', 'EmployeeID', 'WarrantyDate']
223
+
224
+ missing = [f for f in required_fields if f not in data or data[f] is None]
225
+ if missing:
226
+ print(f"Missing fields: {missing}")
227
+ return jsonify({"error": f"Missing fields: {', '.join(missing)}"}), 400
228
+
229
+ dl_query = "SELECT ID FROM DepartmentLocations WHERE DepartmentID = ? AND LocationID = ?"
230
+ dl_row = await fetch_one(dl_query, (data['DepartmentID'], data['LocationID']))
231
+ if not dl_row:
232
+ return jsonify({"error": "This location is not assigned to the selected department"}), 400
233
+ department_location_id = dl_row['ID']
234
+
235
+ dup_check = "SELECT ID FROM Assets WHERE AssetName = ? AND DepartmentLocationID = ?"
236
+ if await fetch_one(dup_check, (data['AssetName'], department_location_id)):
237
+ return jsonify({"error": "Asset with this name already exists at this location"}), 409
238
+
239
+ asset_sn = await generate_asset_sn(data['DepartmentID'], data['AssetGroupID'])
240
+
241
+ insert_query = """
242
+ INSERT INTO Assets (AssetSN, AssetName, DepartmentLocationID, EmployeeID, AssetGroupID, Description, \
243
+ WarrantyDate)
244
+ VALUES (?, ?, ?, ?, ?, ?, ?) \
245
+ """
246
+ params = (
247
+ asset_sn,
248
+ data['AssetName'],
249
+ department_location_id,
250
+ data['EmployeeID'],
251
+ data['AssetGroupID'],
252
+ data.get('Description', ''),
253
+ data['WarrantyDate']
254
+ )
255
+
256
+ new_id = await execute_query(insert_query, params, fetch_last_id=True)
257
+ if new_id:
258
+ return jsonify({"message": "Created", "id": new_id, "assetSN": asset_sn}), 201
259
+ return jsonify({"error": "Database insert failed"}), 500
260
+
261
+ @app.route("/assets/<int:asset_id>", methods=["PUT"])
262
+ async def update_asset(asset_id):
263
+ data = await request.get_json()
264
+ if not data:
265
+ return jsonify({"error": "Invalid input"}), 400
266
+
267
+ existing_asset = await fetch_one("SELECT * FROM Assets WHERE ID = ?", (asset_id,))
268
+ if not existing_asset:
269
+ return jsonify({"error": "Asset not found"}), 404
270
+
271
+ dl_query = "SELECT ID FROM DepartmentLocations WHERE DepartmentID = ? AND LocationID = ?"
272
+ dl_row = await fetch_one(dl_query, (data['DepartmentID'], data['LocationID']))
273
+ if not dl_row:
274
+ return jsonify({"error": "Invalid Department/Location combination"}), 400
275
+ department_location_id = dl_row['ID']
276
+
277
+ dup_check_query = "SELECT ID FROM Assets WHERE AssetName = ? AND DepartmentLocationID = ? AND ID != ?"
278
+ duplicate = await fetch_one(dup_check_query, (data['AssetName'], department_location_id, asset_id))
279
+ if duplicate:
280
+ return jsonify({"error": "An asset with this name already exists at this location"}), 409
281
+
282
+ update_query = """
283
+ UPDATE Assets \
284
+ SET AssetName = ?, \
285
+ DepartmentLocationID = ?, \
286
+ EmployeeID = ?, \
287
+ AssetGroupID = ?, \
288
+ Description = ?, \
289
+ WarrantyDate = ?
290
+ WHERE ID = ? \
291
+ """
292
+ params = (
293
+ data['AssetName'],
294
+ department_location_id,
295
+ data['EmployeeID'],
296
+ data['AssetGroupID'],
297
+ data.get('Description', ''),
298
+ data['WarrantyDate'],
299
+ asset_id
300
+ )
301
+
302
+ result = await execute_query(update_query, params)
303
+
304
+ if result:
305
+ return jsonify({"message": "Asset updated successfully"}), 200
306
+ else:
307
+ return jsonify({"error": "Failed to update asset"}), 500
308
+
309
+
310
+ @app.route("/assets/<int:asset_id>/transfer", methods=["POST"])
311
+ async def transfer_asset(asset_id):
312
+ data = await request.get_json()
313
+ if not data or 'DestinationDepartmentID' not in data or 'DestinationLocationID' not in data:
314
+ return jsonify({"error": "Destination Department and Location are required"}), 400
315
+
316
+ current_asset_query = """
317
+ SELECT a.AssetSN, a.AssetGroupID, a.DepartmentLocationID
318
+ FROM Assets a
319
+ WHERE a.ID = ? \
320
+ """
321
+ current_asset = await fetch_one(current_asset_query, (asset_id,))
322
+ if not current_asset:
323
+ return jsonify({"error": "Asset not found"}), 404
324
+
325
+ from_dl_id = current_asset['DepartmentLocationID']
326
+ from_asset_sn = current_asset['AssetSN']
327
+ asset_group_id = current_asset['AssetGroupID']
328
+
329
+ to_department_id = data['DestinationDepartmentID']
330
+ to_location_id = data['DestinationLocationID']
331
+
332
+ to_dl_query = "SELECT ID FROM DepartmentLocations WHERE DepartmentID = ? AND LocationID = ?"
333
+ to_dl_row = await fetch_one(to_dl_query, (to_department_id, to_location_id))
334
+ if not to_dl_row:
335
+ return jsonify({"error": "Invalid destination Department/Location combination"}), 400
336
+ to_dl_id = to_dl_row['ID']
337
+
338
+ new_asset_sn = await generate_asset_sn(to_department_id, asset_group_id)
339
+
340
+ log_query = """
341
+ INSERT INTO AssetTransferLogs (AssetID, TransferDate, FromDepartmentLocationID, ToDepartmentLocationID, \
342
+ FromAssetSN, ToAssetSN)
343
+ VALUES (?, GETDATE(), ?, ?, ?, ?) \
344
+ """
345
+ await execute_query(log_query, (asset_id, from_dl_id, to_dl_id, from_asset_sn, new_asset_sn))
346
+
347
+ update_asset_query = """
348
+ UPDATE Assets
349
+ SET DepartmentLocationID = ?, \
350
+ AssetSN = ?
351
+ WHERE ID = ? \
352
+ """
353
+ await execute_query(update_asset_query, (to_dl_id, new_asset_sn, asset_id))
354
+
355
+ return jsonify({
356
+ "message": "Asset transferred successfully",
357
+ "newAssetSN": new_asset_sn
358
+ }), 200
359
+
360
+
361
+
362
+ @app.route("/assets/<int:asset_id>/history", methods=["GET"])
363
+ async def get_transfer_history(asset_id):
364
+ query = """
365
+ SELECT atl.TransferDate, \
366
+ d_from.Name AS OldDepartment, \
367
+ atl.FromAssetSN AS OldAssetSN, \
368
+ d_to.Name AS NewDepartment, \
369
+ atl.ToAssetSN AS NewAssetSN
370
+ FROM AssetTransferLogs atl
371
+ -- Join for 'From' Department
372
+ JOIN DepartmentLocations dl_from ON atl.FromDepartmentLocationID = dl_from.ID
373
+ JOIN Departments d_from ON dl_from.DepartmentID = d_from.ID
374
+ -- Join for 'To' Department
375
+ JOIN DepartmentLocations dl_to ON atl.ToDepartmentLocationID = dl_to.ID
376
+ JOIN Departments d_to ON dl_to.DepartmentID = d_to.ID
377
+ WHERE atl.AssetID = ?
378
+ ORDER BY atl.TransferDate DESC \
379
+ """
380
+ history = await fetch_all(query, (asset_id,))
381
+
382
+ if not history:
383
+ return jsonify([]), 200
384
+
385
+ return jsonify(history)
386
+
387
+
388
+ if __name__ == "__main__":
389
+ app.run(host="0.0.0.0", port=5000, debug=True)