@ideal-photography/shared
Version:
Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.
139 lines (119 loc) • 5.14 kB
JavaScript
import { models } from '../../mongoDB/index.js';
/**
* Shared Product Availability Service
* Contains logic for checking product availability and stock status.
*/
export class SharedProductAvailabilityService {
/**
* Check equipment availability
* @param {string} id - Equipment ID
* @param {Date|string} startDate - Start date
* @param {Date|string} endDate - End date
* @returns {Promise<Object>} Availability result { available: boolean, reason: string }
*/
static async checkEquipmentAvailability(id, startDate, endDate) {
try {
const equipment = await models.Equipment.findById(id);
if (!equipment) {
return { available: false, reason: 'Equipment not found' };
}
if (!equipment.isActive) {
return { available: false, reason: 'Equipment is inactive' };
}
if (equipment.inventory.availableUnits <= 0) {
return { available: false, reason: 'No units available' };
}
// Check for booking conflicts
const conflicts = await models.Booking.countDocuments({
product: id,
status: { $in: ['confirmed', 'preparing', 'in_progress'] },
$or: [
{
'typeSpecificData.rentalPeriod.startDate': { $lt: new Date(endDate) },
'typeSpecificData.rentalPeriod.endDate': { $gt: new Date(startDate) }
}
]
});
if (conflicts > 0) {
return { available: false, reason: 'Equipment is booked for this period' };
}
// Check maintenance schedule
const isInMaintenance = equipment.availabilityRules?.maintenanceSchedule?.some(schedule => {
const maintenanceStart = new Date(schedule.startDate);
const maintenanceEnd = new Date(schedule.endDate);
const requestStart = new Date(startDate);
const requestEnd = new Date(endDate);
return (maintenanceStart <= requestEnd && maintenanceEnd >= requestStart);
});
if (isInMaintenance) {
return { available: false, reason: 'Equipment is under maintenance' };
}
// Check blackout dates
const hasBlackoutConflict = equipment.availabilityRules?.blackoutDates?.some(blackoutDate => {
const blackout = new Date(blackoutDate);
const requestStart = new Date(startDate);
const requestEnd = new Date(endDate);
return (blackout >= requestStart && blackout <= requestEnd);
});
if (hasBlackoutConflict) {
return { available: false, reason: 'Equipment is unavailable due to blackout period' };
}
return { available: true };
} catch (error) {
console.error('Error in checkEquipmentAvailability:', error);
throw new Error(`Failed to check equipment availability: ${error.message}`);
}
}
/**
* Check if product is in stock
* @param {Object} product - Product object
* @param {string} productType - Product type
* @param {number} quantity - Required quantity
* @returns {boolean} Is in stock
*/
static isInStock(product, productType, quantity = 1) {
switch (productType) {
case 'minimart':
return (product.inventory?.stockQuantity || 0) >= quantity;
case 'makeover_session':
case 'studio_session':
return product.isActive !== false; // Services are always "in stock" if active
case 'equipment':
default:
return (product.inventory?.availableUnits || 0) >= quantity;
}
}
/**
* Validate product for cart addition
* @param {Object} product - Product object
* @param {string} productType - Product type
* @param {number} quantity - Required quantity
* @returns {Object} Validation result { isValid, errors }
*/
static validateProductForCart(product, productType, quantity = 1) {
const errors = [];
if (!product) {
errors.push('Product not found');
return { isValid: false, errors };
}
if (!this.isInStock(product, productType, quantity)) {
errors.push('Product is out of stock');
}
if (quantity < 1) {
errors.push('Invalid quantity');
}
// Type-specific validations
switch (productType) {
case 'makeover_session':
case 'studio_session':
if (!product.isActive) {
errors.push('Service is not available');
}
break;
}
return {
isValid: errors.length === 0,
errors
};
}
}