Files
DELIVER_MAN/server/db/database.js
2026-01-04 22:14:28 +03:00

661 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import mdb from "mariadb"
export const DB_INTERNAL_ERROR = 'INTERNAL';
export const DB_USER_ERROR = 'USER';
let currentDate = (new Date()).toISOString().slice(0, 10);
export function getCurrentDate() {
return currentDate;
}
function Random_Increase() {
return Math.floor(Math.random() * 5) + 1;
}
export async function setCurrentDate(newDate) {
currentDate = newDate;
console.log(`📅 Date changed to: ${currentDate}`);
}
export default class DBAdapter {
#dbhost = 'localhost';
#dbPort = 3306;
#dbName = 'N';
#dbUserLogin = 'u';
#dbUserPassword = 'p';
#pool = null;
constructor({
dbHost,
dbPort,
dbName,
dbUserLogin,
dbUserPassword
}) {
this.#dbhost = dbHost;
this.#dbPort = dbPort;
this.#dbName = dbName;
this.#dbUserLogin = dbUserLogin;
this.#dbUserPassword = dbUserPassword;
this.#pool = mdb.createPool({
host: this.#dbhost,
port: this.#dbPort,
database: this.#dbName,
user: this.#dbUserLogin,
password: this.#dbUserPassword,
dateStrings: true,
acquireTimeout: 10000,
});
}
async #getConnection() {
return await this.#pool.getConnection();
}
async connect() {
try {
const conn = await this.#pool.getConnection();
console.log("✅ Database connection test successful");
conn.release();
} catch (err) {
console.error("❌ Database connection failed:", err.message);
return Promise.reject();
}
}
async disconnect() {
await this.#pool.end();
console.log("DISCONNECT");
}
async stock(){
try {
const products = await this.#pool.query('SELECT id FROM products');
for (const product of products) {
const increment = Random_Increase();
await this.#pool.query('UPDATE products SET quantity = quantity + ? WHERE id = ?', [
increment,
product.id
]);
}
} catch (err){
return Promise.reject({
type: err
}
);
}
}
async getProducts() {
try {
const products = await this.#pool.query('SELECT * FROM products ORDER BY name');
return products;
} catch(err){
console.log(`WHOOPS`);
return Promise.reject();
}
}
async getOrders() {
try {
const raw_orders = await this.#pool.query('SELECT * FROM orders ORDER BY order_date ASC');
const items = await this.#pool.query(
`SELECT order_items.*, products.name AS product_name FROM order_items
JOIN products ON order_items.product_id = products.id`
);
const groupedItems = {};
if (items && items.length > 0) {
items.forEach(item => {
const orderId = item.order_id;
if (!groupedItems[orderId]) {
groupedItems[orderId] = [];
}
groupedItems[orderId].push(item);
});
}
const ordersWithItems = raw_orders.map(order => {
return {
...order,
items: groupedItems[order.id] || []
};
});
return ordersWithItems;
} catch(err) {
console.log(`WHOOPS: ${err.message}`);
return Promise.reject(err);
}
}
async addOrder({ id, customer_name, orderDate} ){
if (!id || !customer_name || !orderDate){
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Пустое имя клиента или дата заказа")
});
}
const currentDate = await getCurrentDate();
if (orderDate < currentDate){
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Недопустимая дата заказа")
});
}
try {
await this.#pool.query('INSERT INTO orders (id, customer_name, order_date) VALUES (?, ?, ?)',
[id, customer_name, orderDate]
);
} catch(err){
return Promise.reject({
type: DB_INTERNAL_ERROR,
error: new Error("Ошибка сервера")
});
}
}
async getOrdersByDate(date){
try {
const order_ids = await this.#pool.query('SELECT id FROM orders WHERE order_date = ?', [date]);
return order_ids;
} catch(err){
return Promise.reject();
}
}
async clearOrders(orderIds) {
if (!orderIds || orderIds.length === 0) {
console.log("No orders to delete");
return;
}
try {
const placeholders = orderIds.map(() => '?').join(',');
const result = await this.#pool.query(
`DELETE FROM orders WHERE id IN (${placeholders})`,
orderIds
);
console.log(`✅ Deleted ${result.affectedRows} orders with IDs:`, orderIds);
} catch(err) {
console.error(`❌ Error deleting orders:`, err.message);
return Promise.reject();
}
}
async changeOrderInfo({ id, customer_name, orderDate }) {
if (!id || !customer_name || !orderDate) {
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Нужен ID заказа, имя и дата"),
message: "Нужен ID заказа, имя и дата" // Добавляем message напрямую
});
}
const currentDate = await getCurrentDate();
if (orderDate < currentDate) {
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Дата заказа не может быть раньше текущей"),
message: "Дата заказа не может быть раньше текущей"
});
}
try {
const order = await this.#pool.query('SELECT * FROM orders WHERE id = ?', [id]);
if (!order || order.length === 0) { // Исправлено: проверяем length
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Заказ не найден"),
message: "Заказ не найден"
});
}
const updatedName = customer_name;
const updatedDate = orderDate;
await this.#pool.query('UPDATE orders SET customer_name = ?, order_date = ? WHERE id = ?',
[updatedName, updatedDate, id]
);
} catch(err) {
console.error('Database error in changeOrderInfo:', err);
return Promise.reject({
type: DB_INTERNAL_ERROR,
error: new Error("Ошибка сервера"),
message: err.message || "Ошибка операции с базой данных",
details: err.message
});
}
}
async deleteOrderById(order_id) {
let connection;
try {
connection = await this.#pool.getConnection();
await connection.beginTransaction();
const orderItems = await connection.query(
`SELECT product_id, quantity FROM order_items WHERE order_id = ?`,
[order_id]
);
for (const item of orderItems) {
await connection.query(
'UPDATE products SET quantity = quantity + ? WHERE id = ?',
[item.quantity, item.product_id]
);
}
await connection.query(
`DELETE FROM order_items WHERE order_id = ?`,
[order_id]
);
await connection.query(
`DELETE FROM orders WHERE id = ?`,
[order_id]
);
await connection.commit();
connection.release();
} catch (err) {
if (connection) {
try {
await connection.rollback();
} catch (rollbackErr) {
console.error('Rollback error:', rollbackErr.message);
}
connection.release();
}
return Promise.reject({
type: DB_INTERNAL_ERROR,
error: new Error("Не удалось удалить заказ"),
details: err.message
});
}
}
async addOrderItem({ orderId, productId, quantity }) {
let connection;
try {
connection = await this.#pool.getConnection();
await connection.beginTransaction();
const orderCheck = await connection.query(
'SELECT id FROM orders WHERE id = ?',
[orderId]
);
if (!orderCheck || orderCheck.length === 0) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Заказ не найден")
});
}
const product = await connection.query(
'SELECT id, quantity FROM products WHERE id = ? FOR UPDATE',
[productId]
);
const productRow = product?.[0];
if (!productRow) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Товар не найден")
});
}
if (productRow.quantity < quantity) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Недостаточно товара на складе")
});
}
await connection.query(
'UPDATE products SET quantity = quantity - ? WHERE id = ?',
[quantity, productId]
);
const result = await connection.query(
'INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)',
[orderId, productId, quantity]
);
await connection.commit();
connection.release();
return result.insertId;
} catch (err) {
if (connection) {
try {
await connection.rollback();
} catch (rollbackErr) {
console.error('Rollback error:', rollbackErr.message);
}
connection.release();
}
console.error('Transaction error:', err);
let errorType = DB_INTERNAL_ERROR;
let userMessage = "Ошибка операции с базой данных";
let retryable = false;
if (err.code === 'ER_LOCK_WAIT_TIMEOUT' || err.errno === 1205) {
errorType = 'DB_CONFLICT';
userMessage = "Система занята. Попробуйте позже.";
retryable = true;
} else if (err.code === 'ER_DUP_ENTRY') {
errorType = DB_USER_ERROR;
userMessage = "Позиция уже есть в заказе";
}
return Promise.reject({
type: errorType,
error: new Error(userMessage),
details: err.message,
code: err.code,
retryable: retryable,
originalError: err
});
}
}
async updateOrderItem({ itemId, quantity, productId }) {
let connection;
try {
if (!itemId || !Number.isFinite(quantity) || quantity <= 0) {
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Неверная позиция или количество")
});
}
connection = await this.#pool.getConnection();
await connection.beginTransaction();
const item = await connection.query(
'SELECT product_id, quantity FROM order_items WHERE id = ? FOR UPDATE',
[itemId]
);
const itemRow = item?.[0];
if (!itemRow) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Позиция заказа не найдена")
});
}
const targetProductId = productId ?? itemRow.product_id;
if (targetProductId !== itemRow.product_id) {
const newProduct = await connection.query(
'SELECT quantity FROM products WHERE id = ? FOR UPDATE',
[targetProductId]
);
const newProductRow = newProduct?.[0];
if (!newProductRow) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Товар не найден")
});
}
if (newProductRow.quantity < quantity) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Недостаточно товара на складе")
});
}
await connection.query(
'UPDATE products SET quantity = quantity + ? WHERE id = ?',
[itemRow.quantity, itemRow.product_id]
);
await connection.query(
'UPDATE products SET quantity = quantity - ? WHERE id = ?',
[quantity, targetProductId]
);
await connection.query(
'UPDATE order_items SET product_id = ?, quantity = ? WHERE id = ?',
[targetProductId, quantity, itemId]
);
await connection.commit();
connection.release();
return;
}
const diff = quantity - itemRow.quantity;
if (diff > 0) {
const product = await connection.query(
'SELECT quantity FROM products WHERE id = ? FOR UPDATE',
[itemRow.product_id]
);
const productRow = product?.[0];
if (!productRow || productRow.quantity < diff) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Недостаточно товара на складе")
});
}
await connection.query(
'UPDATE products SET quantity = quantity - ? WHERE id = ?',
[diff, itemRow.product_id]
);
}
if (diff < 0) {
await connection.query(
'UPDATE products SET quantity = quantity + ? WHERE id = ?',
[-diff, itemRow.product_id]
);
}
await connection.query(
'UPDATE order_items SET quantity = ? WHERE id = ?',
[quantity, itemId]
);
await connection.commit();
connection.release();
} catch (err) {
if (connection) {
try {
await connection.rollback();
} catch (rollbackErr) {
console.error('Rollback error:', rollbackErr.message);
}
connection.release();
}
return Promise.reject({
type: DB_INTERNAL_ERROR,
error: new Error("Не удалось обновить позицию заказа"),
details: err.message
});
}
}
async deleteOrderItem(itemId) {
let connection;
try {
connection = await this.#pool.getConnection();
await connection.beginTransaction();
const item = await connection.query(
'SELECT product_id, quantity FROM order_items WHERE id = ? FOR UPDATE',
[itemId]
);
const itemRow = item?.[0];
if (!itemRow) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Позиция заказа не найдена")
});
}
await connection.query(
'UPDATE products SET quantity = quantity + ? WHERE id = ?',
[itemRow.quantity, itemRow.product_id]
);
await connection.query(
'DELETE FROM order_items WHERE id = ?',
[itemId]
);
await connection.commit();
connection.release();
} catch (err) {
if (connection) {
try {
await connection.rollback();
} catch (rollbackErr) {
console.error('Rollback error:', rollbackErr.message);
}
connection.release();
}
console.error('Delete order item error:', err);
return Promise.reject({
type: DB_INTERNAL_ERROR,
error: new Error("Не удалось удалить позицию заказа"),
details: err.message,
itemId: itemId
});
}
}
async moveOrderItem({ itemId, targetOrderId }) {
let connection;
try {
connection = await this.#pool.getConnection();
await connection.beginTransaction();
// Проверяем существование целевого заказа
const targetOrderCheck = await connection.query(
'SELECT id FROM orders WHERE id = ?',
[targetOrderId]
);
if (!targetOrderCheck || targetOrderCheck.length === 0) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Целевой заказ не найден")
});
}
const item = await connection.query(
'SELECT id, order_id, product_id, quantity FROM order_items WHERE id = ? FOR UPDATE',
[itemId]
);
const itemRow = item?.[0];
if (!itemRow) {
await connection.rollback();
connection.release();
return Promise.reject({
type: DB_USER_ERROR,
error: new Error("Позиция заказа не найдена")
});
}
if (itemRow.order_id === targetOrderId) {
await connection.rollback();
connection.release();
return;
}
const targetItem = await connection.query(
'SELECT id, quantity FROM order_items WHERE order_id = ? AND product_id = ? FOR UPDATE',
[targetOrderId, itemRow.product_id]
);
const targetItemRow = targetItem?.[0];
if (targetItemRow) {
await connection.query(
'UPDATE order_items SET quantity = ? WHERE id = ?',
[targetItemRow.quantity + itemRow.quantity, targetItemRow.id]
);
await connection.query(
'DELETE FROM order_items WHERE id = ?',
[itemId]
);
} else {
await connection.query(
'UPDATE order_items SET order_id = ? WHERE id = ?',
[targetOrderId, itemId]
);
}
await connection.commit();
connection.release();
} catch (err) {
if (connection) {
try {
await connection.rollback();
} catch (rollbackErr) {
console.error('Rollback error:', rollbackErr.message);
}
connection.release();
}
return Promise.reject({
type: DB_INTERNAL_ERROR,
error: new Error("Не удалось перенести позицию заказа"),
details: err.message
});
}
}
}