@monesto/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/bin/monesto-cli.js +16 -0
- package/dist/cli.js +133 -0
- package/dist/cli.js.map +1 -0
- package/dist/domain/allocation.js +99 -0
- package/dist/domain/allocation.js.map +1 -0
- package/dist/domain/salarySplit.js +88 -0
- package/dist/domain/salarySplit.js.map +1 -0
- package/dist/domain/salarySplit.test.js +29 -0
- package/dist/domain/salarySplit.test.js.map +1 -0
- package/dist/domain/tax.js +50 -0
- package/dist/domain/tax.js.map +1 -0
- package/dist/services/config.js +41 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/rates.js +11 -0
- package/dist/services/rates.js.map +1 -0
- package/dist/ui/prompts.js +83 -0
- package/dist/ui/prompts.js.map +1 -0
- package/dist/ui/table.js +59 -0
- package/dist/ui/table.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @monesto/cli
|
|
2
|
+
|
|
3
|
+
CLI для расчёта чистого месячного дохода, аванса и зарплаты по рабочим дням (с учётом производственного календаря РФ) и распределения по активам: золото, USD, рубли.
|
|
4
|
+
|
|
5
|
+
## Установка
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @monesto/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Или запуск без установки:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @monesto/cli calculate --money 240000 --tax 13%
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Использование
|
|
18
|
+
|
|
19
|
+
### Команда `calculate`
|
|
20
|
+
|
|
21
|
+
Рассчитывает чистый доход после налога, суммы к выплате 10-го и 25-го числа и распределение по активам.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
monesto calculate [опции]
|
|
25
|
+
# или
|
|
26
|
+
npx @monesto/cli calculate [опции]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Опции
|
|
30
|
+
|
|
31
|
+
| Опция | Описание |
|
|
32
|
+
|-------|----------|
|
|
33
|
+
| `--money <number>` | Месячный доход до налога (RUB). |
|
|
34
|
+
| `--tax <значение>` | Налог: процент от гросса (`13%`) или фиксированная сумма (`31200`). |
|
|
35
|
+
| `--currency <code>` | Базовая валюта дохода (по умолчанию `rub`). |
|
|
36
|
+
| `--gold <значение>` | Доля в золото: процент (`10%`) или сумма в RUB (`50000`). |
|
|
37
|
+
| `--usd <значение>` | Доля в USD: процент (`10%`) или сумма в USD (`250`). |
|
|
38
|
+
| `--rub <значение>` | Доля в рублях: процент (`10%`) или сумма в RUB (`50000`). |
|
|
39
|
+
|
|
40
|
+
Если опции не указаны, недостающие параметры можно ввести интерактивно.
|
|
41
|
+
|
|
42
|
+
### Логика выплат
|
|
43
|
+
|
|
44
|
+
- **10-е число** — зарплата за период **16–конец прошлого месяца** (по рабочим дням, с учётом праздников РФ).
|
|
45
|
+
- **25-е число** — аванс за период **1–15 текущего месяца** (по рабочим дням).
|
|
46
|
+
|
|
47
|
+
Расчёт использует производственный календарь (выходные и нерабочие праздничные дни РФ).
|
|
48
|
+
|
|
49
|
+
### Конфигурация (monesto.json)
|
|
50
|
+
|
|
51
|
+
При первом запуске можно ввести доход, налог и распределение по активам. Эти значения сохраняются в `monesto.json` в каталоге конфигурации:
|
|
52
|
+
|
|
53
|
+
- **macOS / Linux:** `~/.config/monesto/monesto.json` (или `$XDG_CONFIG_HOME/monesto/monesto.json`)
|
|
54
|
+
- **Windows:** `%APPDATA%\monesto\monesto.json`
|
|
55
|
+
|
|
56
|
+
При следующих запусках сохранённые значения подставляются автоматически; флаги в командной строке имеют приоритет над конфигом.
|
|
57
|
+
|
|
58
|
+
### Примеры
|
|
59
|
+
|
|
60
|
+
Оклад 240 000 ₽, налог 13%, аванс и зарплата за март 2026:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
monesto calculate --money 240000 --tax 13%
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
С распределением по активам:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
monesto calculate --money 240000 --tax 13% --gold 10% --usd 250 --rub 10%
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Только расчёт без распределения по активам:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
monesto calculate --money 100000 --tax 0% --gold 0% --usd 0% --rub 0%
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Требования
|
|
79
|
+
|
|
80
|
+
- Node.js >= 18
|
|
81
|
+
|
|
82
|
+
## Лицензия
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ESM loader for the compiled CLI entry point
|
|
4
|
+
import('../dist/cli.js')
|
|
5
|
+
.then((mod) => {
|
|
6
|
+
if (typeof mod.main === 'function') {
|
|
7
|
+
return mod.main(process.argv);
|
|
8
|
+
}
|
|
9
|
+
console.error('Entry module does not export main()');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
})
|
|
12
|
+
.catch((error) => {
|
|
13
|
+
console.error('Failed to start @monesto/cli:', error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { allocateAssets } from './domain/allocation.js';
|
|
3
|
+
import { splitNetIncomeByWorkdays } from './domain/salarySplit.js';
|
|
4
|
+
import { calculateNetIncome } from './domain/tax.js';
|
|
5
|
+
import { getGoldPricePerGramRub, getUsdToRubRate } from './services/rates.js';
|
|
6
|
+
import { loadConfig, saveConfig } from './services/config.js';
|
|
7
|
+
import { askMissingParameters } from './ui/prompts.js';
|
|
8
|
+
import { renderAllocationTable, renderSummary } from './ui/table.js';
|
|
9
|
+
function mergeConfig(saved, args) {
|
|
10
|
+
const config = {
|
|
11
|
+
money: saved.money,
|
|
12
|
+
tax: saved.tax,
|
|
13
|
+
currency: saved.currency ?? 'rub',
|
|
14
|
+
imprestDate: saved.imprestDate,
|
|
15
|
+
gold: saved.gold,
|
|
16
|
+
usd: saved.usd,
|
|
17
|
+
rub: saved.rub
|
|
18
|
+
};
|
|
19
|
+
if (args.money != null) {
|
|
20
|
+
const value = Number(args.money);
|
|
21
|
+
if (Number.isFinite(value)) {
|
|
22
|
+
config.money = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (args.tax != null) {
|
|
26
|
+
config.tax = args.tax;
|
|
27
|
+
}
|
|
28
|
+
if (args.currency != null) {
|
|
29
|
+
config.currency = args.currency;
|
|
30
|
+
}
|
|
31
|
+
if (args.imprestDate != null) {
|
|
32
|
+
const value = Number(args.imprestDate);
|
|
33
|
+
if (Number.isFinite(value)) {
|
|
34
|
+
config.imprestDate = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (args.gold != null) {
|
|
38
|
+
config.gold = args.gold;
|
|
39
|
+
}
|
|
40
|
+
if (args.usd != null) {
|
|
41
|
+
config.usd = args.usd;
|
|
42
|
+
}
|
|
43
|
+
if (args.rub != null) {
|
|
44
|
+
config.rub = args.rub;
|
|
45
|
+
}
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
48
|
+
async function handleCalculate(options) {
|
|
49
|
+
const saved = await loadConfig();
|
|
50
|
+
const merged = mergeConfig(saved, options);
|
|
51
|
+
const completed = await askMissingParameters(merged);
|
|
52
|
+
if (completed.money == null) {
|
|
53
|
+
console.error('Не указан месячный доход (--money).');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const now = new Date();
|
|
57
|
+
const year = now.getFullYear();
|
|
58
|
+
const month = now.getMonth() + 1;
|
|
59
|
+
const gross = completed.money;
|
|
60
|
+
const taxInput = completed.tax;
|
|
61
|
+
const taxResult = calculateNetIncome(gross, taxInput);
|
|
62
|
+
const split = splitNetIncomeByWorkdays({
|
|
63
|
+
netIncome: taxResult.netIncome,
|
|
64
|
+
year,
|
|
65
|
+
month
|
|
66
|
+
});
|
|
67
|
+
const [usdToRub, goldPerGramRub] = await Promise.all([
|
|
68
|
+
getUsdToRubRate(),
|
|
69
|
+
getGoldPricePerGramRub()
|
|
70
|
+
]);
|
|
71
|
+
const allocation = allocateAssets({
|
|
72
|
+
netIncomeRub: taxResult.netIncome,
|
|
73
|
+
goldInput: completed.gold,
|
|
74
|
+
usdInput: completed.usd,
|
|
75
|
+
rubInput: completed.rub,
|
|
76
|
+
rates: {
|
|
77
|
+
usdToRub,
|
|
78
|
+
goldPerGramRub
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (!allocation.isValid) {
|
|
82
|
+
if (allocation.errorMessage) {
|
|
83
|
+
console.error(allocation.errorMessage);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
renderSummary(gross, taxResult, split);
|
|
88
|
+
renderAllocationTable(allocation, taxResult.netIncome);
|
|
89
|
+
await saveConfig({
|
|
90
|
+
money: gross,
|
|
91
|
+
tax: taxInput,
|
|
92
|
+
currency: completed.currency ?? 'rub',
|
|
93
|
+
imprestDate: completed.imprestDate ?? 25,
|
|
94
|
+
gold: completed.gold,
|
|
95
|
+
usd: completed.usd,
|
|
96
|
+
rub: completed.rub
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export async function main(argv) {
|
|
100
|
+
const program = new Command();
|
|
101
|
+
program.name('@monesto/cli').description('CLI для расчёта дохода и распределения по активам.');
|
|
102
|
+
program
|
|
103
|
+
.command('calculate')
|
|
104
|
+
.description('Рассчитать чистый доход, аванс/зарплату и распределение по активам.')
|
|
105
|
+
.option('--money <number>', 'Месячный доход до налога (RUB).')
|
|
106
|
+
.option('--tax <tax>', 'Налог: 13% или фиксированная сумма, например 31200.')
|
|
107
|
+
.option('--currency <code>', 'Базовая валюта дохода (v1: только rub).', 'rub')
|
|
108
|
+
.option('--imprest-date <day>', 'День месяца аванса (1-31).')
|
|
109
|
+
.option('--gold <value>', 'Инвестиции в золото: процент (10%) или сумма в RUB (50000).')
|
|
110
|
+
.option('--usd <value>', 'Инвестиции в USD: процент (10%) или сумма в USD (250).')
|
|
111
|
+
.option('--rub <value>', 'Инвестиции в RUB: процент (10%) или сумма в RUB (50000).')
|
|
112
|
+
.action(async (opts) => {
|
|
113
|
+
try {
|
|
114
|
+
await handleCalculate(opts);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error('Ошибка при выполнении расчёта:', error);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (argv.length <= 2) {
|
|
122
|
+
argv = [...argv, 'calculate'];
|
|
123
|
+
}
|
|
124
|
+
await program.parseAsync(argv);
|
|
125
|
+
}
|
|
126
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
127
|
+
// Direct execution of compiled CLI (dist/cli.js)
|
|
128
|
+
main(process.argv).catch((error) => {
|
|
129
|
+
console.error(error);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { parsePercentOrAmount } from './tax.js';
|
|
2
|
+
function computeFromInput(label, netIncomeRub, rawInput, rates) {
|
|
3
|
+
const parsed = parsePercentOrAmount(rawInput);
|
|
4
|
+
if (!parsed)
|
|
5
|
+
return null;
|
|
6
|
+
if (label === 'gold') {
|
|
7
|
+
let amountRub;
|
|
8
|
+
let percentOfNet;
|
|
9
|
+
if (parsed.kind === 'percent') {
|
|
10
|
+
amountRub = (netIncomeRub * parsed.value) / 100;
|
|
11
|
+
percentOfNet = parsed.value;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
amountRub = parsed.value;
|
|
15
|
+
}
|
|
16
|
+
const gramsOfGold = amountRub / rates.goldPerGramRub;
|
|
17
|
+
return {
|
|
18
|
+
asset: 'gold',
|
|
19
|
+
amountRub,
|
|
20
|
+
details: {
|
|
21
|
+
gramsOfGold,
|
|
22
|
+
percentOfNet
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (label === 'usd') {
|
|
27
|
+
let amountRub;
|
|
28
|
+
let usdAmount;
|
|
29
|
+
let percentOfNet;
|
|
30
|
+
if (parsed.kind === 'percent') {
|
|
31
|
+
amountRub = (netIncomeRub * parsed.value) / 100;
|
|
32
|
+
usdAmount = amountRub / rates.usdToRub;
|
|
33
|
+
percentOfNet = parsed.value;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
usdAmount = parsed.value;
|
|
37
|
+
amountRub = usdAmount * rates.usdToRub;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
asset: 'usd',
|
|
41
|
+
amountRub,
|
|
42
|
+
details: {
|
|
43
|
+
usdAmount,
|
|
44
|
+
percentOfNet
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// rub
|
|
49
|
+
let amountRub;
|
|
50
|
+
let percentOfNet;
|
|
51
|
+
if (parsed.kind === 'percent') {
|
|
52
|
+
amountRub = (netIncomeRub * parsed.value) / 100;
|
|
53
|
+
percentOfNet = parsed.value;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
amountRub = parsed.value;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
asset: 'rub',
|
|
60
|
+
amountRub,
|
|
61
|
+
details: {
|
|
62
|
+
percentOfNet
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function allocateAssets(params) {
|
|
67
|
+
const { netIncomeRub, goldInput, usdInput, rubInput, rates } = params;
|
|
68
|
+
if (!Number.isFinite(netIncomeRub) || netIncomeRub < 0) {
|
|
69
|
+
throw new Error('netIncomeRub must be a non-negative number');
|
|
70
|
+
}
|
|
71
|
+
const allocations = [];
|
|
72
|
+
const gold = computeFromInput('gold', netIncomeRub, goldInput, rates);
|
|
73
|
+
if (gold)
|
|
74
|
+
allocations.push(gold);
|
|
75
|
+
const usd = computeFromInput('usd', netIncomeRub, usdInput, rates);
|
|
76
|
+
if (usd)
|
|
77
|
+
allocations.push(usd);
|
|
78
|
+
const rub = computeFromInput('rub', netIncomeRub, rubInput, rates);
|
|
79
|
+
if (rub)
|
|
80
|
+
allocations.push(rub);
|
|
81
|
+
const totalAllocated = allocations.reduce((sum, a) => sum + a.amountRub, 0);
|
|
82
|
+
const remaining = netIncomeRub - totalAllocated;
|
|
83
|
+
if (totalAllocated > netIncomeRub + 1e-6) {
|
|
84
|
+
return {
|
|
85
|
+
allocations,
|
|
86
|
+
totalAllocated,
|
|
87
|
+
remaining,
|
|
88
|
+
isValid: false,
|
|
89
|
+
errorMessage: 'Суммарное распределение по активам превышает доступный чистый доход. Скорректируйте проценты или суммы.'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
allocations,
|
|
94
|
+
totalAllocated,
|
|
95
|
+
remaining,
|
|
96
|
+
isValid: true
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=allocation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allocation.js","sourceRoot":"","sources":["../../src/domain/allocation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** День месяца выплаты зарплаты (хвост прошлого месяца: 16–конец). */
|
|
2
|
+
const SALARY_PAY_DAY = 10;
|
|
3
|
+
/** День месяца выплаты аванса (первая половина текущего месяца: 1–15). */
|
|
4
|
+
const ADVANCE_PAY_DAY = 25;
|
|
5
|
+
/** Первый день периода «хвоста» в прошлом месяце (1–15 = аванс, 16–конец = зарплата 10-го). */
|
|
6
|
+
const PREV_TAIL_START_DAY = 16;
|
|
7
|
+
/** Последний день периода аванса в текущем месяце. */
|
|
8
|
+
const ADVANCE_END_DAY = 15;
|
|
9
|
+
/** Нерабочие праздничные дни РФ (месяц 1–12, день). По производственному календарю. */
|
|
10
|
+
const RU_HOLIDAYS = [
|
|
11
|
+
{ month: 1, day: 1 },
|
|
12
|
+
{ month: 1, day: 2 },
|
|
13
|
+
{ month: 1, day: 7 },
|
|
14
|
+
{ month: 2, day: 23 },
|
|
15
|
+
{ month: 3, day: 8 },
|
|
16
|
+
{ month: 5, day: 1 },
|
|
17
|
+
{ month: 5, day: 9 },
|
|
18
|
+
{ month: 6, day: 12 },
|
|
19
|
+
{ month: 11, day: 4 }
|
|
20
|
+
];
|
|
21
|
+
function isWeekend(date) {
|
|
22
|
+
const day = date.getDay();
|
|
23
|
+
return day === 0 || day === 6;
|
|
24
|
+
}
|
|
25
|
+
function isHoliday(year, month1Based, day) {
|
|
26
|
+
return RU_HOLIDAYS.some((h) => h.month === month1Based && h.day === day);
|
|
27
|
+
}
|
|
28
|
+
function isWorkday(year, month1Based, day) {
|
|
29
|
+
const d = new Date(year, month1Based - 1, day);
|
|
30
|
+
if (isWeekend(d))
|
|
31
|
+
return false;
|
|
32
|
+
if (isHoliday(year, month1Based, day))
|
|
33
|
+
return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function getLastDayOfMonth(year, monthIndex0Based) {
|
|
37
|
+
return new Date(year, monthIndex0Based + 1, 0).getDate();
|
|
38
|
+
}
|
|
39
|
+
/** Рабочие дни в диапазоне с учётом выходных и праздников РФ. */
|
|
40
|
+
function countWorkdaysInRange(year, month1Based, startDay, endDay) {
|
|
41
|
+
let count = 0;
|
|
42
|
+
for (let day = startDay; day <= endDay; day++) {
|
|
43
|
+
if (isWorkday(year, month1Based, day))
|
|
44
|
+
count++;
|
|
45
|
+
}
|
|
46
|
+
return count;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Делит месячный чистый доход на две выплаты:
|
|
50
|
+
* - 10-е текущего месяца — зарплата за рабочие дни 16–конец прошлого месяца;
|
|
51
|
+
* - 25-е текущего месяца — аванс за рабочие дни 1–15 текущего месяца.
|
|
52
|
+
*/
|
|
53
|
+
export function splitNetIncomeByWorkdays(params) {
|
|
54
|
+
const { netIncome, year, month } = params;
|
|
55
|
+
if (!Number.isFinite(netIncome) || netIncome < 0) {
|
|
56
|
+
throw new Error('netIncome must be a non-negative number');
|
|
57
|
+
}
|
|
58
|
+
const currMonthIndex0 = month - 1;
|
|
59
|
+
if (currMonthIndex0 < 0 || currMonthIndex0 > 11) {
|
|
60
|
+
throw new Error('month must be in range 1-12');
|
|
61
|
+
}
|
|
62
|
+
const prevMonthIndex0 = currMonthIndex0 === 0 ? 11 : currMonthIndex0 - 1;
|
|
63
|
+
const prevYear = currMonthIndex0 === 0 ? year - 1 : year;
|
|
64
|
+
const prevMonth1 = prevMonthIndex0 + 1;
|
|
65
|
+
const prevLastDay = getLastDayOfMonth(prevYear, prevMonthIndex0);
|
|
66
|
+
const salaryWorkdays = countWorkdaysInRange(prevYear, prevMonth1, PREV_TAIL_START_DAY, prevLastDay);
|
|
67
|
+
const prevMonthTotalWorkdays = countWorkdaysInRange(prevYear, prevMonth1, 1, prevLastDay);
|
|
68
|
+
const currMonth1 = currMonthIndex0 + 1;
|
|
69
|
+
const currLastDay = getLastDayOfMonth(year, currMonthIndex0);
|
|
70
|
+
const advanceWorkdays = countWorkdaysInRange(year, currMonth1, 1, ADVANCE_END_DAY);
|
|
71
|
+
const currMonthTotalWorkdays = countWorkdaysInRange(year, currMonth1, 1, currLastDay);
|
|
72
|
+
// 10.03 — доля от прошлого месяца: (рабочие 16–конец) / (все рабочие прошлого месяца)
|
|
73
|
+
const salaryAmount = prevMonthTotalWorkdays > 0 ? (netIncome * salaryWorkdays) / prevMonthTotalWorkdays : 0;
|
|
74
|
+
// 25.03 — доля от марта: (рабочие 1–15) / (все рабочие марта)
|
|
75
|
+
const imprestAmount = currMonthTotalWorkdays > 0 ? (netIncome * advanceWorkdays) / currMonthTotalWorkdays : 0;
|
|
76
|
+
return {
|
|
77
|
+
imprestAmount,
|
|
78
|
+
salaryAmount,
|
|
79
|
+
imprestDate: new Date(year, currMonthIndex0, ADVANCE_PAY_DAY),
|
|
80
|
+
salaryDate: new Date(year, currMonthIndex0, SALARY_PAY_DAY),
|
|
81
|
+
workdays: {
|
|
82
|
+
total: salaryWorkdays + advanceWorkdays,
|
|
83
|
+
imprest: advanceWorkdays,
|
|
84
|
+
salary: salaryWorkdays
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=salarySplit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salarySplit.js","sourceRoot":"","sources":["../../src/domain/salarySplit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { splitNetIncomeByWorkdays } from './salarySplit.js';
|
|
3
|
+
describe('splitNetIncomeByWorkdays', () => {
|
|
4
|
+
it('как в расчётном листке: оклад 240000, налог 13%, 10.03 = 98 905.21 за 16–конец февраля (9 раб.дн.)', () => {
|
|
5
|
+
const gross = 240_000;
|
|
6
|
+
const taxPercent = 13;
|
|
7
|
+
const netIncome = gross * (1 - taxPercent / 100); // 208_800
|
|
8
|
+
const year = 2026;
|
|
9
|
+
const month = 3; // март: 10.03 = хвост февраля, 25.03 = аванс марта
|
|
10
|
+
const result = splitNetIncomeByWorkdays({ netIncome, year, month });
|
|
11
|
+
expect(result.salaryDate.getDate()).toBe(10);
|
|
12
|
+
expect(result.salaryDate.getMonth()).toBe(2);
|
|
13
|
+
expect(result.salaryDate.getFullYear()).toBe(2026);
|
|
14
|
+
expect(result.imprestDate.getDate()).toBe(25);
|
|
15
|
+
// Февраль 2026: 1–15 = 10 раб.дн., 16–28 (23 праздник) = 9 раб.дн., всего 19
|
|
16
|
+
expect(result.workdays.salary).toBe(9);
|
|
17
|
+
expect(result.workdays.total).toBe(9 + 10); // 9 (фев 16–28) + 10 (март 1–15)
|
|
18
|
+
// 10.03 = 208_800 * (9/19) = 98_905,26 ≈ 98_905,21 по листку
|
|
19
|
+
expect(result.salaryAmount).toBeCloseTo(98_905.21, 0);
|
|
20
|
+
});
|
|
21
|
+
it('100000 нетто: 10.03 = доля от февраля (16–конец), 25.03 = доля от марта (1–15)', () => {
|
|
22
|
+
const result = splitNetIncomeByWorkdays({ netIncome: 100_000, year: 2026, month: 3 });
|
|
23
|
+
expect(result.workdays.salary).toBe(9); // фев 16–28 без 23.02
|
|
24
|
+
expect(result.workdays.imprest).toBe(10); // март 1–15 (8 марта 2026 — вс, уже выходной)
|
|
25
|
+
expect(result.salaryAmount).toBeCloseTo(100_000 * (9 / 19), 2); // 47_368.42
|
|
26
|
+
expect(result.imprestAmount).toBeCloseTo(100_000 * (10 / 22), 2); // 45_454.55
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=salarySplit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salarySplit.test.js","sourceRoot":"","sources":["../../src/domain/salarySplit.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,oGAAoG,EAAE,GAAG,EAAE;QAC5G,MAAM,KAAK,GAAG,OAAO,CAAC;QACtB,MAAM,UAAU,GAAG,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,UAAU;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC;QAClB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,mDAAmD;QAEpE,MAAM,MAAM,GAAG,wBAAwB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAEpE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAE9C,6EAA6E;QAC7E,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,iCAAiC;QAE7E,6DAA6D;QAC7D,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,MAAM,GAAG,wBAAwB,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAEtF,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;QAC9D,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,8CAA8C;QACxF,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;QAC5E,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;IAChF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function parsePercentOrAmount(input) {
|
|
2
|
+
if (input == null)
|
|
3
|
+
return null;
|
|
4
|
+
if (typeof input === 'number') {
|
|
5
|
+
if (!Number.isFinite(input) || input < 0)
|
|
6
|
+
return null;
|
|
7
|
+
return { kind: 'amount', value: input };
|
|
8
|
+
}
|
|
9
|
+
const trimmed = input.trim();
|
|
10
|
+
if (!trimmed)
|
|
11
|
+
return null;
|
|
12
|
+
if (trimmed.endsWith('%')) {
|
|
13
|
+
const num = Number(trimmed.slice(0, -1));
|
|
14
|
+
if (!Number.isFinite(num) || num < 0)
|
|
15
|
+
return null;
|
|
16
|
+
return { kind: 'percent', value: num };
|
|
17
|
+
}
|
|
18
|
+
const num = Number(trimmed);
|
|
19
|
+
if (!Number.isFinite(num) || num < 0)
|
|
20
|
+
return null;
|
|
21
|
+
return { kind: 'amount', value: num };
|
|
22
|
+
}
|
|
23
|
+
export function calculateNetIncome(gross, taxInput) {
|
|
24
|
+
if (!Number.isFinite(gross) || gross < 0) {
|
|
25
|
+
throw new Error('Gross income must be a non-negative number');
|
|
26
|
+
}
|
|
27
|
+
const parsed = parsePercentOrAmount(taxInput);
|
|
28
|
+
if (!parsed) {
|
|
29
|
+
return {
|
|
30
|
+
taxAmount: 0,
|
|
31
|
+
netIncome: gross
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
let taxAmount;
|
|
35
|
+
if (parsed.kind === 'percent') {
|
|
36
|
+
taxAmount = (gross * parsed.value) / 100;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
taxAmount = parsed.value;
|
|
40
|
+
}
|
|
41
|
+
if (taxAmount > gross) {
|
|
42
|
+
taxAmount = gross;
|
|
43
|
+
}
|
|
44
|
+
const netIncome = gross - taxAmount;
|
|
45
|
+
return {
|
|
46
|
+
taxAmount,
|
|
47
|
+
netIncome
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=tax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tax.js","sourceRoot":"","sources":["../../src/domain/tax.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,oBAAoB,CAAC,KAAuB;IAC1D,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAE/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACtD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAClD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,QAAiB;IACjE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,KAAK;SACjB,CAAC;IACJ,CAAC;IAED,IAAI,SAAiB,CAAC;IAEtB,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,SAAS,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,CAAC;IAED,IAAI,SAAS,GAAG,KAAK,EAAE,CAAC;QACtB,SAAS,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;IAEpC,OAAO;QACL,SAAS;QACT,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
function getConfigDir() {
|
|
5
|
+
const platform = process.platform;
|
|
6
|
+
if (platform === 'win32') {
|
|
7
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
8
|
+
return path.join(appData, 'monesto');
|
|
9
|
+
}
|
|
10
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
11
|
+
if (xdg) {
|
|
12
|
+
return path.join(xdg, 'monesto');
|
|
13
|
+
}
|
|
14
|
+
const configDir = path.join(os.homedir(), '.config', 'monesto');
|
|
15
|
+
return configDir;
|
|
16
|
+
}
|
|
17
|
+
function getConfigPath() {
|
|
18
|
+
return path.join(getConfigDir(), 'monesto.json');
|
|
19
|
+
}
|
|
20
|
+
export async function loadConfig() {
|
|
21
|
+
const filePath = getConfigPath();
|
|
22
|
+
try {
|
|
23
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return parsed || {};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function saveConfig(config) {
|
|
35
|
+
const dir = getConfigDir();
|
|
36
|
+
const filePath = getConfigPath();
|
|
37
|
+
await fs.mkdir(dir, { recursive: true });
|
|
38
|
+
const serialized = JSON.stringify(config, null, 2);
|
|
39
|
+
await fs.writeFile(filePath, serialized, 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/services/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AAYpC,SAAS,YAAY;IACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAElC,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACrF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACxC,IAAI,GAAG,EAAE,CAAC;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAChE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,cAAc,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;QAChD,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YACnE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAqB;IACpD,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IAEjC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnD,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export async function getUsdToRubRate() {
|
|
2
|
+
// Mock implementation with a small artificial delay
|
|
3
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
4
|
+
return 95;
|
|
5
|
+
}
|
|
6
|
+
export async function getGoldPricePerGramRub() {
|
|
7
|
+
// Mock implementation with a small artificial delay
|
|
8
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
9
|
+
return 7000;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=rates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rates.js","sourceRoot":"","sources":["../../src/services/rates.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,oDAAoD;IACpD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,oDAAoD;IACpD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACxD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
export async function askMissingParameters(existing) {
|
|
3
|
+
const questions = [];
|
|
4
|
+
if (existing.money == null) {
|
|
5
|
+
questions.push({
|
|
6
|
+
type: 'number',
|
|
7
|
+
name: 'money',
|
|
8
|
+
message: 'Месячный доход (до налога), RUB:',
|
|
9
|
+
min: 0
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
if (existing.tax == null) {
|
|
13
|
+
questions.push({
|
|
14
|
+
type: 'text',
|
|
15
|
+
name: 'tax',
|
|
16
|
+
message: 'Налог (например, 13% или 31200):',
|
|
17
|
+
validate: (value) => (value && value.trim().length > 0 ? true : 'Укажите налог или оставьте флагом')
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (existing.gold == null && existing.usd == null && existing.rub == null) {
|
|
21
|
+
questions.push({
|
|
22
|
+
type: 'text',
|
|
23
|
+
name: 'gold',
|
|
24
|
+
message: 'Распределение в золото (например, 10% или 50000 RUB). Оставьте пустым, чтобы пропустить:',
|
|
25
|
+
initial: ''
|
|
26
|
+
}, {
|
|
27
|
+
type: 'text',
|
|
28
|
+
name: 'usd',
|
|
29
|
+
message: 'Распределение в USD (например, 10% или 250 USD). Оставьте пустым, чтобы пропустить:',
|
|
30
|
+
initial: ''
|
|
31
|
+
}, {
|
|
32
|
+
type: 'text',
|
|
33
|
+
name: 'rub',
|
|
34
|
+
message: 'Распределение в RUB (например, 10% или 50000 RUB). Оставьте пустым, чтобы пропустить:',
|
|
35
|
+
initial: ''
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (questions.length === 0) {
|
|
39
|
+
return existing;
|
|
40
|
+
}
|
|
41
|
+
const answers = await prompts(questions);
|
|
42
|
+
const merged = {
|
|
43
|
+
...existing,
|
|
44
|
+
...answers
|
|
45
|
+
};
|
|
46
|
+
if (typeof merged.money === 'string') {
|
|
47
|
+
const parsedMoney = Number(merged.money);
|
|
48
|
+
merged.money = Number.isFinite(parsedMoney) ? parsedMoney : existing.money;
|
|
49
|
+
}
|
|
50
|
+
merged.imprestDate = merged.imprestDate ?? existing.imprestDate ?? 25;
|
|
51
|
+
if (merged.gold != null && merged.gold !== '') {
|
|
52
|
+
merged.gold = String(merged.gold);
|
|
53
|
+
}
|
|
54
|
+
else if (existing.gold != null) {
|
|
55
|
+
merged.gold = existing.gold;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
merged.gold = undefined;
|
|
59
|
+
}
|
|
60
|
+
if (merged.usd != null && merged.usd !== '') {
|
|
61
|
+
merged.usd = String(merged.usd);
|
|
62
|
+
}
|
|
63
|
+
else if (existing.usd != null) {
|
|
64
|
+
merged.usd = existing.usd;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
merged.usd = undefined;
|
|
68
|
+
}
|
|
69
|
+
if (merged.rub != null && merged.rub !== '') {
|
|
70
|
+
merged.rub = String(merged.rub);
|
|
71
|
+
}
|
|
72
|
+
else if (existing.rub != null) {
|
|
73
|
+
merged.rub = existing.rub;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
merged.rub = undefined;
|
|
77
|
+
}
|
|
78
|
+
if (!merged.currency) {
|
|
79
|
+
merged.currency = 'rub';
|
|
80
|
+
}
|
|
81
|
+
return merged;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ui/prompts.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/table.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Table from 'cli-table3';
|
|
2
|
+
function formatCurrency(value) {
|
|
3
|
+
return value.toFixed(2);
|
|
4
|
+
}
|
|
5
|
+
export function renderSummary(gross, tax, split) {
|
|
6
|
+
console.log('--- Доход и выплаты ---');
|
|
7
|
+
console.log(`Гросс-доход: ${formatCurrency(gross)} RUB`);
|
|
8
|
+
console.log(`Налог: ${formatCurrency(tax.taxAmount)} RUB`);
|
|
9
|
+
console.log(`Чистый доход: ${formatCurrency(tax.netIncome)} RUB`);
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log('--- Аванс и зарплата (по рабочим дням) ---');
|
|
12
|
+
console.log(`Зарплата (${split.salaryDate.toLocaleDateString()}): ${formatCurrency(split.salaryAmount)} RUB — за 16–конец прошлого месяца`);
|
|
13
|
+
console.log(`Аванс (${split.imprestDate.toLocaleDateString()}): ${formatCurrency(split.imprestAmount)} RUB — за 1–15 текущего месяца`);
|
|
14
|
+
console.log(`Рабочие дни: всего=${split.workdays.total}, зарплата (16–конец прошл.)=${split.workdays.salary}, аванс (1–15 тек.)=${split.workdays.imprest}`);
|
|
15
|
+
console.log('');
|
|
16
|
+
}
|
|
17
|
+
export function renderAllocationTable(allocation, netIncomeRub) {
|
|
18
|
+
console.log('--- Распределение по активам ---');
|
|
19
|
+
const table = new Table({
|
|
20
|
+
head: ['Актив', 'Описание', 'Сумма, RUB', 'Доп. данные']
|
|
21
|
+
});
|
|
22
|
+
for (const item of allocation.allocations) {
|
|
23
|
+
const { asset, amountRub, details } = item;
|
|
24
|
+
const percent = details.percentOfNet != null ? `${details.percentOfNet.toFixed(2)}% от чистого дохода` : '';
|
|
25
|
+
if (asset === 'gold') {
|
|
26
|
+
table.push([
|
|
27
|
+
'Золото',
|
|
28
|
+
'Инвестиции в золото',
|
|
29
|
+
formatCurrency(amountRub),
|
|
30
|
+
`~${(details.gramsOfGold ?? 0).toFixed(3)} г${percent ? `, ${percent}` : ''}`
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
else if (asset === 'usd') {
|
|
34
|
+
table.push([
|
|
35
|
+
'USD',
|
|
36
|
+
'Инвестиции в доллары США',
|
|
37
|
+
formatCurrency(amountRub),
|
|
38
|
+
`${(details.usdAmount ?? 0).toFixed(2)} USD${percent ? `, ${percent}` : ''}`
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
table.push([
|
|
43
|
+
'RUB',
|
|
44
|
+
'Оставляем в рублях',
|
|
45
|
+
formatCurrency(amountRub),
|
|
46
|
+
percent
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
console.log(table.toString());
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(`Итого распределено: ${formatCurrency(allocation.totalAllocated)} RUB`);
|
|
53
|
+
console.log(`Остаток: ${formatCurrency(allocation.remaining)} RUB`);
|
|
54
|
+
if (!allocation.isValid && allocation.errorMessage) {
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log('ВНИМАНИЕ: ' + allocation.errorMessage);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/ui/table.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monesto/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI для расчёта чистого дохода, аванса и зарплаты по рабочим дням и распределения по активам (золото, USD, рубли)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"monesto": "bin/monesto-cli.js",
|
|
8
|
+
"@monesto/cli": "bin/monesto-cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"start": "node ./bin/monesto-cli.js",
|
|
13
|
+
"dev": "node --enable-source-maps ./dist/cli.js",
|
|
14
|
+
"check-types": "tsc -p tsconfig.json --noEmit",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"test": "vitest",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin",
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"monesto",
|
|
25
|
+
"cli",
|
|
26
|
+
"salary",
|
|
27
|
+
"income",
|
|
28
|
+
"allocation",
|
|
29
|
+
"ruble",
|
|
30
|
+
"usd",
|
|
31
|
+
"gold"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"cli-table3": "^0.6.5",
|
|
39
|
+
"commander": "^12.1.0",
|
|
40
|
+
"prompts": "^2.4.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@repo/eslint-config": "*",
|
|
44
|
+
"@repo/typescript-config": "*",
|
|
45
|
+
"eslint": "^9.39.1",
|
|
46
|
+
"tslib": "^2.8.1",
|
|
47
|
+
"typescript": "5.9.2",
|
|
48
|
+
"vitest": "^3.2.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|