// calculators/soap.js
// Калькулятор мыла
/**
* Схема полей калькулятора
*/
const fieldSchema = [
{ id: 'soapName', name: 'soapName', label: 'Название мыла', type: 'text', required: true },
{ id: 'weight', name: 'weight', label: 'Вес мыла, г', type: 'number', required: true },
{ id: 'basePrice', name: 'basePrice', label: 'Цена основы, руб', type: 'number', required: false },
{ id: 'aromaPrice', name: 'aromaPrice', label: 'Цена отдушки, руб', type: 'number', required: false },
{ id: 'aromaWeight', name: 'aromaWeight', label: 'Фасовка отдушки, г', type: 'number', required: false },
{ id: 'pigmentPrice', name: 'pigmentPrice', label: 'Цена пигмента, руб', type: 'number', required: false },
{ id: 'pigmentWeight', name: 'pigmentWeight', label: 'Фасовка пигмента, г', type: 'number', required: false },
{ id: 'moldPrice', name: 'moldPrice', label: 'Цена формы, руб', type: 'number', required: false },
{ id: 'box', name: 'box', label: 'Пакет/коробка, руб', type: 'number', required: false },
{ id: 'filler', name: 'filler', label: 'Наполнитель, руб', type: 'number', required: false },
{ id: 'ribbon', name: 'ribbon', label: 'Лента, руб', type: 'number', required: false },
{ id: 'labelValue', name: 'labelValue', label: 'Наклейка, руб', type: 'number', required: false },
{ id: 'markup', name: 'markup', label: 'Наценка, %', type: 'number', required: false },
];
/**
* Расчёт себестоимости мыла
* @param {Object} data - Данные для расчёта
* @returns {Object} Результаты расчёта
*/
function calculate(data) {
const {
weight = 0,
basePrice = 0,
aromaPrice = 0,
aromaWeight = 1,
pigmentPrice = 0,
pigmentWeight = 1,
moldPrice = 0,
packaging = { box: 0, filler: 0, ribbon: 0, label: 0 },
} = data;
const base = (weight / 1000) * basePrice;
const aroma = ((weight * 0.01) / aromaWeight) * aromaPrice;
const pigment = ((weight * 0.005) / pigmentWeight) * pigmentPrice;
const mold = moldPrice / 100;
const packagingCost = (packaging.box || 0) + (packaging.filler || 0) + (packaging.ribbon || 0) + (packaging.label || 0);
const subtotal = base + aroma + pigment + mold + packagingCost;
const operational = subtotal * 0.05;
const total = subtotal + operational;
return {
base: round(base),
aroma: round(aroma),
pigment: round(pigment),
mold: round(mold),
packaging: round(packagingCost),
operational: round(operational),
total: round(total),
};
}
function round(val) {
return Math.round(val * 10) / 10;
}
/**
* Форматирует сообщение для Telegram
* @param {Object} data - Исходные данные
* @param {Object} result - Результаты расчёта
* @returns {string} Отформатированное сообщение
*/
function formatMessage(data, result) {
const {
soapName,
weight = 0,
box = 0,
filler = 0,
ribbon = 0,
labelValue = 0,
markup = 0,
totalCost,
finalPrice,
pricePer100g,
} = data;
let text = `🧼 Расчёт мыла: ${soapName}\n\n`;
text += `⚖️ Вес мыла: ${weight} г\n\n`;
text += `📦 Упаковка:\n`;
text += ` 📥 Пакет/коробка: ${box} ₽\n`;
text += ` 🌾 Наполнитель: ${filler} ₽\n`;
text += ` 🎀 Лента: ${ribbon} ₽\n`;
text += ` 🏷️ Наклейка: ${labelValue} ₽\n\n`;
text += `💹 Наценка: ${markup}%\n\n`;
text += `📊 Итоги расчёта:\n`;
text += ` 💵 Себестоимость: ${Number(totalCost).toFixed(1)} ₽\n`;
text += ` 🎯 Итоговая цена с наценкой: ${Number(finalPrice).toFixed(1)} ₽\n`;
text += ` ⚗️ Цена за 100 г: ${Number(pricePer100g).toFixed(1)} ₽`;
return text;
}
/**
* Получить обязательные поля для валидации
* @returns {string[]} Массив имён обязательных полей
*/
function getRequiredFields() {
return fieldSchema.filter((f) => f.required).map((f) => f.name);
}
/**
* Получить числовые поля для валидации
* @returns {string[]} Массив имён числовых полей
*/
function getNumericFields() {
return fieldSchema.filter((f) => f.type === 'number').map((f) => f.name);
}
module.exports = {
id: 'soap',
name: 'Мыло',
fieldSchema,
calculate,
formatMessage,
getRequiredFields,
getNumericFields,
};