Created modular system for calculators, added soap and candles calculators, universal components, updated backend
247 lines
7.2 KiB
TypeScript
247 lines
7.2 KiB
TypeScript
// Конфигурация калькулятора мыла
|
||
import { CalculatorConfig } from '@/lib/calculator-types';
|
||
import { calculateSoapStep, calculateSoapSubtotal, round } from './calc';
|
||
|
||
export const soapCalculatorConfig: CalculatorConfig = {
|
||
id: 'soap',
|
||
name: 'Калькулятор мыла',
|
||
description: 'Расчет себестоимости мыла ручной работы',
|
||
icon: '🧼',
|
||
|
||
fields: [
|
||
{
|
||
id: 'soapName',
|
||
type: 'text',
|
||
label: 'Название мыла',
|
||
placeholder: 'Введите название',
|
||
defaultValue: '',
|
||
gridCols: 1,
|
||
required: false,
|
||
},
|
||
{
|
||
id: 'photo',
|
||
type: 'file',
|
||
label: 'Фото мыла (необязательно)',
|
||
accept: 'image/*',
|
||
gridCols: 1,
|
||
required: false,
|
||
},
|
||
{
|
||
id: 'weight',
|
||
type: 'number',
|
||
label: 'Вес мыла, г',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'base',
|
||
showStepAfter: 'base',
|
||
},
|
||
{
|
||
id: 'basePrice',
|
||
type: 'number',
|
||
label: 'Цена основы, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'base',
|
||
},
|
||
{
|
||
id: 'aromaPrice',
|
||
type: 'number',
|
||
label: 'Цена отдушки, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'aroma',
|
||
showStepAfter: 'aroma',
|
||
},
|
||
{
|
||
id: 'aromaWeight',
|
||
type: 'number',
|
||
label: 'Фасовка отдушки, г',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'aroma',
|
||
},
|
||
{
|
||
id: 'pigmentPrice',
|
||
type: 'number',
|
||
label: 'Цена пигмента, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'pigment',
|
||
showStepAfter: 'pigment',
|
||
},
|
||
{
|
||
id: 'pigmentWeight',
|
||
type: 'number',
|
||
label: 'Фасовка пигмента, г',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'pigment',
|
||
},
|
||
{
|
||
id: 'moldPrice',
|
||
type: 'number',
|
||
label: 'Цена формы, руб',
|
||
defaultValue: '',
|
||
gridCols: 1,
|
||
required: false,
|
||
groupName: 'mold',
|
||
showStepAfter: 'mold',
|
||
},
|
||
{
|
||
id: 'box',
|
||
type: 'number',
|
||
label: 'Пакет/коробка, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'packaging',
|
||
showStepAfter: 'packaging',
|
||
},
|
||
{
|
||
id: 'filler',
|
||
type: 'number',
|
||
label: 'Наполнитель, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'packaging',
|
||
},
|
||
{
|
||
id: 'ribbon',
|
||
type: 'number',
|
||
label: 'Лента, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'packaging',
|
||
},
|
||
{
|
||
id: 'label',
|
||
type: 'number',
|
||
label: 'Наклейка, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'packaging',
|
||
},
|
||
{
|
||
id: 'markup',
|
||
type: 'number',
|
||
label: 'Наценка, %',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
},
|
||
],
|
||
|
||
calculationSteps: [
|
||
{
|
||
id: 'base',
|
||
name: 'Себестоимость основы',
|
||
formula: (values) => round(calculateSoapStep('base', values)),
|
||
formulaDescription: '(вес / 1000) * цена_основы',
|
||
},
|
||
{
|
||
id: 'aroma',
|
||
name: 'Себестоимость отдушки (1 %)',
|
||
formula: (values) => round(calculateSoapStep('aroma', values)),
|
||
formulaDescription: '((вес * 0.01) / фасовка_отдушки) * цена_отдушки',
|
||
},
|
||
{
|
||
id: 'pigment',
|
||
name: 'Себестоимость пигмента (0.5 %)',
|
||
formula: (values) => round(calculateSoapStep('pigment', values)),
|
||
formulaDescription: '((вес * 0.005) / фасовка_пигмента) * цена_пигмента',
|
||
},
|
||
{
|
||
id: 'mold',
|
||
name: 'Себестоимость формы',
|
||
formula: (values) => round(calculateSoapStep('mold', values)),
|
||
formulaDescription: 'цена_формы / 100',
|
||
},
|
||
{
|
||
id: 'packaging',
|
||
name: 'Стоимость упаковки',
|
||
formula: (values) => round(calculateSoapStep('packaging', values)),
|
||
formulaDescription: 'пакет + наполнитель + лента + наклейка',
|
||
},
|
||
],
|
||
|
||
subtotals: [
|
||
{
|
||
id: 'operational',
|
||
name: 'Операционные расходы (5 %)',
|
||
formula: (values, steps) => round(calculateSoapSubtotal('operational', values, steps)),
|
||
formulaDescription: '(основа + отдушка + пигмент + форма + упаковка) * 0.05',
|
||
},
|
||
{
|
||
id: 'total',
|
||
name: 'Итого себестоимость',
|
||
formula: (values, steps) => round(calculateSoapSubtotal('total', values, steps)),
|
||
highlight: true,
|
||
formulaDescription: 'основа + отдушка + пигмент + форма + упаковка + операционные',
|
||
},
|
||
],
|
||
|
||
additionalCalculations: [
|
||
{
|
||
id: 'finalPrice',
|
||
name: 'Итоговая цена с наценкой',
|
||
formula: (values, steps, subtotals) => {
|
||
const total = subtotals.total || 0;
|
||
const markup = values.markup || 0;
|
||
return round(total * (1 + markup / 100));
|
||
},
|
||
formulaDescription: 'итого_себестоимость * (1 + наценка / 100)',
|
||
},
|
||
{
|
||
id: 'pricePer100g',
|
||
name: 'Цена за 100 г',
|
||
formula: (values, steps, subtotals, additional) => {
|
||
const weight = values.weight || 0;
|
||
const finalPrice = additional?.finalPrice || 0;
|
||
if (weight > 0) {
|
||
return round((finalPrice / weight) * 100);
|
||
}
|
||
return 0;
|
||
},
|
||
formulaDescription: '(итоговая_цена / вес) * 100',
|
||
},
|
||
],
|
||
|
||
formatTelegramMessage: (values, steps, subtotals, additional) => {
|
||
const soapName = values.soapName || 'Без названия';
|
||
const weight = values.weight || 0;
|
||
const box = values.box || 0;
|
||
const filler = values.filler || 0;
|
||
const ribbon = values.ribbon || 0;
|
||
const label = values.label || 0;
|
||
const markup = values.markup || 0;
|
||
const totalCost = subtotals.total || 0;
|
||
const finalPrice = additional?.finalPrice || 0;
|
||
const pricePer100g = additional?.pricePer100g || 0;
|
||
|
||
let text = `🧼 <b>Расчёт мыла:</b> <i>${soapName}</i>\n\n`;
|
||
text += `⚖️ <b>Вес мыла:</b> ${weight} г\n\n`;
|
||
text += `📦 <b>Упаковка:</b>\n`;
|
||
text += ` 📥 Пакет/коробка: ${box} ₽\n`;
|
||
text += ` 🌾 Наполнитель: ${filler} ₽\n`;
|
||
text += ` 🎀 Лента: ${ribbon} ₽\n`;
|
||
text += ` 🏷️ Наклейка: ${label} ₽\n\n`;
|
||
text += `💹 <b>Наценка:</b> ${markup}%\n\n`;
|
||
text += `📊 <b>Итоги расчёта:</b>\n`;
|
||
text += ` 💵 Себестоимость: ${totalCost.toFixed(1)} ₽\n`;
|
||
text += ` 🎯 Итоговая цена с наценкой: ${finalPrice.toFixed(1)} ₽\n`;
|
||
text += ` ⚗️ Цена за 100 г: ${pricePer100g.toFixed(1)} ₽`;
|
||
|
||
return text;
|
||
},
|
||
};
|
||
|