DoSoapCalc/frontend/docs/calculator-creation-guide.md
DosAi 02c7520c90 Refactor: Modular calculator architecture
Created modular system for calculators, added soap and candles calculators, universal components, updated backend
2025-11-02 15:45:07 +03:00

387 lines
12 KiB
Markdown
Raw Permalink 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.

# Руководство по созданию новых калькуляторов
Это руководство поможет вам быстро создать новый калькулятор в модульной системе.
## Структура модуля калькулятора
Каждый калькулятор состоит из двух файлов в папке `frontend/calculators/[название]/`:
- `config.ts` — конфигурация калькулятора (поля, формулы, подитоги)
- `calc.ts` — функции расчета (опционально, если нужны сложные вычисления)
## Шаг 1: Создание папки модуля
Создайте папку для вашего калькулятора:
```
frontend/calculators/candles/
```
## Шаг 2: Создание файла calc.ts (если нужны функции расчета)
Если у вас простые формулы, можно обойтись без этого файла и указать формулы прямо в конфигурации.
Пример для калькулятора свечей:
```typescript
// calculators/candles/calc.ts
export function calculateCandleStep(
stepId: string,
values: Record<string, number>
): number {
const { waxWeight = 0, waxPrice = 0, wickCount = 0, wickPrice = 0 } = values;
switch (stepId) {
case 'wax':
return (waxWeight / 1000) * waxPrice;
case 'wick':
return wickCount * wickPrice;
default:
return 0;
}
}
export function round(val: number): number {
return Math.round(val * 10) / 10;
}
```
## Шаг 3: Создание файла config.ts
Это основной файл конфигурации калькулятора:
```typescript
// calculators/candles/config.ts
import { CalculatorConfig } from '@/lib/calculator-types';
import { calculateCandleStep, 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: 'waxWeight',
type: 'number',
label: 'Вес воска, г',
defaultValue: '',
gridCols: 2,
},
{
id: 'waxPrice',
type: 'number',
label: 'Цена воска за 1 кг, руб',
defaultValue: '',
gridCols: 2,
},
{
id: 'wickCount',
type: 'number',
label: 'Количество фитилей',
defaultValue: '',
gridCols: 2,
},
{
id: 'wickPrice',
type: 'number',
label: 'Цена одного фитиля, руб',
defaultValue: '',
gridCols: 2,
},
],
// Шаги расчета
calculationSteps: [
{
id: 'wax',
name: 'Себестоимость воска',
formula: (values) => round(calculateCandleStep('wax', values)),
formulaDescription: '(весоска / 1000) * ценаоска',
},
{
id: 'wick',
name: 'Себестоимость фитилей',
formula: (values) => round(calculateCandleStep('wick', values)),
formulaDescription: оличество_фитилей * цена_фитиля',
},
],
// Подитоги
subtotals: [
{
id: 'total',
name: 'Итого себестоимость',
formula: (values, steps) => {
return round((steps.wax || 0) + (steps.wick || 0));
},
highlight: true,
formulaDescription: 'воск + фитили',
},
],
// Дополнительные расчеты (опционально)
additionalCalculations: [
{
id: 'pricePerCandle',
name: 'Цена за свечу',
formula: (values, steps, subtotals) => {
const total = subtotals.total || 0;
return round(total);
},
},
],
// Форматирование сообщения для Telegram (опционально)
formatTelegramMessage: (values, steps, subtotals, additional) => {
const candleName = values.candleName || 'Без названия';
const totalCost = subtotals.total || 0;
let text = `🕯️ <b>Расчёт свечи:</b> <i>${candleName}</i>\n\n`;
text += `📊 <b>Итоги расчёта:</b>\n`;
text += ` 💵 Себестоимость: ${totalCost.toFixed(1)}\n`;
return text;
},
};
```
## Шаг 4: Регистрация калькулятора
Откройте файл `frontend/lib/calculator-registry.ts` и добавьте регистрацию:
```typescript
import { candlesCalculatorConfig } from '@/calculators/candles/config';
// В функции initializeCalculators():
registerCalculator(candlesCalculatorConfig);
```
## Типы полей
### text
Текстовое поле ввода:
```typescript
{
id: 'productName',
type: 'text',
label: 'Название продукта',
defaultValue: '',
gridCols: 1, // 1 или 2 колонки
}
```
### number
Числовое поле ввода:
```typescript
{
id: 'weight',
type: 'number',
label: 'Вес, г',
defaultValue: '',
gridCols: 2,
validation: {
min: 0,
max: 10000,
},
// Группировка полей (опционально)
groupName: 'base', // Имя группы для группировки связанных полей
showStepAfter: 'base', // ID шага расчета, который показать после этой группы
}
```
### file
Поле для загрузки файла (обычно фото):
```typescript
{
id: 'photo',
type: 'file',
label: 'Фото продукта',
accept: 'image/*',
gridCols: 1,
}
```
## Группировка полей
Для правильного расположения блоков расчета сразу после соответствующих полей используйте группировку:
```typescript
fields: [
// Группа "base" - после этих полей покажется расчет "base"
{
id: 'weight',
type: 'number',
label: 'Вес, г',
groupName: 'base',
showStepAfter: 'base', // Первое поле группы должно иметь showStepAfter
},
{
id: 'price',
type: 'number',
label: 'Цена, руб',
groupName: 'base', // Та же группа
},
// Блок расчета "base" автоматически появится здесь
// Группа "packaging"
{
id: 'box',
type: 'number',
label: 'Коробка, руб',
groupName: 'packaging',
showStepAfter: 'packaging',
},
{
id: 'ribbon',
type: 'number',
label: 'Лента, руб',
groupName: 'packaging',
},
// Блок расчета "packaging" автоматически появится здесь
]
```
## Формулы расчета
Формулы — это функции, которые принимают значения полей и возвращают число:
```typescript
{
id: 'baseCost',
name: 'Базовая стоимость',
formula: (values) => {
const weight = values.weight || 0;
const price = values.price || 0;
return weight * price;
},
formulaDescription: 'вес * цена', // Опционально, для документации
}
```
В формуле доступны все значения полей через объект `values`, где ключ — это `id` поля.
## Подитоги
Подитоги могут зависеть от шагов расчета:
```typescript
{
id: 'total',
name: 'Итого',
formula: (values, steps) => {
// values — значения полей
// steps — результаты всех шагов расчета
return (steps.base || 0) + (steps.packaging || 0);
},
highlight: true, // Выделить итоговое значение
}
```
## Дополнительные расчеты
Используются для расчетов, которые зависят от подитогов или других дополнительных расчетов:
```typescript
{
id: 'finalPrice',
name: 'Итоговая цена',
formula: (values, steps, subtotals, additional) => {
const total = subtotals.total || 0;
const markup = values.markup || 0;
return total * (1 + markup / 100);
},
}
```
**Важно**: Дополнительные расчеты выполняются последовательно. Если один расчет зависит от другого, расположите зависимый расчет **после** того, от которого он зависит:
```typescript
additionalCalculations: [
{
id: 'finalPrice',
name: 'Итоговая цена с наценкой',
formula: (values, steps, subtotals) => {
const total = subtotals.total || 0;
const markup = values.markup || 0;
return total * (1 + markup / 100);
},
},
{
id: 'pricePer100g',
name: 'Цена за 100 г',
formula: (values, steps, subtotals, additional) => {
// Используем уже рассчитанный finalPrice из additional
const finalPrice = additional?.finalPrice || 0;
const weight = values.weight || 0;
if (weight > 0) {
return (finalPrice / weight) * 100;
}
return 0;
},
},
]
```
## Форматирование сообщения для Telegram
Если не указать `formatTelegramMessage`, будет использовано универсальное форматирование. Но лучше создать свою функцию:
```typescript
formatTelegramMessage: (values, steps, subtotals, additional) => {
// values — все поля формы (строки или числа)
// steps — результаты шагов расчета
// subtotals — результаты подитогов
// additional — результаты дополнительных расчетов
let text = `📊 <b>Расчёт:</b>\n\n`;
text += `Итого: ${subtotals.total.toFixed(1)}\n`;
return text;
}
```
## Проверка работы
1. Убедитесь, что калькулятор появился в меню на главной странице
2. Заполните форму и проверьте расчеты
3. Отправьте результат в Telegram и убедитесь, что сообщение корректно форматировано
## Советы
- Используйте функцию `round()` для округления результатов (обычно до 1 знака после запятой)
- Для сложных расчетов выносите логику в `calc.ts`
- Группируйте связанные поля визуально, используя `gridCols`
- Всегда указывайте `formulaDescription` для документации
- Тестируйте с нулевыми и пустыми значениями
## Примеры
Полные примеры калькуляторов можно найти в:
**Калькулятор мыла:**
- `frontend/calculators/soap/config.ts`
- `frontend/calculators/soap/calc.ts`
**Калькулятор свечей (пример простого калькулятора):**
- `frontend/calculators/candles/config.ts`
- `frontend/calculators/candles/calc.ts`
Оба примера демонстрируют использование группировки полей, шагов расчета, подитогов и дополнительных расчетов.