Created modular system for calculators, added soap and candles calculators, universal components, updated backend
247 lines
7.5 KiB
TypeScript
247 lines
7.5 KiB
TypeScript
// Конфигурация калькулятора свечей
|
||
import { CalculatorConfig } from '@/lib/calculator-types';
|
||
import { calculateCandleStep, calculateCandleSubtotal, round } from './calc';
|
||
|
||
export const candlesCalculatorConfig: CalculatorConfig = {
|
||
id: 'candles',
|
||
name: 'Калькулятор свечей',
|
||
description: 'Расчет себестоимости свечей ручной работы',
|
||
icon: '🕯️',
|
||
|
||
fields: [
|
||
{
|
||
id: 'candleName',
|
||
type: 'text',
|
||
label: 'Название свечи',
|
||
placeholder: 'Введите название',
|
||
defaultValue: '',
|
||
gridCols: 1,
|
||
required: false,
|
||
},
|
||
{
|
||
id: 'photo',
|
||
type: 'file',
|
||
label: 'Фото свечи (необязательно)',
|
||
accept: 'image/*',
|
||
gridCols: 1,
|
||
required: false,
|
||
},
|
||
{
|
||
id: 'waxWeight',
|
||
type: 'number',
|
||
label: 'Вес воска, г',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'wax',
|
||
showStepAfter: 'wax',
|
||
},
|
||
{
|
||
id: 'waxPrice',
|
||
type: 'number',
|
||
label: 'Цена воска за 1 кг, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'wax',
|
||
},
|
||
{
|
||
id: 'wickCount',
|
||
type: 'number',
|
||
label: 'Количество фитилей',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'wick',
|
||
showStepAfter: 'wick',
|
||
},
|
||
{
|
||
id: 'wickPrice',
|
||
type: 'number',
|
||
label: 'Цена одного фитиля, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'wick',
|
||
},
|
||
{
|
||
id: 'fragrancePrice',
|
||
type: 'number',
|
||
label: 'Цена отдушки, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'fragrance',
|
||
showStepAfter: 'fragrance',
|
||
},
|
||
{
|
||
id: 'fragranceWeight',
|
||
type: 'number',
|
||
label: 'Фасовка отдушки, г',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'fragrance',
|
||
},
|
||
{
|
||
id: 'dyePrice',
|
||
type: 'number',
|
||
label: 'Цена красителя, руб',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'dye',
|
||
showStepAfter: 'dye',
|
||
},
|
||
{
|
||
id: 'dyeWeight',
|
||
type: 'number',
|
||
label: 'Фасовка красителя, г',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
groupName: 'dye',
|
||
},
|
||
{
|
||
id: 'moldPrice',
|
||
type: 'number',
|
||
label: 'Стоимость формы/банки, руб',
|
||
defaultValue: '',
|
||
gridCols: 1,
|
||
required: false,
|
||
groupName: 'mold',
|
||
showStepAfter: 'mold',
|
||
},
|
||
{
|
||
id: 'packaging',
|
||
type: 'number',
|
||
label: 'Упаковка, руб',
|
||
defaultValue: '',
|
||
gridCols: 1,
|
||
required: false,
|
||
groupName: 'packaging',
|
||
showStepAfter: 'packaging',
|
||
},
|
||
{
|
||
id: 'markup',
|
||
type: 'number',
|
||
label: 'Наценка, %',
|
||
defaultValue: '',
|
||
gridCols: 2,
|
||
required: false,
|
||
},
|
||
],
|
||
|
||
calculationSteps: [
|
||
{
|
||
id: 'wax',
|
||
name: 'Себестоимость воска',
|
||
formula: (values) => round(calculateCandleStep('wax', values)),
|
||
formulaDescription: '(вес_воска / 1000) * цена_воска',
|
||
},
|
||
{
|
||
id: 'wick',
|
||
name: 'Себестоимость фитилей',
|
||
formula: (values) => round(calculateCandleStep('wick', values)),
|
||
formulaDescription: 'количество_фитилей * цена_фитиля',
|
||
},
|
||
{
|
||
id: 'fragrance',
|
||
name: 'Себестоимость отдушки (10 %)',
|
||
formula: (values) => round(calculateCandleStep('fragrance', values)),
|
||
formulaDescription: '((вес_воска * 0.10) / фасовка_отдушки) * цена_отдушки',
|
||
},
|
||
{
|
||
id: 'dye',
|
||
name: 'Себестоимость красителя (1 %)',
|
||
formula: (values) => round(calculateCandleStep('dye', values)),
|
||
formulaDescription: '((вес_воска * 0.01) / фасовка_красителя) * цена_красителя',
|
||
},
|
||
{
|
||
id: 'mold',
|
||
name: 'Стоимость формы/банки',
|
||
formula: (values) => {
|
||
const moldPrice = values.moldPrice || 0;
|
||
// Предположим, форма рассчитана на 100 использований
|
||
return round(moldPrice / 100);
|
||
},
|
||
formulaDescription: 'стоимость_формы / 100',
|
||
},
|
||
{
|
||
id: 'packaging',
|
||
name: 'Стоимость упаковки',
|
||
formula: (values) => {
|
||
const packaging = values.packaging || 0;
|
||
return round(packaging);
|
||
},
|
||
formulaDescription: 'стоимость_упаковки',
|
||
},
|
||
],
|
||
|
||
subtotals: [
|
||
{
|
||
id: 'operational',
|
||
name: 'Операционные расходы (5 %)',
|
||
formula: (values, steps) => round(calculateCandleSubtotal('operational', values, steps)),
|
||
formulaDescription: '(воск + фитили + отдушка + краситель + форма + упаковка) * 0.05',
|
||
},
|
||
{
|
||
id: 'total',
|
||
name: 'Итого себестоимость',
|
||
formula: (values, steps) => round(calculateCandleSubtotal('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.waxWeight || 0;
|
||
const finalPrice = additional?.finalPrice || 0;
|
||
if (weight > 0) {
|
||
return round((finalPrice / weight) * 100);
|
||
}
|
||
return 0;
|
||
},
|
||
formulaDescription: '(итоговая_цена / вес_воска) * 100',
|
||
},
|
||
],
|
||
|
||
formatTelegramMessage: (values, steps, subtotals, additional) => {
|
||
const candleName = values.candleName || 'Без названия';
|
||
const waxWeight = values.waxWeight || 0;
|
||
const wickCount = values.wickCount || 0;
|
||
const packaging = values.packaging || 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>${candleName}</i>\n\n`;
|
||
text += `⚖️ <b>Вес воска:</b> ${waxWeight} г\n`;
|
||
text += `🕯️ <b>Количество фитилей:</b> ${wickCount} шт\n`;
|
||
text += `📦 <b>Упаковка:</b> ${packaging} ₽\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;
|
||
},
|
||
};
|
||
|