yapi-seller-service 0.0.0-alpha1

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.
Files changed (3) hide show
  1. package/README.md +26 -0
  2. package/index.js +1621 -0
  3. package/package.json +1 -0
package/index.js ADDED
@@ -0,0 +1,1621 @@
1
+ // index.js
2
+ /**
3
+ * @file index.js
4
+ * @description A simulated service layer for managing seller operations within the Yapi platform.
5
+ * This package provides functionalities for seller profile management, product listing and management,
6
+ * order viewing, and basic inventory updates, simulating interactions with a backend API.
7
+ * All data storage and API interactions are simulated in-memory using standard JavaScript features.
8
+ * This code is intended to mimic the structure and complexity of a real service package,
9
+ * including validation, error handling, and asynchronous operations.
10
+ */
11
+
12
+ // --- Simulated Data Storage (In-memory) ---
13
+ // These variables simulate a backend database for a specific seller.
14
+ // In a real application, these would be API calls to a server.
15
+
16
+ /** @type {SellerProfile|null} */
17
+ let _sellerProfile = null;
18
+
19
+ /** @type {Product[]} */
20
+ let _products = [];
21
+
22
+ /** @type {Order[]} */
23
+ let _orders = [];
24
+
25
+ // --- Constants and Configuration (Simulated) ---
26
+
27
+ const SIMULATED_API_MIN_DELAY_MS = 100;
28
+ const SIMULATED_API_MAX_DELAY_MS = 500;
29
+ const SIMULATED_API_ERROR_CHANCE = 0.1; // 10% chance of simulated network/connection error
30
+
31
+ const PRODUCT_STATUSES = ['draft', 'active', 'archived'];
32
+ const ORDER_STATUSES = ['pending', 'processing', 'shipped', 'delivered', 'cancelled', 'returned'];
33
+
34
+ // --- Data Structure Classes (Simulated Models) ---
35
+
36
+ /**
37
+ * Represents a Seller Profile on the Yapi platform.
38
+ * @typedef {object} SellerProfile
39
+ * @property {string} id - Unique identifier for the seller.
40
+ * @property {string} name - The seller's name or business name.
41
+ * @property {string} email - Seller's contact email.
42
+ * @property {string} phone - Seller's contact phone number.
43
+ * @property {string} address - Seller's primary address.
44
+ * @property {string} [description] - Optional description of the seller or business.
45
+ * @property {Date} createdAt - Timestamp when the profile was created.
46
+ * @property {Date} updatedAt - Timestamp when the profile was last updated.
47
+ */
48
+ class SellerProfile {
49
+ /**
50
+ * @param {object} data - Initial data for the seller profile.
51
+ * @param {string} data.id - Unique identifier.
52
+ * @param {string} data.name - Seller name.
53
+ * @param {string} data.email - Seller email.
54
+ * @param {string} data.phone - Seller phone.
55
+ * @param {string} data.address - Seller address.
56
+ * @param {string} [data.description] - Optional description.
57
+ * @param {Date} [data.createdAt=new Date()] - Creation timestamp.
58
+ * @param {Date} [data.updatedAt=new Date()] - Update timestamp.
59
+ */
60
+ constructor({ id, name, email, phone, address, description = '', createdAt = new Date(), updatedAt = new Date() }) {
61
+ if (!id || !name || !email || !phone || !address) {
62
+ throw new Error("SellerProfile requires id, name, email, phone, and address.");
63
+ }
64
+ this.id = id;
65
+ this.name = name;
66
+ this.email = email;
67
+ this.phone = phone;
68
+ this.address = address;
69
+ this.description = description;
70
+ this.createdAt = new Date(createdAt); // Ensure Date object
71
+ this.updatedAt = new Date(updatedAt); // Ensure Date object
72
+ }
73
+
74
+ /**
75
+ * Updates profile data.
76
+ * @param {object} updateData - Data to update.
77
+ */
78
+ update(updateData) {
79
+ Object.assign(this, updateData);
80
+ this.updatedAt = new Date();
81
+ }
82
+
83
+ /**
84
+ * Returns a plain object representation.
85
+ * @returns {object}
86
+ */
87
+ toObject() {
88
+ return {
89
+ id: this.id,
90
+ name: this.name,
91
+ email: this.email,
92
+ phone: this.phone,
93
+ address: this.address,
94
+ description: this.description,
95
+ createdAt: this.createdAt,
96
+ updatedAt: this.updatedAt,
97
+ };
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Represents a Product listed by the seller.
103
+ * @typedef {object} Product
104
+ * @property {string} id - Unique identifier for the product.
105
+ * @property {string} sellerId - The ID of the seller who owns this product.
106
+ * @property {string} title - Product title.
107
+ * @property {string} [description] - Optional product description.
108
+ * @property {number} price - Product price (e.g., in currency units).
109
+ * @property {number} stock - Current stock quantity.
110
+ * @property {string} [imageUrl] - URL of the product image.
111
+ * @property {string} [category] - Product category.
112
+ * @property {string} status - Product status ('draft', 'active', 'archived').
113
+ * @property {Date} createdAt - Timestamp when the product was created.
114
+ * @property {Date} updatedAt - Timestamp when the product was last updated.
115
+ */
116
+ class Product {
117
+ /**
118
+ * @param {object} data - Initial data for the product.
119
+ * @param {string} data.id - Unique identifier.
120
+ * @param {string} data.sellerId - Seller ID.
121
+ * @param {string} data.title - Product title.
122
+ * @param {number} data.price - Product price.
123
+ * @param {number} data.stock - Stock quantity.
124
+ * @param {string} [data.description] - Description.
125
+ * @param {string} [data.imageUrl] - Image URL.
126
+ * @param {string} [data.category] - Category.
127
+ * @param {string} [data.status='draft'] - Product status.
128
+ * @param {Date} [data.createdAt=new Date()] - Creation timestamp.
129
+ * @param {Date} [data.updatedAt=new Date()] - Update timestamp.
130
+ */
131
+ constructor({
132
+ id,
133
+ sellerId,
134
+ title,
135
+ description = '',
136
+ price,
137
+ stock,
138
+ imageUrl = '',
139
+ category = 'uncategorized',
140
+ status = 'draft',
141
+ createdAt = new Date(),
142
+ updatedAt = new Date()
143
+ }) {
144
+ if (!id || !sellerId || !title || price === undefined || stock === undefined) {
145
+ throw new Error("Product requires id, sellerId, title, price, and stock.");
146
+ }
147
+ if (!PRODUCT_STATUSES.includes(status)) {
148
+ throw new Error(`Invalid product status: ${status}. Must be one of ${PRODUCT_STATUSES.join(', ')}.`);
149
+ }
150
+
151
+ this.id = id;
152
+ this.sellerId = sellerId;
153
+ this.title = title;
154
+ this.description = description;
155
+ this.price = price;
156
+ this.stock = stock;
157
+ this.imageUrl = imageUrl;
158
+ this.category = category;
159
+ this.status = status;
160
+ this.createdAt = new Date(createdAt);
161
+ this.updatedAt = new Date(updatedAt);
162
+ }
163
+
164
+ /**
165
+ * Updates product data.
166
+ * @param {object} updateData - Data to update.
167
+ */
168
+ update(updateData) {
169
+ // Basic validation for status update
170
+ if (updateData.status !== undefined && !PRODUCT_STATUSES.includes(updateData.status)) {
171
+ throw new Error(`Invalid product status update: ${updateData.status}. Must be one of ${PRODUCT_STATUSES.join(', ')}.`);
172
+ }
173
+ Object.assign(this, updateData);
174
+ this.updatedAt = new Date();
175
+ }
176
+
177
+ /**
178
+ * Returns a plain object representation.
179
+ * @returns {object}
180
+ */
181
+ toObject() {
182
+ return {
183
+ id: this.id,
184
+ sellerId: this.sellerId,
185
+ title: this.title,
186
+ description: this.description,
187
+ price: this.price,
188
+ stock: this.stock,
189
+ imageUrl: this.imageUrl,
190
+ category: this.category,
191
+ status: this.status,
192
+ createdAt: this.createdAt,
193
+ updatedAt: this.updatedAt,
194
+ };
195
+ }
196
+ }
197
+
198
+
199
+ /**
200
+ * Represents an Order placed by a buyer for the seller's products.
201
+ * Note: This simplified model represents an order *item* or a single product order.
202
+ * A real system would likely have an Order header and multiple OrderItems.
203
+ * @typedef {object} Order
204
+ * @property {string} id - Unique identifier for the order item.
205
+ * @property {string} orderId - Identifier for the overall order (if multiple items).
206
+ * @property {string} sellerId - The ID of the seller fulfilling this item.
207
+ * @property {string} productId - The ID of the product ordered.
208
+ * @property {string} productName - The name of the product ordered (snapshot at time of order).
209
+ * @property {number} quantity - Quantity of the product ordered.
210
+ * @property {number} itemPrice - Price of one unit of the product at the time of order.
211
+ * @property {number} totalPrice - Total price for this item (quantity * itemPrice).
212
+ * @property {string} buyerId - The ID of the buyer.
213
+ * @property {object} buyerInfo - Snapshot of buyer's info (e.g., { name, address }).
214
+ * @property {string} status - Order item status ('pending', 'processing', 'shipped', 'delivered', 'cancelled', 'returned').
215
+ * @property {Date} orderDate - Timestamp when the order was placed.
216
+ * @property {Date} [updatedAt] - Timestamp when the order item status was last updated.
217
+ */
218
+ class Order {
219
+ /**
220
+ * @param {object} data - Initial data for the order item.
221
+ * @param {string} data.id - Unique identifier.
222
+ * @param {string} data.orderId - Overall order ID.
223
+ * @param {string} data.sellerId - Seller ID.
224
+ * @param {string} data.productId - Product ID.
225
+ * @param {string} data.productName - Product name snapshot.
226
+ * @param {number} data.quantity - Quantity.
227
+ * @param {number} data.itemPrice - Price per item at order time.
228
+ * @param {object} data.buyerInfo - Buyer info snapshot.
229
+ * @param {string} [data.status='pending'] - Order status.
230
+ * @param {Date} [data.orderDate=new Date()] - Order placement timestamp.
231
+ * @param {Date} [data.updatedAt=new Date()] - Update timestamp.
232
+ */
233
+ constructor({
234
+ id,
235
+ orderId,
236
+ sellerId,
237
+ productId,
238
+ productName,
239
+ quantity,
240
+ itemPrice,
241
+ buyerInfo,
242
+ status = 'pending',
243
+ orderDate = new Date(),
244
+ updatedAt = new Date()
245
+ }) {
246
+ if (!id || !orderId || !sellerId || !productId || !productName || quantity === undefined || itemPrice === undefined || !buyerInfo) {
247
+ throw new Error("Order requires id, orderId, sellerId, productId, productName, quantity, itemPrice, and buyerInfo.");
248
+ }
249
+ if (!ORDER_STATUSES.includes(status)) {
250
+ throw new Error(`Invalid order status: ${status}. Must be one of ${ORDER_STATUSES.join(', ')}.`);
251
+ }
252
+
253
+ this.id = id;
254
+ this.orderId = orderId;
255
+ this.sellerId = sellerId;
256
+ this.productId = productId;
257
+ this.productName = productName;
258
+ this.quantity = quantity;
259
+ this.itemPrice = itemPrice;
260
+ this.totalPrice = quantity * itemPrice;
261
+ this.buyerInfo = buyerInfo; // Simple object copy
262
+ this.status = status;
263
+ this.orderDate = new Date(orderDate);
264
+ this.updatedAt = new Date(updatedAt);
265
+ }
266
+
267
+ /**
268
+ * Updates the order item status.
269
+ * @param {string} newStatus - The new status for the order item.
270
+ */
271
+ updateStatus(newStatus) {
272
+ if (!ORDER_STATUSES.includes(newStatus)) {
273
+ throw new Error(`Invalid order status update: ${newStatus}. Must be one of ${ORDER_STATUSES.join(', ')}.`);
274
+ }
275
+ this.status = newStatus;
276
+ this.updatedAt = new Date();
277
+ }
278
+
279
+ /**
280
+ * Returns a plain object representation.
281
+ * @returns {object}
282
+ */
283
+ toObject() {
284
+ return {
285
+ id: this.id,
286
+ orderId: this.orderId,
287
+ sellerId: this.sellerId,
288
+ productId: this.productId,
289
+ productName: this.productName,
290
+ quantity: this.quantity,
291
+ itemPrice: this.itemPrice,
292
+ totalPrice: this.totalPrice,
293
+ buyerInfo: { ...this.buyerInfo }, // Return a copy
294
+ status: this.status,
295
+ orderDate: this.orderDate,
296
+ updatedAt: this.updatedAt,
297
+ };
298
+ }
299
+ }
300
+
301
+
302
+ // --- Utility Functions (Helper Methods) ---
303
+
304
+ /**
305
+ * Generates a simple unique ID string.
306
+ * Not a true UUID, but sufficient for simulation.
307
+ * @returns {string} A unique identifier string.
308
+ * @private
309
+ */
310
+ function _generateUniqueId() {
311
+ // Simple simulation of ID generation
312
+ // Combination of random string and timestamp for uniqueness likelihood
313
+ return 'yapi_' + Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
314
+ }
315
+
316
+ /**
317
+ * Basic validation for a non-empty string.
318
+ * @param {*} value - The value to validate.
319
+ * @param {string} name - The name of the parameter being validated (for error messages).
320
+ * @param {boolean} [allowEmptyString=false] - If true, an empty string is considered valid.
321
+ * @private
322
+ * @throws {Error} If validation fails.
323
+ */
324
+ function _validateString(value, name, allowEmptyString = false) {
325
+ if (value === null || value === undefined) {
326
+ throw new Error(`${name} cannot be null or undefined.`);
327
+ }
328
+ if (typeof value !== 'string') {
329
+ throw new Error(`${name} must be a string.`);
330
+ }
331
+ if (!allowEmptyString && value.trim().length === 0) {
332
+ throw new Error(`${name} cannot be an empty string.`);
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Basic validation for a non-negative number (integer or float).
338
+ * @param {*} value - The value to validate.
339
+ * @param {string} name - The name of the parameter being validated.
340
+ * @private
341
+ * @throws {Error} If validation fails.
342
+ */
343
+ function _validatePositiveNumber(value, name) {
344
+ if (value === null || value === undefined) {
345
+ throw new Error(`${name} cannot be null or undefined.`);
346
+ }
347
+ if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
348
+ throw new Error(`${name} must be a valid number.`);
349
+ }
350
+ if (value < 0) {
351
+ throw new Error(`${name} cannot be negative.`);
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Basic validation for a non-negative integer.
357
+ * @param {*} value - The value to validate.
358
+ * @param {string} name - The name of the parameter being validated.
359
+ * @private
360
+ * @throws {Error} If validation fails.
361
+ */
362
+ function _validateNonNegativeInteger(value, name) {
363
+ _validatePositiveNumber(value, name); // Must be non-negative number first
364
+ if (!Number.isInteger(value)) {
365
+ throw new Error(`${name} must be an integer.`);
366
+ }
367
+ }
368
+
369
+
370
+ /**
371
+ * Validates that a value is a valid status from a given list.
372
+ * @param {string} status - The status string to validate.
373
+ * @param {string[]} validStatuses - An array of valid status strings.
374
+ * @param {string} name - The name of the status being validated (e.g., 'product status').
375
+ * @private
376
+ * @throws {Error} If validation fails.
377
+ */
378
+ function _validateStatus(status, validStatuses, name) {
379
+ _validateString(status, name);
380
+ if (!validStatuses.includes(status)) {
381
+ throw new Error(`Invalid ${name}: "${status}". Must be one of ${validStatuses.join(', ')}.`);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Validates data required for creating a new product.
387
+ * @param {object} productData - The data object for creating a product.
388
+ * @private
389
+ * @throws {Error} If validation fails.
390
+ */
391
+ function _validateNewProductData(productData) {
392
+ if (!productData || typeof productData !== 'object') {
393
+ throw new Error('Invalid product data: Must be an object.');
394
+ }
395
+ _validateString(productData.title, 'product title');
396
+ if (productData.title.length < 3 || productData.title.length > 100) {
397
+ throw new Error('Invalid product title: Must be between 3 and 100 characters.');
398
+ }
399
+ // Description is optional but validate if provided
400
+ if (productData.description !== undefined && productData.description !== null) {
401
+ _validateString(productData.description, 'product description', true); // Allow empty string
402
+ if (productData.description.length > 500) {
403
+ throw new Error('Invalid product description: Cannot exceed 500 characters.');
404
+ }
405
+ } else {
406
+ productData.description = ''; // Default if not provided
407
+ }
408
+ _validatePositiveNumber(productData.price, 'product price');
409
+ if (productData.price > 100000) { // Arbitrary max price
410
+ throw new Error('Invalid product price: Price exceeds maximum allowed value.');
411
+ }
412
+ _validateNonNegativeInteger(productData.stock, 'product stock');
413
+ if (productData.stock > 1000000) { // Arbitrary max stock
414
+ throw new Error('Invalid product stock: Stock exceeds maximum allowed value.');
415
+ }
416
+ // Image URL is optional but validate if provided
417
+ if (productData.imageUrl !== undefined && productData.imageUrl !== null) {
418
+ _validateString(productData.imageUrl, 'product imageUrl', true);
419
+ if (productData.imageUrl.length > 0 && !productData.imageUrl.startsWith('http')) {
420
+ throw new Error('Invalid product imageUrl: Must be a valid URL starting with http.');
421
+ }
422
+ } else {
423
+ productData.imageUrl = ''; // Default if not provided
424
+ }
425
+ // Category is optional but validate if provided
426
+ if (productData.category !== undefined && productData.category !== null) {
427
+ _validateString(productData.category, 'product category', true); // Allow empty string
428
+ if (productData.category.length > 50) {
429
+ throw new Error('Invalid product category: Cannot exceed 50 characters.');
430
+ }
431
+ } else {
432
+ productData.category = 'uncategorized'; // Default if not provided
433
+ }
434
+ // Status is optional but validate if provided, default to 'draft'
435
+ if (productData.status !== undefined && productData.status !== null) {
436
+ _validateStatus(productData.status, PRODUCT_STATUSES, 'product status');
437
+ } else {
438
+ productData.status = 'draft'; // Default if not provided
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Validates data provided for updating an existing product.
444
+ * @param {object} updateData - The data object for updating a product.
445
+ * @private
446
+ * @throws {Error} If validation fails.
447
+ */
448
+ function _validateProductUpdateData(updateData) {
449
+ if (!updateData || typeof updateData !== 'object') {
450
+ throw new Error('Invalid product update data: Must be an object.');
451
+ }
452
+ // Allow partial updates, but validate fields if they exist
453
+ let hasUpdateFields = false;
454
+ if (updateData.title !== undefined) {
455
+ _validateString(updateData.title, 'title');
456
+ if (updateData.title.length < 3 || updateData.title.length > 100) {
457
+ throw new Error('Invalid product title: Must be between 3 and 100 characters.');
458
+ }
459
+ hasUpdateFields = true;
460
+ }
461
+ if (updateData.description !== undefined) {
462
+ if (updateData.description !== null) { // Allow null/undefined to potentially clear
463
+ _validateString(updateData.description, 'description', true);
464
+ if (updateData.description.length > 500) {
465
+ throw new Error('Invalid product description: Cannot exceed 500 characters.');
466
+ }
467
+ }
468
+ hasUpdateFields = true;
469
+ }
470
+ if (updateData.price !== undefined) {
471
+ _validatePositiveNumber(updateData.price, 'price');
472
+ if (updateData.price > 100000) {
473
+ throw new Error('Invalid product price: Price exceeds maximum allowed value.');
474
+ }
475
+ hasUpdateFields = true;
476
+ }
477
+ if (updateData.stock !== undefined) {
478
+ _validateNonNegativeInteger(updateData.stock, 'stock');
479
+ if (updateData.stock > 1000000) {
480
+ throw new Error('Invalid product stock: Stock exceeds maximum allowed value.');
481
+ }
482
+ hasUpdateFields = true;
483
+ }
484
+ if (updateData.imageUrl !== undefined) {
485
+ if (updateData.imageUrl !== null) {
486
+ _validateString(updateData.imageUrl, 'imageUrl', true);
487
+ if (updateData.imageUrl.length > 0 && !updateData.imageUrl.startsWith('http')) {
488
+ throw new Error('Invalid product imageUrl: Must be a valid URL starting with http.');
489
+ }
490
+ }
491
+ hasUpdateFields = true;
492
+ }
493
+ if (updateData.category !== undefined) {
494
+ if (updateData.category !== null) {
495
+ _validateString(updateData.category, 'category', true);
496
+ if (updateData.category.length > 50) {
497
+ throw new Error('Invalid product category: Cannot exceed 50 characters.');
498
+ }
499
+ }
500
+ hasUpdateFields = true;
501
+ }
502
+ if (updateData.status !== undefined) {
503
+ _validateStatus(updateData.status, PRODUCT_STATUSES, 'status');
504
+ hasUpdateFields = true;
505
+ }
506
+
507
+ if (!hasUpdateFields) {
508
+ throw new Error('Invalid product update data: No valid fields provided for update.');
509
+ }
510
+ }
511
+
512
+
513
+ /**
514
+ * Validates data provided for updating the seller profile.
515
+ * @param {object} updateData - The data object for updating the profile.
516
+ * @private
517
+ * @throws {Error} If validation fails.
518
+ */
519
+ function _validateSellerProfileUpdateData(updateData) {
520
+ if (!updateData || typeof updateData !== 'object') {
521
+ throw new Error('Invalid profile update data: Must be an object.');
522
+ }
523
+ let hasUpdateFields = false;
524
+ if (updateData.name !== undefined) {
525
+ _validateString(updateData.name, 'name');
526
+ hasUpdateFields = true;
527
+ }
528
+ if (updateData.email !== undefined) {
529
+ _validateString(updateData.email, 'email');
530
+ // Basic email format check (can be improved)
531
+ if (!/\S+@\S+\.\S+/.test(updateData.email)) {
532
+ throw new Error('Invalid email format.');
533
+ }
534
+ hasUpdateFields = true;
535
+ }
536
+ if (updateData.phone !== undefined) {
537
+ _validateString(updateData.phone, 'phone');
538
+ // Basic phone format check (can be improved, e.g., regex)
539
+ if (updateData.phone.length < 5) {
540
+ throw new Error('Invalid phone number format.');
541
+ }
542
+ hasUpdateFields = true;
543
+ }
544
+ if (updateData.address !== undefined) {
545
+ _validateString(updateData.address, 'address');
546
+ hasUpdateFields = true;
547
+ }
548
+ if (updateData.description !== undefined) {
549
+ if (updateData.description !== null) {
550
+ _validateString(updateData.description, 'description', true);
551
+ if (updateData.description.length > 1000) {
552
+ throw new Error('Invalid description: Cannot exceed 1000 characters.');
553
+ }
554
+ }
555
+ hasUpdateFields = true;
556
+ }
557
+
558
+ if (!hasUpdateFields) {
559
+ throw new Error('Invalid profile update data: No valid fields provided for update.');
560
+ }
561
+ }
562
+
563
+
564
+ /**
565
+ * Simulates an asynchronous API call with potential network delay and error chance.
566
+ * Wraps a given operation function.
567
+ * @param {function(): any} operation - The synchronous function containing the core logic to simulate.
568
+ * @param {number} [minDelay=SIMULATED_API_MIN_DELAY_MS] - Minimum delay in milliseconds.
569
+ * @param {number} [maxDelay=SIMULATED_API_MAX_DELAY_MS] - Maximum delay in milliseconds.
570
+ * @param {number} [errorChance=SIMULATED_API_ERROR_CHANCE] - Probability of a simulated API network/connection error (0 to 1).
571
+ * @returns {Promise<any>} A promise that resolves with the result of the operation or rejects with an error.
572
+ * @private
573
+ */
574
+ function _simulateApiCall(operation, minDelay = SIMULATED_API_MIN_DELAY_MS, maxDelay = SIMULATED_API_MAX_DELAY_MS, errorChance = SIMULATED_API_ERROR_CHANCE) {
575
+ const delay = Math.random() * (maxDelay - minDelay) + minDelay;
576
+ const simulateNetworkError = Math.random() < errorChance;
577
+
578
+ return new Promise((resolve, reject) => {
579
+ setTimeout(() => {
580
+ if (simulateNetworkError) {
581
+ console.warn(`[YapiSellerService - Simulation] Simulated API network error after ${delay.toFixed(0)}ms delay.`);
582
+ // Use a generic network error message
583
+ reject(new Error(`Network Error: Could not connect to the service.`));
584
+ return;
585
+ }
586
+ try {
587
+ // Execute the actual operation
588
+ const result = operation();
589
+ console.debug(`[YapiSellerService - Simulation] Operation successful after ${delay.toFixed(0)}ms delay.`);
590
+ resolve(result);
591
+ } catch (error) {
592
+ // Catch and propagate errors thrown by the operation itself
593
+ console.error(`[YapiSellerService - Simulation] Operation failed after ${delay.toFixed(0)}ms delay:`, error.message);
594
+ // Wrap known errors or propagate them
595
+ if (error instanceof Error) {
596
+ reject(error);
597
+ } else {
598
+ reject(new Error(`An unexpected error occurred during the operation: ${error}`));
599
+ }
600
+ }
601
+ }, delay);
602
+ });
603
+ }
604
+
605
+
606
+ // --- Simulate Initial Data Population (for demonstration) ---
607
+ // In a real scenario, this data would come from a backend API.
608
+ function _populateSimulatedData(sellerId) {
609
+ if (_sellerProfile === null) {
610
+ _sellerProfile = new SellerProfile({
611
+ id: sellerId,
612
+ name: 'Acme Supplies Inc.',
613
+ email: 'seller@acmesupplies.com',
614
+ phone: '+1-555-0101',
615
+ address: '123 Main St, Anytown, CA 91234',
616
+ description: 'Your one-stop shop for quality goods.',
617
+ createdAt: new Date(Date.now() - 86400000 * 365), // 1 year ago
618
+ updatedAt: new Date(Date.now() - 86400000 * 30) // 30 days ago
619
+ });
620
+
621
+ // Simulate some products
622
+ const product1Id = _generateUniqueId();
623
+ const product2Id = _generateUniqueId();
624
+ const product3Id = _generateUniqueId();
625
+ const product4Id = _generateUniqueId();
626
+
627
+ _products = [
628
+ new Product({
629
+ id: product1Id,
630
+ sellerId: sellerId,
631
+ title: 'Wireless Mouse',
632
+ description: 'Ergonomic wireless mouse with long battery life.',
633
+ price: 25.99,
634
+ stock: 150,
635
+ imageUrl: 'https://example.com/images/mouse.jpg',
636
+ category: 'electronics',
637
+ status: 'active',
638
+ createdAt: new Date(Date.now() - 86400000 * 100),
639
+ updatedAt: new Date(Date.now() - 86400000 * 5)
640
+ }),
641
+ new Product({
642
+ id: product2Id,
643
+ sellerId: sellerId,
644
+ title: 'Mechanical Keyboard',
645
+ description: 'Clicky mechanical keyboard for typing enthusiasts.',
646
+ price: 75.00,
647
+ stock: 50,
648
+ imageUrl: 'https://example.com/images/keyboard.jpg',
649
+ category: 'electronics',
650
+ status: 'active',
651
+ createdAt: new Date(Date.now() - 86400000 * 90),
652
+ updatedAt: new Date(Date.now() - 86400000 * 2)
653
+ }),
654
+ new Product({
655
+ id: product3Id,
656
+ sellerId: sellerId,
657
+ title: 'Desk Lamp',
658
+ description: 'Adjustable LED desk lamp with dimmer.',
659
+ price: 35.50,
660
+ stock: 10, // Low stock
661
+ imageUrl: 'https://example.com/images/lamp.jpg',
662
+ category: 'home goods',
663
+ status: 'active',
664
+ createdAt: new Date(Date.now() - 86400000 * 80),
665
+ updatedAt: new Date(Date.now() - 86400000 * 1)
666
+ }),
667
+ new Product({
668
+ id: product4Id,
669
+ sellerId: sellerId,
670
+ title: 'Archived Item',
671
+ description: 'This product is no longer available.',
672
+ price: 9.99,
673
+ stock: 0,
674
+ imageUrl: 'https://example.com/images/archived.jpg',
675
+ category: 'misc',
676
+ status: 'archived',
677
+ createdAt: new Date(Date.now() - 86400000 * 200),
678
+ updatedAt: new Date(Date.now() - 86400000 * 60)
679
+ }),
680
+ ];
681
+
682
+ // Simulate some orders for these products
683
+ const order1Id = _generateUniqueId();
684
+ const order2Id = _generateUniqueId();
685
+ const order3Id = _generateUniqueId();
686
+ const order4Id = _generateUniqueId();
687
+
688
+
689
+ _orders = [
690
+ new Order({
691
+ id: _generateUniqueId(),
692
+ orderId: order1Id,
693
+ sellerId: sellerId,
694
+ productId: product1Id,
695
+ productName: 'Wireless Mouse',
696
+ quantity: 2,
697
+ itemPrice: 25.99,
698
+ buyerInfo: { name: 'Alice Smith', address: '456 Oak Ave' },
699
+ status: 'delivered',
700
+ orderDate: new Date(Date.now() - 86400000 * 20),
701
+ updatedAt: new Date(Date.now() - 86400000 * 15)
702
+ }),
703
+ new Order({
704
+ id: _generateUniqueId(),
705
+ orderId: order2Id,
706
+ sellerId: sellerId,
707
+ productId: product2Id,
708
+ productName: 'Mechanical Keyboard',
709
+ quantity: 1,
710
+ itemPrice: 75.00,
711
+ buyerInfo: { name: 'Bob Johnson', address: '789 Pine Ln' },
712
+ status: 'shipped',
713
+ orderDate: new Date(Date.now() - 86400000 * 10),
714
+ updatedAt: new Date(Date.now() - 86400000 * 8)
715
+ }),
716
+ new Order({
717
+ id: _generateUniqueId(),
718
+ orderId: order3Id,
719
+ sellerId: sellerId,
720
+ productId: product1Id,
721
+ productName: 'Wireless Mouse',
722
+ quantity: 1,
723
+ itemPrice: 25.99,
724
+ buyerInfo: { name: 'Charlie Brown', address: '1011 Maple Dr' },
725
+ status: 'pending',
726
+ orderDate: new Date(Date.now() - 86400000 * 2),
727
+ updatedAt: new Date(Date.now() - 86400000 * 1)
728
+ }),
729
+ new Order({
730
+ id: _generateUniqueId(),
731
+ orderId: order4Id,
732
+ sellerId: sellerId,
733
+ productId: product3Id,
734
+ productName: 'Desk Lamp',
735
+ quantity: 1,
736
+ itemPrice: 35.50,
737
+ buyerInfo: { name: 'Diana Prince', address: '1213 Elm St' },
738
+ status: 'processing',
739
+ orderDate: new Date(Date.now() - 86400000 * 0.5),
740
+ updatedAt: new Date(Date.now() - 86400000 * 0.1)
741
+ }),
742
+ ];
743
+ console.log('[YapiSellerService - Simulation] Simulated data populated.');
744
+ }
745
+ }
746
+
747
+
748
+ // --- Main Service Class ---
749
+
750
+ /**
751
+ * @class YapiSellerService
752
+ * @description Provides methods for interacting with seller-specific data on the Yapi platform.
753
+ * All operations are simulated and asynchronous, mimicking real API calls.
754
+ */
755
+ class YapiSellerService {
756
+
757
+ /**
758
+ * Creates an instance of YapiSellerService.
759
+ * @param {string} sellerId - The unique identifier for the seller.
760
+ * @throws {Error} If sellerId is not provided or is invalid.
761
+ */
762
+ constructor(sellerId) {
763
+ _validateString(sellerId, 'sellerId');
764
+ this._sellerId = sellerId;
765
+ _populateSimulatedData(this._sellerId); // Populate data specific to this seller instance (in simulation)
766
+ console.log(`[YapiSellerService] Initialized for seller: ${this._sellerId}`);
767
+ }
768
+
769
+ /**
770
+ * Get the seller's unique ID.
771
+ * @returns {string} The seller ID this service instance is associated with.
772
+ */
773
+ getSellerId() {
774
+ return this._sellerId;
775
+ }
776
+
777
+ // --- Seller Profile Methods ---
778
+
779
+ /**
780
+ * Retrieves the profile information for the current seller.
781
+ * @returns {Promise<SellerProfile>} A promise that resolves with the seller's profile object.
782
+ * @throws {Error} If the seller profile is not found (in simulation, this means it wasn't populated).
783
+ */
784
+ async getProfile() {
785
+ console.log(`[YapiSellerService] Attempting to get profile for seller: ${this._sellerId}`);
786
+ return this._simulateApiCall(() => {
787
+ if (!_sellerProfile || _sellerProfile.id !== this._sellerId) {
788
+ // This case should ideally not happen with _populateSimulatedData,
789
+ // but included for robust simulation logic.
790
+ console.error(`[YapiSellerService] Profile not found for seller: ${this._sellerId}`);
791
+ throw new Error(`Seller profile not found.`);
792
+ }
793
+ // Return a copy to prevent external modification of the internal state
794
+ return new SellerProfile(_sellerProfile.toObject());
795
+ });
796
+ }
797
+
798
+ /**
799
+ * Updates the profile information for the current seller.
800
+ * @param {object} updateData - An object containing the fields to update (e.g., { name, email, address }).
801
+ * @returns {Promise<SellerProfile>} A promise that resolves with the updated seller's profile object.
802
+ * @throws {Error} If the updateData is invalid or the profile is not found.
803
+ */
804
+ async updateProfile(updateData) {
805
+ console.log(`[YapiSellerService] Attempting to update profile for seller: ${this._sellerId}`);
806
+ return this._simulateApiCall(() => {
807
+ _validateSellerProfileUpdateData(updateData);
808
+
809
+ if (!_sellerProfile || _sellerProfile.id !== this._sellerId) {
810
+ console.error(`[YapiSellerService] Profile not found for update for seller: ${this._sellerId}`);
811
+ throw new Error(`Seller profile not found.`);
812
+ }
813
+
814
+ // Apply updates to the internal simulated profile object
815
+ _sellerProfile.update(updateData);
816
+
817
+ console.log(`[YapiSellerService] Profile updated successfully for seller: ${this._sellerId}`);
818
+ // Return a copy of the updated profile
819
+ return new SellerProfile(_sellerProfile.toObject());
820
+ });
821
+ }
822
+
823
+ // --- Product Management Methods ---
824
+
825
+ /**
826
+ * Creates a new product for the seller.
827
+ * @param {object} productData - The data for the new product (excluding id and sellerId).
828
+ * @param {string} productData.title - The title of the product.
829
+ * @param {string} [productData.description] - The description of the product.
830
+ * @param {number} productData.price - The price of the product.
831
+ * @param {number} productData.stock - The initial stock quantity.
832
+ * @param {string} [productData.imageUrl] - The URL of the main product image.
833
+ * @param {string} [productData.category] - The product category.
834
+ * @param {string} [productData.status='draft'] - The initial status of the product.
835
+ * @returns {Promise<Product>} A promise that resolves with the newly created Product object.
836
+ * @throws {Error} If the productData is invalid.
837
+ */
838
+ async createProduct(productData) {
839
+ console.log(`[YapiSellerService] Attempting to create product for seller: ${this._sellerId}`);
840
+ return this._simulateApiCall(() => {
841
+ _validateNewProductData(productData);
842
+
843
+ const newProductId = _generateUniqueId();
844
+ const product = new Product({
845
+ id: newProductId,
846
+ sellerId: this._sellerId,
847
+ ...productData,
848
+ createdAt: new Date(), // Set creation timestamp
849
+ updatedAt: new Date() // Set update timestamp
850
+ });
851
+
852
+ _products.push(product);
853
+ console.log(`[YapiSellerService] Product created successfully with ID: ${newProductId} for seller: ${this._sellerId}`);
854
+ // Return a copy
855
+ return new Product(product.toObject());
856
+ }, SIMULATED_API_MIN_DELAY_MS, SIMULATED_API_MAX_DELAY_MS, SIMULATED_API_ERROR_CHANCE); // Potentially higher delay/error for creation
857
+
858
+ }
859
+
860
+ /**
861
+ * Retrieves a specific product by its ID.
862
+ * @param {string} productId - The unique identifier of the product.
863
+ * @returns {Promise<Product>} A promise that resolves with the Product object.
864
+ * @throws {Error} If the productId is invalid or the product is not found for this seller.
865
+ */
866
+ async getProductById(productId) {
867
+ console.log(`[YapiSellerService] Attempting to get product by ID: ${productId} for seller: ${this._sellerId}`);
868
+ return this._simulateApiCall(() => {
869
+ _validateString(productId, 'productId');
870
+
871
+ const product = _products.find(p => p.id === productId && p.sellerId === this._sellerId);
872
+
873
+ if (!product) {
874
+ console.error(`[YapiSellerService] Product with ID ${productId} not found for seller: ${this._sellerId}.`);
875
+ throw new Error(`Product with ID ${productId} not found.`);
876
+ }
877
+
878
+ console.log(`[YapiSellerService] Successfully retrieved product with ID: ${productId}`);
879
+ // Return a copy
880
+ return new Product(product.toObject());
881
+ });
882
+ }
883
+
884
+ /**
885
+ * Updates an existing product for the seller.
886
+ * Allows partial updates. Fields not included in updateData will not be changed.
887
+ * @param {string} productId - The unique identifier of the product to update.
888
+ * @param {object} updateData - An object containing the fields to update.
889
+ * @returns {Promise<Product>} A promise that resolves with the updated Product object.
890
+ * @throws {Error} If the productId is invalid, updateData is invalid, or the product is not found.
891
+ */
892
+ async updateProduct(productId, updateData) {
893
+ console.log(`[YapiSellerService] Attempting to update product ID: ${productId} for seller: ${this._sellerId}`);
894
+ return this._simulateApiCall(() => {
895
+ _validateString(productId, 'productId');
896
+ _validateProductUpdateData(updateData); // Validates the update data object structure and fields
897
+
898
+ const productIndex = _products.findIndex(p => p.id === productId && p.sellerId === this._sellerId);
899
+
900
+ if (productIndex === -1) {
901
+ console.error(`[YapiSellerService] Product with ID ${productId} not found for update for seller: ${this._sellerId}.`);
902
+ throw new Error(`Product with ID ${productId} not found.`);
903
+ }
904
+
905
+ // Apply updates to the found product instance
906
+ _products[productIndex].update(updateData);
907
+
908
+ console.log(`[YapiSellerService] Product ID: ${productId} updated successfully for seller: ${this._sellerId}`);
909
+ // Return a copy of the updated product
910
+ return new Product(_products[productIndex].toObject());
911
+ });
912
+ }
913
+
914
+ /**
915
+ * Deletes a product permanently.
916
+ * This simulates a hard delete.
917
+ * @param {string} productId - The unique identifier of the product to delete.
918
+ * @returns {Promise<void>} A promise that resolves when the product is deleted.
919
+ * @throws {Error} If the productId is invalid or the product is not found.
920
+ */
921
+ async deleteProduct(productId) {
922
+ console.log(`[YapiSellerService] Attempting to delete product ID: ${productId} for seller: ${this._sellerId}`);
923
+ return this._simulateApiCall(() => {
924
+ _validateString(productId, 'productId');
925
+
926
+ const initialLength = _products.length;
927
+ // Filter out the product. Assumes product IDs are unique to the seller.
928
+ _products = _products.filter(p => !(p.id === productId && p.sellerId === this._sellerId));
929
+
930
+ if (_products.length === initialLength) {
931
+ console.error(`[YapiSellerService] Product with ID ${productId} not found for delete for seller: ${this._sellerId}.`);
932
+ throw new Error(`Product with ID ${productId} not found or could not be deleted.`);
933
+ }
934
+
935
+ console.log(`[YapiSellerService] Product ID: ${productId} deleted successfully for seller: ${this._sellerId}`);
936
+ }, SIMULATED_API_MIN_DELAY_MS, SIMULATED_API_MAX_DELAY_MS * 2, SIMULATED_API_ERROR_CHANCE * 1.5); // Deletion might be slower/riskier
937
+
938
+ }
939
+
940
+ /**
941
+ * Archives a product (sets its status to 'archived').
942
+ * This simulates a soft delete or deactivation.
943
+ * @param {string} productId - The unique identifier of the product to archive.
944
+ * @returns {Promise<Product>} A promise that resolves with the updated Product object with status 'archived'.
945
+ * @throws {Error} If the productId is invalid or the product is not found.
946
+ */
947
+ async archiveProduct(productId) {
948
+ console.log(`[YapiSellerService] Attempting to archive product ID: ${productId} for seller: ${this._sellerId}`);
949
+ return this._simulateApiCall(async () => { // Use async here if updateProduct is async
950
+ _validateString(productId, 'productId');
951
+ // Use updateProduct to handle the actual status change and validation
952
+ const updatedProduct = await this.updateProduct(productId, { status: 'archived' });
953
+ console.log(`[YapiSellerService] Product ID: ${productId} archived successfully for seller: ${this._sellerId}`);
954
+ return updatedProduct; // updateProduct returns the product copy
955
+ }); // Simulate updateProduct's delay within its own call
956
+ }
957
+
958
+ /**
959
+ * Unarchives a product (sets its status to 'active').
960
+ * @param {string} productId - The unique identifier of the product to unarchive.
961
+ * @returns {Promise<Product>} A promise that resolves with the updated Product object with status 'active'.
962
+ * @throws {Error} If the productId is invalid or the product is not found or cannot be unarchived.
963
+ */
964
+ async unarchiveProduct(productId) {
965
+ console.log(`[YapiSellerService] Attempting to unarchive product ID: ${productId} for seller: ${this._sellerId}`);
966
+ return this._simulateApiCall(async () => { // Use async here if updateProduct is async
967
+ _validateString(productId, 'productId');
968
+ // Use updateProduct to handle the actual status change and validation
969
+ // Check current status before attempting to unarchive? Optional, updateProduct handles it.
970
+ const updatedProduct = await this.updateProduct(productId, { status: 'active' });
971
+ console.log(`[YapiSellerService] Product ID: ${productId} unarchived successfully for seller: ${this._sellerId}`);
972
+ return updatedProduct; // updateProduct returns the product copy
973
+ });
974
+ }
975
+
976
+
977
+ /**
978
+ * Lists products for the seller with pagination and filtering options.
979
+ * @param {object} [options] - Options for listing products.
980
+ * @param {number} [options.page=1] - The page number (1-based index).
981
+ * @param {number} [options.limit=10] - The number of items per page.
982
+ * @param {string} [options.status] - Filter by product status ('draft', 'active', 'archived').
983
+ * @param {string} [options.category] - Filter by product category.
984
+ * @param {string} [options.sortBy='createdAt'] - Field to sort by ('createdAt', 'updatedAt', 'title', 'price', 'stock').
985
+ * @param {'asc'|'desc'} [options.sortOrder='desc'] - Sort order ('asc' or 'desc').
986
+ * @param {string} [options.searchQuery] - A search term to filter by title or description.
987
+ * @returns {Promise<{products: Product[], totalCount: number, page: number, limit: number}>} A promise resolving with product data and pagination info.
988
+ * @throws {Error} If options are invalid.
989
+ */
990
+ async listProducts(options = {}) {
991
+ console.log(`[YapiSellerService] Attempting to list products for seller: ${this._sellerId} with options:`, options);
992
+ return this._simulateApiCall(() => {
993
+ // 1. Validate options
994
+ const page = options.page === undefined ? 1 : options.page;
995
+ const limit = options.limit === undefined ? 10 : options.limit;
996
+ const statusFilter = options.status;
997
+ const categoryFilter = options.category;
998
+ const sortBy = options.sortBy === undefined ? 'createdAt' : options.sortBy;
999
+ const sortOrder = options.sortOrder === undefined ? 'desc' : options.sortOrder;
1000
+ const searchQuery = options.searchQuery;
1001
+
1002
+ _validateNonNegativeInteger(page, 'options.page');
1003
+ if (page < 1) throw new Error('options.page must be at least 1.');
1004
+ _validateNonNegativeInteger(limit, 'options.limit');
1005
+ if (limit < 1 || limit > 100) throw new Error('options.limit must be between 1 and 100.'); // Arbitrary limit max
1006
+
1007
+ if (statusFilter !== undefined && statusFilter !== null) {
1008
+ _validateStatus(statusFilter, PRODUCT_STATUSES, 'options.status');
1009
+ }
1010
+ if (categoryFilter !== undefined && categoryFilter !== null) {
1011
+ _validateString(categoryFilter, 'options.category', true); // Allow empty string category
1012
+ }
1013
+ if (sortBy !== undefined && sortBy !== null) {
1014
+ _validateString(sortBy, 'options.sortBy');
1015
+ const validSortBy = ['createdAt', 'updatedAt', 'title', 'price', 'stock'];
1016
+ if (!validSortBy.includes(sortBy)) {
1017
+ throw new Error(`Invalid options.sortBy: "${sortBy}". Must be one of ${validSortBy.join(', ')}.`);
1018
+ }
1019
+ }
1020
+ if (sortOrder !== undefined && sortOrder !== null) {
1021
+ _validateString(sortOrder, 'options.sortOrder');
1022
+ if (sortOrder !== 'asc' && sortOrder !== 'desc') {
1023
+ throw new Error(`Invalid options.sortOrder: "${sortOrder}". Must be 'asc' or 'desc'.`);
1024
+ }
1025
+ }
1026
+ if (searchQuery !== undefined && searchQuery !== null) {
1027
+ _validateString(searchQuery, 'options.searchQuery', true);
1028
+ }
1029
+
1030
+
1031
+ // 2. Filter products relevant to this seller
1032
+ let filteredProducts = _products.filter(p => p.sellerId === this._sellerId);
1033
+
1034
+ // 3. Apply filters
1035
+ if (statusFilter !== undefined && statusFilter !== null) {
1036
+ filteredProducts = filteredProducts.filter(p => p.status === statusFilter);
1037
+ }
1038
+ if (categoryFilter !== undefined && categoryFilter !== null) {
1039
+ // Case-insensitive category filter
1040
+ filteredProducts = filteredProducts.filter(p => p.category.toLowerCase() === categoryFilter.toLowerCase());
1041
+ }
1042
+ if (searchQuery !== undefined && searchQuery !== null && searchQuery.trim().length > 0) {
1043
+ const lowerQuery = searchQuery.toLowerCase();
1044
+ filteredProducts = filteredProducts.filter(p =>
1045
+ (p.title && p.title.toLowerCase().includes(lowerQuery)) ||
1046
+ (p.description && p.description.toLowerCase().includes(lowerQuery))
1047
+ );
1048
+ }
1049
+
1050
+
1051
+ // 4. Apply sorting
1052
+ const sortMultiplier = sortOrder === 'asc' ? 1 : -1;
1053
+ filteredProducts.sort((a, b) => {
1054
+ const valueA = a[sortBy];
1055
+ const valueB = b[sortBy];
1056
+
1057
+ if (valueA === undefined || valueA === null) return -1 * sortMultiplier;
1058
+ if (valueB === undefined || valueB === null) return 1 * sortMultiplier;
1059
+
1060
+ if (typeof valueA === 'string' && typeof valueB === 'string') {
1061
+ return valueA.localeCompare(valueB) * sortMultiplier;
1062
+ }
1063
+ if (valueA < valueB) {
1064
+ return -1 * sortMultiplier;
1065
+ }
1066
+ if (valueA > valueB) {
1067
+ return 1 * sortMultiplier;
1068
+ }
1069
+ return 0; // Equal
1070
+ });
1071
+
1072
+ // 5. Apply pagination
1073
+ const totalCount = filteredProducts.length;
1074
+ const startIndex = (page - 1) * limit;
1075
+ const endIndex = startIndex + limit;
1076
+ const paginatedProducts = filteredProducts.slice(startIndex, endIndex);
1077
+
1078
+ console.log(`[YapiSellerService] Listed ${paginatedProducts.length} products (total ${totalCount}) for seller: ${this._sellerId}`);
1079
+
1080
+ // Return copies of products
1081
+ const productObjects = paginatedProducts.map(p => new Product(p.toObject()));
1082
+
1083
+ return {
1084
+ products: productObjects,
1085
+ totalCount: totalCount,
1086
+ page: page,
1087
+ limit: limit
1088
+ };
1089
+ });
1090
+ }
1091
+
1092
+
1093
+ /**
1094
+ * Gets the total count of products for the seller, optionally filtered by status.
1095
+ * @param {object} [filter] - Filter options.
1096
+ * @param {string} [filter.status] - Filter by product status ('draft', 'active', 'archived').
1097
+ * @returns {Promise<number>} A promise resolving with the total number of products.
1098
+ * @throws {Error} If filter options are invalid.
1099
+ */
1100
+ async countProducts(filter = {}) {
1101
+ console.log(`[YapiSellerService] Attempting to count products for seller: ${this._sellerId} with filter:`, filter);
1102
+ return this._simulateApiCall(() => {
1103
+ const statusFilter = filter.status;
1104
+
1105
+ if (statusFilter !== undefined && statusFilter !== null) {
1106
+ _validateStatus(statusFilter, PRODUCT_STATUSES, 'filter.status');
1107
+ }
1108
+
1109
+ let count = _products.filter(p => p.sellerId === this._sellerId).length;
1110
+
1111
+ if (statusFilter !== undefined && statusFilter !== null) {
1112
+ count = _products.filter(p => p.sellerId === this._sellerId && p.status === statusFilter).length;
1113
+ }
1114
+
1115
+ console.log(`[YapiSellerService] Counted ${count} products for seller: ${this._sellerId}`);
1116
+ return count;
1117
+ });
1118
+ }
1119
+
1120
+
1121
+ // --- Order Management Methods ---
1122
+
1123
+ /**
1124
+ * Lists orders relevant to this seller with pagination and filtering options.
1125
+ * Orders are items where the ordered product belongs to this seller.
1126
+ * @param {object} [options] - Options for listing orders.
1127
+ * @param {number} [options.page=1] - The page number (1-based index).
1128
+ * @param {number} [options.limit=10] - The number of items per page.
1129
+ * @param {string} [options.status] - Filter by order status ('pending', 'processing', etc.).
1130
+ * @param {string} [options.orderId] - Filter by the overall order ID.
1131
+ * @param {string} [options.productId] - Filter by a specific product ID.
1132
+ * @param {Date} [options.startDate] - Filter for orders placed on or after this date.
1133
+ * @param {Date} [options.endDate] - Filter for orders placed on or before this date.
1134
+ * @param {string} [options.sortBy='orderDate'] - Field to sort by ('orderDate', 'updatedAt', 'totalPrice', 'quantity').
1135
+ * @param {'asc'|'desc'} [options.sortOrder='desc'] - Sort order ('asc' or 'desc').
1136
+ * @returns {Promise<{orders: Order[], totalCount: number, page: number, limit: number}>} A promise resolving with order data and pagination info.
1137
+ * @throws {Error} If options are invalid.
1138
+ */
1139
+ async listOrders(options = {}) {
1140
+ console.log(`[YapiSellerService] Attempting to list orders for seller: ${this._sellerId} with options:`, options);
1141
+ return this._simulateApiCall(() => {
1142
+ // 1. Validate options
1143
+ const page = options.page === undefined ? 1 : options.page;
1144
+ const limit = options.limit === undefined ? 10 : options.limit;
1145
+ const statusFilter = options.status;
1146
+ const orderIdFilter = options.orderId;
1147
+ const productIdFilter = options.productId;
1148
+ const startDateFilter = options.startDate instanceof Date ? options.startDate : null;
1149
+ const endDateFilter = options.endDate instanceof Date ? options.endDate : null;
1150
+ const sortBy = options.sortBy === undefined ? 'orderDate' : options.sortBy;
1151
+ const sortOrder = options.sortOrder === undefined ? 'desc' : options.sortOrder;
1152
+
1153
+
1154
+ _validateNonNegativeInteger(page, 'options.page');
1155
+ if (page < 1) throw new Error('options.page must be at least 1.');
1156
+ _validateNonNegativeInteger(limit, 'options.limit');
1157
+ if (limit < 1 || limit > 100) throw new Error('options.limit must be between 1 and 100.');
1158
+
1159
+ if (statusFilter !== undefined && statusFilter !== null) {
1160
+ _validateStatus(statusFilter, ORDER_STATUSES, 'options.status');
1161
+ }
1162
+ if (orderIdFilter !== undefined && orderIdFilter !== null) {
1163
+ _validateString(orderIdFilter, 'options.orderId');
1164
+ }
1165
+ if (productIdFilter !== undefined && productIdFilter !== null) {
1166
+ _validateString(productIdFilter, 'options.productId');
1167
+ }
1168
+ if (startDateFilter !== null && !(startDateFilter instanceof Date) || isNaN(startDateFilter.getTime())) {
1169
+ throw new Error('options.startDate must be a valid Date object.');
1170
+ }
1171
+ if (endDateFilter !== null && !(endDateFilter instanceof Date) || isNaN(endDateFilter.getTime())) {
1172
+ throw new Error('options.endDate must be a valid Date object.');
1173
+ }
1174
+ if (startDateFilter && endDateFilter && startDateFilter > endDateFilter) {
1175
+ throw new Error('options.startDate cannot be after options.endDate.');
1176
+ }
1177
+
1178
+ if (sortBy !== undefined && sortBy !== null) {
1179
+ _validateString(sortBy, 'options.sortBy');
1180
+ const validSortBy = ['orderDate', 'updatedAt', 'totalPrice', 'quantity', 'itemPrice'];
1181
+ if (!validSortBy.includes(sortBy)) {
1182
+ throw new Error(`Invalid options.sortBy: "${sortBy}". Must be one of ${validSortBy.join(', ')}.`);
1183
+ }
1184
+ }
1185
+ if (sortOrder !== undefined && sortOrder !== null) {
1186
+ _validateString(sortOrder, 'options.sortOrder');
1187
+ if (sortOrder !== 'asc' && sortOrder !== 'desc') {
1188
+ throw new Error(`Invalid options.sortOrder: "${sortOrder}". Must be 'asc' or 'desc'.`);
1189
+ }
1190
+ }
1191
+
1192
+
1193
+ // 2. Filter orders relevant to this seller
1194
+ let filteredOrders = _orders.filter(order => order.sellerId === this._sellerId);
1195
+
1196
+ // 3. Apply filters
1197
+ if (statusFilter !== undefined && statusFilter !== null) {
1198
+ filteredOrders = filteredOrders.filter(order => order.status === statusFilter);
1199
+ }
1200
+ if (orderIdFilter !== undefined && orderIdFilter !== null) {
1201
+ filteredOrders = filteredOrders.filter(order => order.orderId === orderIdFilter);
1202
+ }
1203
+ if (productIdFilter !== undefined && productIdFilter !== null) {
1204
+ filteredOrders = filteredOrders.filter(order => order.productId === productIdFilter);
1205
+ }
1206
+ if (startDateFilter !== null) {
1207
+ filteredOrders = filteredOrders.filter(order => order.orderDate >= startDateFilter);
1208
+ }
1209
+ if (endDateFilter !== null) {
1210
+ // Include the end date itself
1211
+ const endOfDay = new Date(endDateFilter);
1212
+ endOfDay.setHours(23, 59, 59, 999);
1213
+ filteredOrders = filteredOrders.filter(order => order.orderDate <= endOfDay);
1214
+ }
1215
+
1216
+
1217
+ // 4. Apply sorting
1218
+ const sortMultiplier = sortOrder === 'asc' ? 1 : -1;
1219
+ filteredOrders.sort((a, b) => {
1220
+ const valueA = a[sortBy];
1221
+ const valueB = b[sortBy];
1222
+
1223
+ if (valueA === undefined || valueA === null) return -1 * sortMultiplier;
1224
+ if (valueB === undefined || valueB === null) return 1 * sortMultiplier;
1225
+
1226
+ // Handle Date comparison
1227
+ if (valueA instanceof Date && valueB instanceof Date) {
1228
+ return (valueA.getTime() - valueB.getTime()) * sortMultiplier;
1229
+ }
1230
+ // Handle string comparison
1231
+ if (typeof valueA === 'string' && typeof valueB === 'string') {
1232
+ return valueA.localeCompare(valueB) * sortMultiplier;
1233
+ }
1234
+ // Handle number comparison
1235
+ if (valueA < valueB) {
1236
+ return -1 * sortMultiplier;
1237
+ }
1238
+ if (valueA > valueB) {
1239
+ return 1 * sortMultiplier;
1240
+ }
1241
+ return 0; // Equal
1242
+ });
1243
+
1244
+
1245
+ // 5. Apply pagination
1246
+ const totalCount = filteredOrders.length;
1247
+ const startIndex = (page - 1) * limit;
1248
+ const endIndex = startIndex + limit;
1249
+ const paginatedOrders = filteredOrders.slice(startIndex, endIndex);
1250
+
1251
+ console.log(`[YapiSellerService] Listed ${paginatedOrders.length} orders (total ${totalCount}) for seller: ${this._sellerId}`);
1252
+
1253
+ // Return copies of orders
1254
+ const orderObjects = paginatedOrders.map(o => new Order(o.toObject()));
1255
+
1256
+ return {
1257
+ orders: orderObjects,
1258
+ totalCount: totalCount,
1259
+ page: page,
1260
+ limit: limit
1261
+ };
1262
+ });
1263
+ }
1264
+
1265
+ /**
1266
+ * Retrieves a specific order item by its ID.
1267
+ * Note: This retrieves a single item within a potentially larger order.
1268
+ * @param {string} orderItemId - The unique identifier of the order item.
1269
+ * @returns {Promise<Order>} A promise that resolves with the Order object.
1270
+ * @throws {Error} If the orderItemId is invalid or the order item is not found for this seller.
1271
+ */
1272
+ async getOrderItemById(orderItemId) {
1273
+ console.log(`[YapiSellerService] Attempting to get order item by ID: ${orderItemId} for seller: ${this._sellerId}`);
1274
+ return this._simulateApiCall(() => {
1275
+ _validateString(orderItemId, 'orderItemId');
1276
+
1277
+ const orderItem = _orders.find(o => o.id === orderItemId && o.sellerId === this._sellerId);
1278
+
1279
+ if (!orderItem) {
1280
+ console.error(`[YapiSellerService] Order item with ID ${orderItemId} not found for seller: ${this._sellerId}.`);
1281
+ throw new Error(`Order item with ID ${orderItemId} not found.`);
1282
+ }
1283
+
1284
+ console.log(`[YapiSellerService] Successfully retrieved order item with ID: ${orderItemId}`);
1285
+ // Return a copy
1286
+ return new Order(orderItem.toObject());
1287
+ });
1288
+ }
1289
+
1290
+ /**
1291
+ * Updates the status of a specific order item.
1292
+ * @param {string} orderItemId - The unique identifier of the order item to update.
1293
+ * @param {string} newStatus - The new status for the order item ('pending', 'processing', etc.).
1294
+ * @returns {Promise<Order>} A promise that resolves with the updated Order object.
1295
+ * @throws {Error} If the orderItemId is invalid, newStatus is invalid, or the order item is not found.
1296
+ */
1297
+ async updateOrderItemStatus(orderItemId, newStatus) {
1298
+ console.log(`[YapiSellerService] Attempting to update status of order item ID: ${orderItemId} to "${newStatus}" for seller: ${this._sellerId}`);
1299
+ return this._simulateApiCall(() => {
1300
+ _validateString(orderItemId, 'orderItemId');
1301
+ _validateStatus(newStatus, ORDER_STATUSES, 'newStatus');
1302
+
1303
+ const orderItem = _orders.find(o => o.id === orderItemId && o.sellerId === this._sellerId);
1304
+
1305
+ if (!orderItem) {
1306
+ console.error(`[YapiSellerService] Order item with ID ${orderItemId} not found for status update for seller: ${this._sellerId}.`);
1307
+ throw new Error(`Order item with ID ${orderItemId} not found.`);
1308
+ }
1309
+
1310
+ // Apply status update using the Order class method
1311
+ orderItem.updateStatus(newStatus);
1312
+
1313
+ console.log(`[YapiSellerService] Order item ID: ${orderItemId} status updated to "${newStatus}" for seller: ${this._sellerId}`);
1314
+ // Return a copy of the updated order item
1315
+ return new Order(orderItem.toObject());
1316
+ });
1317
+ }
1318
+
1319
+
1320
+ /**
1321
+ * Gets the total count of orders for the seller, optionally filtered by status.
1322
+ * @param {object} [filter] - Filter options.
1323
+ * @param {string} [filter.status] - Filter by order status ('pending', 'processing', etc.).
1324
+ * @returns {Promise<number>} A promise resolving with the total number of orders.
1325
+ * @throws {Error} If filter options are invalid.
1326
+ */
1327
+ async countOrders(filter = {}) {
1328
+ console.log(`[YapiSellerService] Attempting to count orders for seller: ${this._sellerId} with filter:`, filter);
1329
+ return this._simulateApiCall(() => {
1330
+ const statusFilter = filter.status;
1331
+
1332
+ if (statusFilter !== undefined && statusFilter !== null) {
1333
+ _validateStatus(statusFilter, ORDER_STATUSES, 'filter.status');
1334
+ }
1335
+
1336
+ let count = _orders.filter(o => o.sellerId === this._sellerId).length;
1337
+
1338
+ if (statusFilter !== undefined && statusFilter !== null) {
1339
+ count = _orders.filter(o => o.sellerId === this._sellerId && o.status === statusFilter).length;
1340
+ }
1341
+
1342
+ console.log(`[YapiSellerService] Counted ${count} orders for seller: ${this._sellerId}`);
1343
+ return count;
1344
+ });
1345
+ }
1346
+
1347
+
1348
+ // --- Inventory Methods (Basic) ---
1349
+
1350
+ /**
1351
+ * Updates the stock level for a specific product.
1352
+ * The change quantity can be positive (add stock) or negative (remove stock).
1353
+ * @param {string} productId - The unique identifier of the product.
1354
+ * @param {number} quantityChange - The amount to change the stock by. Can be positive or negative integer.
1355
+ * @returns {Promise<Product>} A promise that resolves with the updated Product object.
1356
+ * @throws {Error} If productId is invalid, quantityChange is not a valid integer, or the product is not found, or if the change results in negative stock.
1357
+ */
1358
+ async updateStock(productId, quantityChange) {
1359
+ console.log(`[YapiSellerService] Attempting to update stock for product ID: ${productId} by ${quantityChange} for seller: ${this._sellerId}`);
1360
+ return this._simulateApiCall(() => {
1361
+ _validateString(productId, 'productId');
1362
+ if (typeof quantityChange !== 'number' || !Number.isInteger(quantityChange)) {
1363
+ throw new Error('quantityChange must be an integer.');
1364
+ }
1365
+
1366
+ const product = _products.find(p => p.id === productId && p.sellerId === this._sellerId);
1367
+
1368
+ if (!product) {
1369
+ console.error(`[YapiSellerService] Product with ID ${productId} not found for stock update for seller: ${this._sellerId}.`);
1370
+ throw new Error(`Product with ID ${productId} not found.`);
1371
+ }
1372
+
1373
+ const newStock = product.stock + quantityChange;
1374
+ if (newStock < 0) {
1375
+ console.error(`[YapiSellerService] Stock update for product ID ${productId} failed: Resulting stock would be negative (${newStock}).`);
1376
+ throw new Error(`Cannot update stock: Resulting stock quantity (${newStock}) cannot be negative.`);
1377
+ }
1378
+
1379
+ product.stock = newStock;
1380
+ product.updatedAt = new Date(); // Mark product as updated
1381
+ console.log(`[YapiSellerService] Stock updated for product ID: ${productId}. New stock: ${product.stock} for seller: ${this._sellerId}`);
1382
+
1383
+ // Return a copy of the updated product
1384
+ return new Product(product.toObject());
1385
+ });
1386
+ }
1387
+
1388
+ /**
1389
+ * Gets the current stock level for a specific product.
1390
+ * @param {string} productId - The unique identifier of the product.
1391
+ * @returns {Promise<number>} A promise that resolves with the current stock quantity.
1392
+ * @throws {Error} If productId is invalid or the product is not found.
1393
+ */
1394
+ async getStockLevel(productId) {
1395
+ console.log(`[YapiSellerService] Attempting to get stock level for product ID: ${productId} for seller: ${this._sellerId}`);
1396
+ return this._simulateApiCall(() => {
1397
+ _validateString(productId, 'productId');
1398
+
1399
+ const product = _products.find(p => p.id === productId && p.sellerId === this._sellerId);
1400
+
1401
+ if (!product) {
1402
+ console.error(`[YapiSellerService] Product with ID ${productId} not found for stock check for seller: ${this._sellerId}.`);
1403
+ throw new Error(`Product with ID ${productId} not found.`);
1404
+ }
1405
+
1406
+ console.log(`[YapiSellerService] Stock level for product ID: ${productId} is ${product.stock} for seller: ${this._sellerId}`);
1407
+ return product.stock;
1408
+ });
1409
+ }
1410
+
1411
+ /**
1412
+ * Lists products for the seller that are currently below a specified stock threshold.
1413
+ * Only lists products with 'active' status by default.
1414
+ * @param {number} threshold - The stock quantity threshold. Products with stock less than this will be listed.
1415
+ * @returns {Promise<Product[]>} A promise that resolves with an array of Product objects with low stock.
1416
+ * @throws {Error} If the threshold is invalid.
1417
+ */
1418
+ async listLowStockProducts(threshold) {
1419
+ console.log(`[YapiSellerService] Attempting to list low stock products (threshold < ${threshold}) for seller: ${this._sellerId}`);
1420
+ return this._simulateApiCall(() => {
1421
+ _validateNonNegativeInteger(threshold, 'threshold');
1422
+ if (threshold < 0) throw new Error('Threshold cannot be negative.');
1423
+
1424
+
1425
+ const lowStockProducts = _products.filter(p =>
1426
+ p.sellerId === this._sellerId &&
1427
+ p.status === 'active' && // Typically only active products need stock monitoring
1428
+ p.stock < threshold
1429
+ );
1430
+
1431
+ console.log(`[YapiSellerService] Found ${lowStockProducts.length} products below stock threshold ${threshold} for seller: ${this._sellerId}`);
1432
+ // Return copies of products
1433
+ return lowStockProducts.map(p => new Product(p.toObject()));
1434
+ });
1435
+ }
1436
+
1437
+
1438
+ // --- Additional Utility/Simulation Methods (Private) ---
1439
+
1440
+ /**
1441
+ * Internal helper to find a product index by ID.
1442
+ * @param {string} productId - The product ID.
1443
+ * @returns {number} The index of the product in the _products array, or -1 if not found.
1444
+ * @private
1445
+ */
1446
+ _findProductIndexById(productId) {
1447
+ // Note: Real API wouldn't expose indices, this is purely for simulation data manipulation
1448
+ return _products.findIndex(p => p.id === productId && p.sellerId === this._sellerId);
1449
+ }
1450
+
1451
+ /**
1452
+ * Internal helper to find an order item index by ID.
1453
+ * @param {string} orderItemId - The order item ID.
1454
+ * @returns {number} The index of the order item in the _orders array, or -1 if not found.
1455
+ * @private
1456
+ */
1457
+ _findOrderItemIndexById(orderItemId) {
1458
+ // Note: Real API wouldn't expose indices, this is purely for simulation data manipulation
1459
+ return _orders.findIndex(o => o.id === orderItemId && o.sellerId === this._sellerId);
1460
+ }
1461
+
1462
+ /**
1463
+ * Simulates clearing all in-memory data for the seller.
1464
+ * Useful for testing or resetting the simulation state.
1465
+ * In a real service, this method would not exist or would require high privileges.
1466
+ * @returns {Promise<void>} A promise that resolves when data is cleared.
1467
+ */
1468
+ async _clearSimulatedData() {
1469
+ console.warn(`[YapiSellerService - Simulation] Clearing simulated data for seller: ${this._sellerId}`);
1470
+ return this._simulateApiCall(() => {
1471
+ _sellerProfile = null; // Reset seller profile
1472
+ _products = []; // Clear products
1473
+ _orders = []; // Clear orders
1474
+ console.warn(`[YapiSellerService - Simulation] Simulated data cleared for seller: ${this._sellerId}`);
1475
+ }, 50, 100); // Faster simulation clear
1476
+ }
1477
+ }
1478
+
1479
+ // --- Exports ---
1480
+
1481
+ /**
1482
+ * @exports YapiSellerService
1483
+ */
1484
+ module.exports = YapiSellerService;
1485
+
1486
+ // Note: In a real ES Module environment, you would use `export default YapiSellerService;`
1487
+ // and potentially export the data classes if needed, e.g., `export { YapiSellerService, Product, Order, SellerProfile };`
1488
+ // Using module.exports for Node.js CommonJS compatibility, common in older npm packages.
1489
+
1490
+ // Example Usage (Illustrative - would typically be in a separate test or example file)
1491
+ /*
1492
+ async function runExample() {
1493
+ const sellerId = 'SELLER123';
1494
+ const sellerService = new YapiSellerService(sellerId);
1495
+
1496
+ try {
1497
+ // Get profile
1498
+ const profile = await sellerService.getProfile();
1499
+ console.log('\n--- Seller Profile ---');
1500
+ console.log(profile.toObject());
1501
+
1502
+ // Update profile
1503
+ await sellerService.updateProfile({ phone: '+1-555-9876', description: 'Updated description.' });
1504
+ const updatedProfile = await sellerService.getProfile();
1505
+ console.log('\n--- Updated Seller Profile ---');
1506
+ console.log(updatedProfile.toObject());
1507
+
1508
+ // List products
1509
+ console.log('\n--- Listing Products (Page 1) ---');
1510
+ const productList = await sellerService.listProducts({ page: 1, limit: 2, status: 'active', sortBy: 'title', sortOrder: 'asc' });
1511
+ console.log(`Total active products: ${productList.totalCount}`);
1512
+ productList.products.forEach(p => console.log(p.toObject()));
1513
+
1514
+ console.log('\n--- Listing Products (Page 2) ---');
1515
+ const productList2 = await sellerService.listProducts({ page: 2, limit: 2, status: 'active', sortBy: 'title', sortOrder: 'asc' });
1516
+ productList2.products.forEach(p => console.log(p.toObject()));
1517
+
1518
+ // Get a specific product
1519
+ if (productList.products.length > 0) {
1520
+ const firstProductId = productList.products[0].id;
1521
+ console.log(`\n--- Getting Product ID: ${firstProductId} ---`);
1522
+ const specificProduct = await sellerService.getProductById(firstProductId);
1523
+ console.log(specificProduct.toObject());
1524
+
1525
+ // Update a product
1526
+ console.log(`\n--- Updating Stock for Product ID: ${firstProductId} ---`);
1527
+ const updatedProduct = await sellerService.updateStock(firstProductId, -5); // Sell 5 units
1528
+ console.log(updatedProduct.toObject());
1529
+ const updatedProduct2 = await sellerService.updateStock(firstProductId, 10); // Restock 10 units
1530
+ console.log(updatedProduct2.toObject());
1531
+
1532
+
1533
+ // Archive a product
1534
+ if (productList.products.length > 1) {
1535
+ const secondProductId = productList.products[1].id;
1536
+ console.log(`\n--- Archiving Product ID: ${secondProductId} ---`);
1537
+ await sellerService.archiveProduct(secondProductId);
1538
+ console.log(`Product ${secondProductId} status is now archived.`);
1539
+ }
1540
+
1541
+
1542
+ } else {
1543
+ console.log('\nNo active products found to demonstrate get/update/archive.');
1544
+ }
1545
+
1546
+
1547
+ // List orders
1548
+ console.log('\n--- Listing Orders (Page 1) ---');
1549
+ const orderList = await sellerService.listOrders({ limit: 3, sortBy: 'orderDate' });
1550
+ console.log(`Total orders: ${orderList.totalCount}`);
1551
+ orderList.orders.forEach(o => console.log(o.toObject()));
1552
+
1553
+ // Get a specific order item
1554
+ if (orderList.orders.length > 0) {
1555
+ const firstOrderItemId = orderList.orders[0].id;
1556
+ console.log(`\n--- Getting Order Item ID: ${firstOrderItemId} ---`);
1557
+ const specificOrderItem = await sellerService.getOrderItemById(firstOrderItemId);
1558
+ console.log(specificOrderItem.toObject());
1559
+
1560
+ // Update order status
1561
+ console.log(`\n--- Updating Status for Order Item ID: ${firstOrderItemId} ---`);
1562
+ const updatedOrderItem = await sellerService.updateOrderItemStatus(firstOrderItemId, 'shipped');
1563
+ console.log(updatedOrderItem.toObject());
1564
+ } else {
1565
+ console.log('\nNo orders found to demonstrate get/update.');
1566
+ }
1567
+
1568
+
1569
+ // Count products by status
1570
+ console.log('\n--- Counting Products by Status ---');
1571
+ const activeCount = await sellerService.countProducts({ status: 'active' });
1572
+ const archivedCount = await sellerService.countProducts({ status: 'archived' });
1573
+ const draftCount = await sellerService.countProducts({ status: 'draft' });
1574
+ const totalProductCount = await sellerService.countProducts();
1575
+ console.log(`Active products: ${activeCount}`);
1576
+ console.log(`Archived products: ${archivedCount}`);
1577
+ console.log(`Draft products: ${draftCount}`);
1578
+ console.log(`Total products: ${totalProductCount}`);
1579
+
1580
+ // Count orders by status
1581
+ console.log('\n--- Counting Orders by Status ---');
1582
+ const pendingCount = await sellerService.countOrders({ status: 'pending' });
1583
+ const shippedCount = await sellerService.countOrders({ status: 'shipped' });
1584
+ const totalOrderCount = await sellerService.countOrders();
1585
+ console.log(`Pending orders: ${pendingCount}`);
1586
+ console.log(`Shipped orders: ${shippedCount}`);
1587
+ console.log(`Total order items: ${totalOrderCount}`);
1588
+
1589
+
1590
+ // List low stock products
1591
+ console.log('\n--- Listing Low Stock Products (Threshold 100) ---');
1592
+ const lowStock = await sellerService.listLowStockProducts(100);
1593
+ lowStock.forEach(p => console.log(`Low Stock: ${p.title} (${p.stock})`));
1594
+
1595
+ // Simulate an error case (e.g., get non-existent product)
1596
+ console.log('\n--- Demonstrating Error Handling (Get non-existent product) ---');
1597
+ try {
1598
+ await sellerService.getProductById('non-existent-id');
1599
+ } catch (error) {
1600
+ console.error('Caught expected error:', error.message);
1601
+ }
1602
+
1603
+ // Simulate an error case (e.g., invalid update data)
1604
+ console.log('\n--- Demonstrating Error Handling (Invalid update data) ---');
1605
+ try {
1606
+ await sellerService.updateProfile({ name: '' }); // Invalid empty string
1607
+ } catch (error) {
1608
+ console.error('Caught expected error:', error.message);
1609
+ }
1610
+
1611
+
1612
+ } catch (error) {
1613
+ console.error('\nAn unexpected error occurred during example execution:', error);
1614
+ } finally {
1615
+ // Clean up simulated data if needed (optional)
1616
+ // await sellerService._clearSimulatedData();
1617
+ }
1618
+ }
1619
+
1620
+ // runExample();
1621
+ */