235 lines
6.4 KiB
JavaScript
235 lines
6.4 KiB
JavaScript
const database = require('./database');
|
|
|
|
class Product {
|
|
constructor(data = {}) {
|
|
this.id = data.id || null;
|
|
this.name = data.name || '';
|
|
this.description = data.description || '';
|
|
this.category = data.category || '';
|
|
this.quantity = data.quantity || 0;
|
|
this.unit = data.unit || '';
|
|
this.barcode = data.barcode || null;
|
|
this.qr_code = data.qr_code || null;
|
|
this.location = data.location || '';
|
|
this.min_stock_level = data.min_stock_level || 0;
|
|
this.created_at = data.created_at || null;
|
|
this.updated_at = data.updated_at || null;
|
|
}
|
|
|
|
/**
|
|
* Validate product data
|
|
* @returns {Object} validation result with isValid boolean and errors array
|
|
*/
|
|
validate() {
|
|
const errors = [];
|
|
|
|
// Required field validation
|
|
if (!this.name || this.name.trim().length === 0) {
|
|
errors.push('Product name is required');
|
|
}
|
|
|
|
if (this.name && this.name.length > 255) {
|
|
errors.push('Product name must be less than 255 characters');
|
|
}
|
|
|
|
// Quantity validation
|
|
if (typeof this.quantity !== 'number' || this.quantity < 0) {
|
|
errors.push('Quantity must be a non-negative number');
|
|
}
|
|
|
|
// Min stock level validation
|
|
if (typeof this.min_stock_level !== 'number' || this.min_stock_level < 0) {
|
|
errors.push('Minimum stock level must be a non-negative number');
|
|
}
|
|
|
|
// Barcode validation (if provided)
|
|
if (this.barcode && (typeof this.barcode !== 'string' || this.barcode.trim().length === 0)) {
|
|
errors.push('Barcode must be a non-empty string if provided');
|
|
}
|
|
|
|
// Category validation
|
|
if (this.category && this.category.length > 100) {
|
|
errors.push('Category must be less than 100 characters');
|
|
}
|
|
|
|
// Unit validation
|
|
if (this.unit && this.unit.length > 20) {
|
|
errors.push('Unit must be less than 20 characters');
|
|
}
|
|
|
|
return {
|
|
isValid: errors.length === 0,
|
|
errors: errors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Save product to database
|
|
* @returns {Promise<Product>} saved product instance
|
|
*/
|
|
async save() {
|
|
const validation = this.validate();
|
|
if (!validation.isValid) {
|
|
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
|
|
}
|
|
|
|
const db = database.getDatabase();
|
|
|
|
try {
|
|
if (this.id) {
|
|
// Update existing product
|
|
const stmt = db.prepare(`
|
|
UPDATE items
|
|
SET name = ?, description = ?, category = ?, quantity = ?,
|
|
unit = ?, barcode = ?, qr_code = ?, location = ?,
|
|
min_stock_level = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`);
|
|
|
|
stmt.run(
|
|
this.name, this.description, this.category, this.quantity,
|
|
this.unit, this.barcode, this.qr_code, this.location,
|
|
this.min_stock_level, this.id
|
|
);
|
|
} else {
|
|
// Insert new product
|
|
const stmt = db.prepare(`
|
|
INSERT INTO items (name, description, category, quantity, unit, barcode, qr_code, location, min_stock_level)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(
|
|
this.name, this.description, this.category, this.quantity,
|
|
this.unit, this.barcode, this.qr_code, this.location,
|
|
this.min_stock_level
|
|
);
|
|
|
|
this.id = result.lastInsertRowid;
|
|
}
|
|
|
|
return this;
|
|
} catch (error) {
|
|
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
|
|
throw new Error('A product with this barcode already exists');
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find product by ID
|
|
* @param {number} id - Product ID
|
|
* @returns {Promise<Product|null>} Product instance or null if not found
|
|
*/
|
|
static async findById(id) {
|
|
const db = database.getDatabase();
|
|
const stmt = db.prepare('SELECT * FROM items WHERE id = ?');
|
|
const row = stmt.get(id);
|
|
|
|
return row ? new Product(row) : null;
|
|
}
|
|
|
|
/**
|
|
* Find product by barcode
|
|
* @param {string} barcode - Product barcode
|
|
* @returns {Promise<Product|null>} Product instance or null if not found
|
|
*/
|
|
static async findByBarcode(barcode) {
|
|
const db = database.getDatabase();
|
|
const stmt = db.prepare('SELECT * FROM items WHERE barcode = ?');
|
|
const row = stmt.get(barcode);
|
|
|
|
return row ? new Product(row) : null;
|
|
}
|
|
|
|
/**
|
|
* Get all products with optional filtering
|
|
* @param {Object} filters - Optional filters
|
|
* @returns {Promise<Product[]>} Array of Product instances
|
|
*/
|
|
static async findAll(filters = {}) {
|
|
const db = database.getDatabase();
|
|
let query = 'SELECT * FROM items';
|
|
const params = [];
|
|
const conditions = [];
|
|
|
|
if (filters.category) {
|
|
conditions.push('category = ?');
|
|
params.push(filters.category);
|
|
}
|
|
|
|
if (filters.name) {
|
|
conditions.push('name LIKE ?');
|
|
params.push(`%${filters.name}%`);
|
|
}
|
|
|
|
if (conditions.length > 0) {
|
|
query += ' WHERE ' + conditions.join(' AND ');
|
|
}
|
|
|
|
query += ' ORDER BY name';
|
|
|
|
const stmt = db.prepare(query);
|
|
const rows = stmt.all(...params);
|
|
|
|
return rows.map(row => new Product(row));
|
|
}
|
|
|
|
/**
|
|
* Delete product by ID
|
|
* @param {number} id - Product ID
|
|
* @returns {Promise<boolean>} true if deleted, false if not found
|
|
*/
|
|
static async deleteById(id) {
|
|
const db = database.getDatabase();
|
|
const stmt = db.prepare('DELETE FROM items WHERE id = ?');
|
|
const result = stmt.run(id);
|
|
|
|
return result.changes > 0;
|
|
}
|
|
|
|
/**
|
|
* Convert product to plain object
|
|
* @returns {Object} Plain object representation
|
|
*/
|
|
toJSON() {
|
|
return {
|
|
id: this.id,
|
|
name: this.name,
|
|
description: this.description,
|
|
category: this.category,
|
|
quantity: this.quantity,
|
|
unit: this.unit,
|
|
barcode: this.barcode,
|
|
qr_code: this.qr_code,
|
|
location: this.location,
|
|
min_stock_level: this.min_stock_level,
|
|
created_at: this.created_at,
|
|
updated_at: this.updated_at
|
|
};
|
|
}
|
|
/**
|
|
* Get all unique categories
|
|
* @returns {Promise<Array>} Array of unique category names
|
|
*/
|
|
static async getCategories() {
|
|
const db = database.getDatabase();
|
|
|
|
try {
|
|
const stmt = db.prepare(`
|
|
SELECT DISTINCT category
|
|
FROM products
|
|
WHERE category IS NOT NULL AND category != ''
|
|
ORDER BY category
|
|
`);
|
|
|
|
const rows = stmt.all();
|
|
return rows.map(row => row.category);
|
|
} catch (error) {
|
|
console.error('Error getting categories:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Product; |