@shyntech-proximity/inventories 1.0.0
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.
- package/README.md +135 -0
- package/package.json +19 -0
- package/src/index.js +536 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Inventory Backend API Documentation
|
|
2
|
+
|
|
3
|
+
**Base URL:** `/api` (example)
|
|
4
|
+
**Auth:** Currently relies on device headers (`x-device-sig`, `x-ws-token`) and IP-based identification for proximity features.
|
|
5
|
+
|
|
6
|
+
**Models:**
|
|
7
|
+
|
|
8
|
+
* **Vendor**: Contains vendor_info and inventory arrays.
|
|
9
|
+
* **DeviceScan**: Records devices’ last scan (SSID info, timestamp, etc.).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Common Headers
|
|
14
|
+
|
|
15
|
+
| Header | Description | Required |
|
|
16
|
+
| -------------- | -------------------------- | -------------------------------------- |
|
|
17
|
+
| `x-device-sig` | Unique device signature | Optional (used in proximity detection) |
|
|
18
|
+
| `x-ws-token` | Session or workspace token | Optional |
|
|
19
|
+
| `public_ip` | Client IP address | Optional |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. Inventory Endpoints
|
|
24
|
+
|
|
25
|
+
### 2.1 Get Inventory by Item ID
|
|
26
|
+
|
|
27
|
+
**GET** `/inventory/:itemId`
|
|
28
|
+
|
|
29
|
+
| Parameter | Type | Required | Description |
|
|
30
|
+
| --------- | ------ | -------- | ------------------------ |
|
|
31
|
+
| `itemId` | String | Yes | Unique inventory item ID |
|
|
32
|
+
|
|
33
|
+
**Response:**
|
|
34
|
+
|
|
35
|
+
| Field | Type | Description |
|
|
36
|
+
| -------- | ------ | ------------------------------------------- |
|
|
37
|
+
| `vendor` | Object | Vendor info containing `vendor_info` fields |
|
|
38
|
+
| `item` | Object | Inventory item matching `itemId` |
|
|
39
|
+
|
|
40
|
+
**Error Codes:**
|
|
41
|
+
|
|
42
|
+
| Status | Message |
|
|
43
|
+
| ------ | ------------------------ |
|
|
44
|
+
| 404 | Inventory item not found |
|
|
45
|
+
| 500 | Internal server error |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 2.2 Get Inventory by Proximity
|
|
50
|
+
|
|
51
|
+
**GET** `/inventory`
|
|
52
|
+
|
|
53
|
+
**Headers used:** `x-device-sig`, `public_ip`, `x-ws-token`
|
|
54
|
+
|
|
55
|
+
**Response:**
|
|
56
|
+
|
|
57
|
+
* List of vendors matching the last device scan SSIDs
|
|
58
|
+
|
|
59
|
+
| Field | Type | Description |
|
|
60
|
+
| ------------- | ------ | ----------------------- |
|
|
61
|
+
| `vendor_id` | String | Vendor identifier |
|
|
62
|
+
| `vendor_name` | String | Name of vendor |
|
|
63
|
+
| `address` | String | Vendor address |
|
|
64
|
+
| `inventory` | Array | List of inventory items |
|
|
65
|
+
|
|
66
|
+
**Error Codes:**
|
|
67
|
+
|
|
68
|
+
| Status | Message |
|
|
69
|
+
| ------ | ------------------------------------------ |
|
|
70
|
+
| 404 | No recent proximity data found for this IP |
|
|
71
|
+
| 500 | Internal server error |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### 2.3 Search Inventory
|
|
76
|
+
|
|
77
|
+
**GET** `/search?query=<query>`
|
|
78
|
+
|
|
79
|
+
| Query Parameter | Type | Required | Description |
|
|
80
|
+
| --------------- | ------ | -------- | -------------------------------------------------------------- |
|
|
81
|
+
| `query` | String | Yes | Fuzzy search term for inventory, vendor name, address, or tags |
|
|
82
|
+
|
|
83
|
+
**Response:**
|
|
84
|
+
|
|
85
|
+
| Field | Type | Description |
|
|
86
|
+
| --------- | ------ | ------------------------------ |
|
|
87
|
+
| `query` | String | Search term |
|
|
88
|
+
| `results` | Array | List of vendors matching query |
|
|
89
|
+
|
|
90
|
+
**Error Codes:**
|
|
91
|
+
|
|
92
|
+
| Status | Message |
|
|
93
|
+
| ------ | --------------------------- |
|
|
94
|
+
| 400 | Query parameter is required |
|
|
95
|
+
| 500 | Internal server error |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 2.4 Create / Update / Delete Inventory
|
|
100
|
+
|
|
101
|
+
| Method | Endpoint | Request Body | Response | Notes |
|
|
102
|
+
| ------ | ---------------------------------- | ----------------------------------------- | ----------------------------- | ---------------------------- |
|
|
103
|
+
| POST | `/vendor/:vendorId/inventory` | `{ item_id, name, category, price, ... }` | 200, added item | Add single inventory item |
|
|
104
|
+
| POST | `/vendor/:vendorId/inventory/bulk` | `{ items: [{ item_id, name, ...}, ...] }` | 200, items added | Add multiple inventory items |
|
|
105
|
+
| PUT | `/inventory/:itemId` | `{ field: value, ... }` | 200, updated item | Update fields of single item |
|
|
106
|
+
| PUT | `/inventory/:vendorId/bulk` | `[ { item_id, field: value }, ... ]` | 200, summary of updated items | Bulk update inventory items |
|
|
107
|
+
| DELETE | `/inventory/:itemId` | None | 200, deleted item | Delete single item |
|
|
108
|
+
| DELETE | `/inventory` | `{ itemIds: [id1, id2, ...] }` | 200, `deletedCount` | Bulk delete items |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 3. Error Response Format
|
|
113
|
+
|
|
114
|
+
| Field | Type | Description |
|
|
115
|
+
| --------- | ------ | ------------------------------------------------ |
|
|
116
|
+
| `error` | String | Error message |
|
|
117
|
+
| `message` | String | Optional descriptive message for success/failure |
|
|
118
|
+
|
|
119
|
+
**Example:**
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"error": "Vendor not found"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 4. Notes / Guidelines
|
|
130
|
+
|
|
131
|
+
1. **Proximity detection:** `GET /inventory` uses device signature, token, or IP as a fallback to retrieve recent Wi-Fi scans.
|
|
132
|
+
2. **Bulk operations:** Partial success is possible; the response includes per-item update status.
|
|
133
|
+
3. **Consistency:** All update operations return the new state of the object after modification.
|
|
134
|
+
4. **Pagination:** Not implemented yet for search; consider adding `limit` and `offset` query params for large datasets.
|
|
135
|
+
5. **Logging:** Server logs device signatures and SSIDs for audit/debugging.
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shyntech-proximity/inventories",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "src/index.js",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"src"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "echo 'no build'",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"@shyntech:registry": "https://gitlab.com/api/v4/projects/78314798/packages/npm/"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.9.3"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { DeviceScan } from "../models/DeviceScan.js";
|
|
3
|
+
import { Vendor } from "../models/Vendor.js";
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
router.proximityCache = {};
|
|
7
|
+
/**
|
|
8
|
+
* Browser client calls this endpoint.
|
|
9
|
+
* Server finds last scan from same IP and returns matching vendor inventories.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// đź§ Fallback Matching Logic
|
|
13
|
+
function findProximityKey(req) {
|
|
14
|
+
const ip = req.headers["public_ip"];
|
|
15
|
+
const deviceSig = req.headers["x-device-sig"];
|
|
16
|
+
const token = req.headers["x-ws-token"];
|
|
17
|
+
console.log("Device: ", deviceSig);
|
|
18
|
+
if (deviceSig) return deviceSig;
|
|
19
|
+
if (ip) return ip;
|
|
20
|
+
if (token) return token;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
router.get("/inventory/:itemId", async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const itemId = req.params.itemId;
|
|
32
|
+
|
|
33
|
+
// Find the vendor that contains the inventory item
|
|
34
|
+
const vendor = await Vendor.findOne(
|
|
35
|
+
{ "inventory.item_id": itemId },
|
|
36
|
+
{ "inventory.$": 1, vendor_info: 1 } // project only the matching inventory item and vendor_info
|
|
37
|
+
).lean();
|
|
38
|
+
|
|
39
|
+
if (!vendor || !vendor.inventory || vendor.inventory.length === 0) {
|
|
40
|
+
return res.status(404).json({ error: "Inventory item not found" });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
res.json({
|
|
44
|
+
vendor: vendor.vendor_info,
|
|
45
|
+
item: vendor.inventory[0]
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(error);
|
|
49
|
+
res.status(500).json({ error: "Internal server error" });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
router.get("/search", async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const { query } = req.query;
|
|
60
|
+
|
|
61
|
+
if (!query || query.trim() === "") {
|
|
62
|
+
return res.status(400).json({ error: "Query parameter is required" });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const regex = new RegExp(query, "i"); // case-insensitive fuzzy match
|
|
66
|
+
|
|
67
|
+
// Search inventory items
|
|
68
|
+
const vendors = await Vendor.find({
|
|
69
|
+
$or: [
|
|
70
|
+
{ "vendor_info.name": regex },
|
|
71
|
+
{ "vendor_info.address": regex },
|
|
72
|
+
{ "inventory.name": regex },
|
|
73
|
+
{ "inventory.category": regex },
|
|
74
|
+
{ "inventory.tags": regex }
|
|
75
|
+
]
|
|
76
|
+
}).lean();
|
|
77
|
+
|
|
78
|
+
const results = vendors.map(vendor => {
|
|
79
|
+
// Filter inventory items in vendor that match the query
|
|
80
|
+
const matchingItems = vendor.inventory.filter(item =>
|
|
81
|
+
regex.test(item.name) ||
|
|
82
|
+
regex.test(item.category) ||
|
|
83
|
+
item.tags.some(tag => regex.test(tag))
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
vendor_id: vendor.vendor_info.vendor_id,
|
|
88
|
+
vendor_name: vendor.vendor_info.name,
|
|
89
|
+
address: vendor.vendor_info.address,
|
|
90
|
+
inventory: matchingItems
|
|
91
|
+
};
|
|
92
|
+
}).filter(vendor => vendor.inventory.length > 0 || regex.test(vendor.vendor_name) || regex.test(vendor.address));
|
|
93
|
+
|
|
94
|
+
res.json({
|
|
95
|
+
query,
|
|
96
|
+
results
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(error);
|
|
100
|
+
res.status(500).json({ error: "Internal server error" });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
router.get("/inventory", async (req, res) => {
|
|
109
|
+
const deviceId = findProximityKey(req)|| req.socket.remoteAddress;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const lastScan = await DeviceScan.findOne({ device_id: deviceId })
|
|
113
|
+
.sort({ timestamp: -1 })
|
|
114
|
+
.lean();
|
|
115
|
+
if (!lastScan) {
|
|
116
|
+
return res.status(404).json({ message: "No recent proximity data found for this IP" });
|
|
117
|
+
}
|
|
118
|
+
const ssids = lastScan?.detected_wifi.map(w => w.ssid);
|
|
119
|
+
console.log(ssids)
|
|
120
|
+
const vendors = await Vendor.find({ ssid: { $in: ssids } }).lean();
|
|
121
|
+
console.log(vendors);
|
|
122
|
+
res.json(vendors);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(error);
|
|
125
|
+
res.status(500).json({ error: "Internal server error" });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// =========================
|
|
133
|
+
// Get vendor by phoneNumber
|
|
134
|
+
// =========================
|
|
135
|
+
router.get("/vendor", async (req, res) => {
|
|
136
|
+
try {
|
|
137
|
+
const { phoneNumber } = req.query;
|
|
138
|
+
console.log(phoneNumber);
|
|
139
|
+
if (!phoneNumber) {
|
|
140
|
+
return res
|
|
141
|
+
.status(400)
|
|
142
|
+
.json({ error: "phoneNumber query parameter is required" });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Find vendor using vendor_info.phoneNumber
|
|
146
|
+
const vendor = await Vendor.findOne({ "phoneNumber": phoneNumber })
|
|
147
|
+
.lean();
|
|
148
|
+
|
|
149
|
+
if (!vendor) {
|
|
150
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
res.json(vendor);
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error("Error fetching vendor by phoneNumber:", error);
|
|
157
|
+
res.status(500).json({ error: "Internal server error" });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
// Get inventory for a specific vendor
|
|
167
|
+
router.get("/vendor/:vendorId/inventory", async (req, res) => {
|
|
168
|
+
try {
|
|
169
|
+
const vendorId = req.params.vendorId;
|
|
170
|
+
// Find the vendor by vendor_info.vendor_id
|
|
171
|
+
const vendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId })
|
|
172
|
+
.select("inventory") // only return inventory and vendor_info
|
|
173
|
+
.lean();
|
|
174
|
+
|
|
175
|
+
if (!vendor) {
|
|
176
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
res.json(vendor.inventory);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(error);
|
|
182
|
+
res.status(500).json({ error: "Internal server error" });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
// STORE MANAGEMENT STARTS HERE
|
|
193
|
+
router.get("/vendor/:vendorId", async (req, res) => {
|
|
194
|
+
try {
|
|
195
|
+
const vendorId = req.params.vendorId;
|
|
196
|
+
// vendor_id is inside vendor_info
|
|
197
|
+
const vendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId }).lean();
|
|
198
|
+
|
|
199
|
+
if (!vendor) {
|
|
200
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
res.json(vendor);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(error);
|
|
206
|
+
res.status(500).json({ error: "Internal server error" });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
// POST /vendor
|
|
214
|
+
// body: { vendor_id, name, address, contact, gps_location, opening_hours, inventory }
|
|
215
|
+
router.post("/vendor", async (req, res) => {
|
|
216
|
+
try {
|
|
217
|
+
const vendorData = req.body;
|
|
218
|
+
|
|
219
|
+
// Check if vendor already exists
|
|
220
|
+
const existing = await Vendor.findOne({ vendor_id: vendorData.vendor_id });
|
|
221
|
+
if (existing) {
|
|
222
|
+
return res.status(400).json({ error: "Vendor with this ID already exists" });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const newVendor = await Vendor.create(vendorData);
|
|
226
|
+
res.status(201).json({
|
|
227
|
+
message: "Vendor created successfully",
|
|
228
|
+
vendor: newVendor,
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(error);
|
|
232
|
+
res.status(500).json({ error: "Internal server error" });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// body: fields to update: { name, address, contact, gps_location, opening_hours, inventory }
|
|
241
|
+
// PUT /vendor/:vendorId
|
|
242
|
+
router.put("/vendor/:vendorId", async (req, res) => {
|
|
243
|
+
try {
|
|
244
|
+
const { vendorId } = req.params;
|
|
245
|
+
const updateData = req.body;
|
|
246
|
+
|
|
247
|
+
// Check if a vendor with this vendor_id exists
|
|
248
|
+
const existingVendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId });
|
|
249
|
+
if (!existingVendor) {
|
|
250
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Flatten nested updateData to update specific fields (so we don’t overwrite everything)
|
|
254
|
+
const setFields = {};
|
|
255
|
+
for (const [key, value] of Object.entries(updateData)) {
|
|
256
|
+
setFields[`vendor_info.${key}`] = value;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Update vendor info
|
|
260
|
+
const updatedVendor = await Vendor.findOneAndUpdate(
|
|
261
|
+
{ "vendor_info.vendor_id": vendorId },
|
|
262
|
+
{ $set: setFields },
|
|
263
|
+
{ new: true }
|
|
264
|
+
).lean();
|
|
265
|
+
|
|
266
|
+
res.json({
|
|
267
|
+
message: "Vendor updated successfully",
|
|
268
|
+
vendor: updatedVendor,
|
|
269
|
+
});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error("Error updating vendor:", error);
|
|
272
|
+
res.status(500).json({ error: "Internal server error" });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
// DELETE /vendor/:vendorId
|
|
281
|
+
router.delete("/vendor/:vendorId", async (req, res) => {
|
|
282
|
+
try {
|
|
283
|
+
const { vendorId } = req.params;
|
|
284
|
+
|
|
285
|
+
const deletedVendor = await Vendor.findOneAndDelete({ vendor_id: vendorId }).lean();
|
|
286
|
+
|
|
287
|
+
if (!deletedVendor) {
|
|
288
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
res.json({
|
|
292
|
+
message: "Vendor deleted successfully",
|
|
293
|
+
vendor: deletedVendor,
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error(error);
|
|
297
|
+
res.status(500).json({ error: "Internal server error" });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
//INVENTORY STARTS HERE
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
// Add a single inventory item to a vendor
|
|
310
|
+
router.post("/vendor/:vendorId/inventory", async (req, res) => {
|
|
311
|
+
try {
|
|
312
|
+
const vendorId = req.params.vendorId;
|
|
313
|
+
const newItem = req.body; // e.g., { item_id, name, category, price, ... }
|
|
314
|
+
|
|
315
|
+
const updatedVendor = await Vendor.findOneAndUpdate(
|
|
316
|
+
{ vendor_id: vendorId },
|
|
317
|
+
{ $push: { inventory: newItem } },
|
|
318
|
+
{ new: true, projection: { inventory: 1, vendor_info: 1 } }
|
|
319
|
+
).lean();
|
|
320
|
+
|
|
321
|
+
if (!updatedVendor) {
|
|
322
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
res.json({
|
|
326
|
+
message: "Inventory item added successfully",
|
|
327
|
+
vendor: updatedVendor.vendor_info,
|
|
328
|
+
item: newItem,
|
|
329
|
+
});
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error(error);
|
|
332
|
+
res.status(500).json({ error: "Internal server error" });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
// Add multiple inventory items to a vendor
|
|
343
|
+
router.post("/vendor/:vendorId/inventory/bulk", async (req, res) => {
|
|
344
|
+
try {
|
|
345
|
+
const vendorId = req.params.vendorId;
|
|
346
|
+
const newItems = req.body.items; // e.g., [{ item_id, name, ... }, {...}]
|
|
347
|
+
|
|
348
|
+
if (!Array.isArray(newItems) || newItems.length === 0) {
|
|
349
|
+
return res.status(400).json({ error: "Items array is required" });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const updatedVendor = await Vendor.findOneAndUpdate(
|
|
353
|
+
{ vendor_id: vendorId },
|
|
354
|
+
{ $push: { inventory: { $each: newItems } } },
|
|
355
|
+
{ new: true, projection: { inventory: 1, vendor_info: 1 } }
|
|
356
|
+
).lean();
|
|
357
|
+
|
|
358
|
+
if (!updatedVendor) {
|
|
359
|
+
return res.status(404).json({ error: "Vendor not found" });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
res.json({
|
|
363
|
+
message: `${newItems.length} inventory items added successfully`,
|
|
364
|
+
vendor: updatedVendor.vendor_info,
|
|
365
|
+
items: newItems,
|
|
366
|
+
});
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error(error);
|
|
369
|
+
res.status(500).json({ error: "Internal server error" });
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
router.put("/inventory/:itemId", async (req, res) => {
|
|
379
|
+
try {
|
|
380
|
+
const itemId = req.params.itemId;
|
|
381
|
+
const updateData = req.body; // e.g., { price: 300, quantity_available: 50 }
|
|
382
|
+
|
|
383
|
+
// Find the vendor containing the item and update the matching inventory element
|
|
384
|
+
const result = await Vendor.findOneAndUpdate(
|
|
385
|
+
{ "inventory.item_id": itemId },
|
|
386
|
+
{ $set: Object.fromEntries(
|
|
387
|
+
Object.entries(updateData).map(([key, value]) => [`inventory.$.${key}`, value])
|
|
388
|
+
)
|
|
389
|
+
},
|
|
390
|
+
{ new: true, projection: { "inventory.$": 1, vendor_info: 1 } } // return updated item
|
|
391
|
+
).lean();
|
|
392
|
+
|
|
393
|
+
if (!result || !result.inventory || result.inventory.length === 0) {
|
|
394
|
+
return res.status(404).json({ error: "Inventory item not found" });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
res.json({
|
|
398
|
+
message: "Inventory item updated successfully",
|
|
399
|
+
vendor: result.vendor_info,
|
|
400
|
+
item: result.inventory[0],
|
|
401
|
+
});
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error(error);
|
|
404
|
+
res.status(500).json({ error: "Internal server error" });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
router.put("/inventory/:vendorId/bulk", async (req, res) => {
|
|
415
|
+
try {
|
|
416
|
+
const { vendorId } = req.params;
|
|
417
|
+
const itemsToUpdate = req.body;
|
|
418
|
+
|
|
419
|
+
if (!Array.isArray(itemsToUpdate) || itemsToUpdate.length === 0) {
|
|
420
|
+
return res.status(400).json({ error: "Items array is required" });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Validate the vendor first
|
|
424
|
+
const vendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId });
|
|
425
|
+
if (!vendor) {
|
|
426
|
+
return res.status(404).json({ error: `Vendor ${vendorId} not found` });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Perform updates in parallel
|
|
430
|
+
const updateResults = await Promise.all(
|
|
431
|
+
itemsToUpdate.map(async (item) => {
|
|
432
|
+
const { item_id, ...updates } = item;
|
|
433
|
+
const updateQuery = {};
|
|
434
|
+
|
|
435
|
+
// Dynamically build the update fields
|
|
436
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
437
|
+
updateQuery[`inventory.$.${key}`] = value;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const result = await Vendor.updateOne(
|
|
441
|
+
{ "vendor_info.vendor_id": vendorId, "inventory.item_id": item_id },
|
|
442
|
+
{ $set: updateQuery }
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
return { item_id, matched: result.matchedCount, modified: result.modifiedCount };
|
|
446
|
+
})
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Fetch the fresh vendor document after updates
|
|
450
|
+
const updatedVendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId }).lean();
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
console.log(updateResults)
|
|
454
|
+
|
|
455
|
+
res.json({
|
|
456
|
+
message: `${updateResults.filter(r => r.modified > 0).length} of ${itemsToUpdate.length} items updated successfully`,
|
|
457
|
+
updateResults,
|
|
458
|
+
vendor: updatedVendor?.vendor_info,
|
|
459
|
+
inventory: updatedVendor?.inventory,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error("Bulk update error:", error);
|
|
464
|
+
res.status(500).json({ error: "Internal server error" });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
// DELETE /inventory/:itemId
|
|
474
|
+
router.delete("/inventory/:itemId", async (req, res) => {
|
|
475
|
+
try {
|
|
476
|
+
const { itemId } = req.params;
|
|
477
|
+
|
|
478
|
+
const result = await Vendor.findOneAndUpdate(
|
|
479
|
+
{ "inventory.item_id": itemId },
|
|
480
|
+
{ $pull: { inventory: { item_id: itemId } } },
|
|
481
|
+
{ new: true, projection: { vendor_info: 1 } }
|
|
482
|
+
).lean();
|
|
483
|
+
|
|
484
|
+
if (!result) {
|
|
485
|
+
return res.status(404).json({ error: "Inventory item not found" });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
res.json({
|
|
489
|
+
message: "Inventory item deleted successfully",
|
|
490
|
+
vendor: result.vendor_info,
|
|
491
|
+
});
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error(error);
|
|
494
|
+
res.status(500).json({ error: "Internal server error" });
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
// DELETE /inventory
|
|
504
|
+
// body: { itemIds: ["INV001", "INV002", ...] }
|
|
505
|
+
router.delete("/inventory", async (req, res) => {
|
|
506
|
+
try {
|
|
507
|
+
const { itemIds } = req.body;
|
|
508
|
+
|
|
509
|
+
if (!Array.isArray(itemIds) || itemIds.length === 0) {
|
|
510
|
+
return res.status(400).json({ error: "itemIds array is required" });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const result = await Vendor.updateMany(
|
|
514
|
+
{ "inventory.item_id": { $in: itemIds } },
|
|
515
|
+
{ $pull: { inventory: { item_id: { $in: itemIds } } } }
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
res.json({
|
|
519
|
+
message: "Inventory items deleted successfully",
|
|
520
|
+
deletedCount: result.modifiedCount,
|
|
521
|
+
});
|
|
522
|
+
} catch (error) {
|
|
523
|
+
console.error(error);
|
|
524
|
+
res.status(500).json({ error: "Internal server error" });
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
export default router;
|
|
535
|
+
|
|
536
|
+
|