el-contador 1.2.8 → 1.2.10
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 +3 -7
- package/bin/cli.js +7 -10
- package/package.json +2 -1
- package/server/index.js +7 -0
- package/server/lib/fiscal.js +113 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ You do **not** need to install PostgreSQL on the host or create a database/user
|
|
|
49
49
|
|
|
50
50
|
On a new server (VPS, dedicated, etc.):
|
|
51
51
|
|
|
52
|
-
1. Install Node.js (v18+), npm, and Docker (
|
|
52
|
+
1. Install Node.js (v18+), npm, Docker, and **Docker Compose V2** (`docker compose`). The CLI requires the Compose plugin; legacy `docker-compose` (V1) is not supported.
|
|
53
53
|
2. Create a directory and install the app as above (e.g. `npm init -y`, `npm install el-contador`, copy `.env.example` to `.env`, set `DB_PASSWORD` and `SESSION_SECRET`).
|
|
54
54
|
3. Start with `npx el-contador` (or the `docker compose -f ...` command). The app listens on `ADMIN_PORT` (default 3080) on the host.
|
|
55
55
|
4. To expose it on a **subdomain or domain** (e.g. `admin.example.com`), configure a **reverse proxy** (nginx, Caddy, or Apache) on that server. The package does not install or configure nginx for you; you add the proxy config yourself. See below.
|
|
@@ -117,13 +117,9 @@ After a successful update, the backend log will show `el-contador-server@X.Y.Z`
|
|
|
117
117
|
- `el-contador down` or `el-contador stop` – stop containers.
|
|
118
118
|
- `el-contador update` – run `npm update el-contador` then rebuild and start.
|
|
119
119
|
|
|
120
|
-
If `npx el-contador` fails
|
|
120
|
+
If `npx el-contador` fails with **"docker compose (Compose V2) is required"**, install the Compose plugin: `sudo apt install docker-compose-plugin` (Debian/Ubuntu). Then run `npx el-contador` again.
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
docker-compose --project-directory node_modules/el-contador -f node_modules/el-contador/docker-compose.yml --env-file .env up -d
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
On Ubuntu: `sudo apt install docker-compose-plugin` (V2) or the standalone `docker-compose` package.
|
|
122
|
+
If you see **KeyError: 'ContainerConfig'** (Python traceback), you are using legacy `docker-compose` (V1). Install Docker Compose V2 as above; the CLI no longer falls back to V1.
|
|
127
123
|
|
|
128
124
|
## Publishing to npm (maintainers)
|
|
129
125
|
|
package/bin/cli.js
CHANGED
|
@@ -16,22 +16,19 @@ function shQuote(s) {
|
|
|
16
16
|
return "'" + String(s).replace(/'/g, "'\"'\"'") + "'";
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Prefer Docker Compose V2 only. V1 (docker-compose) can raise KeyError: 'ContainerConfig'
|
|
20
|
+
// with newer Docker Engine when inspecting container volumes.
|
|
19
21
|
function getDockerComposeCmd() {
|
|
20
|
-
const
|
|
22
|
+
const r = spawnSync(
|
|
21
23
|
process.platform === 'win32' ? 'cmd' : 'sh',
|
|
22
24
|
[process.platform === 'win32' ? '/c' : '-c', 'docker compose version'],
|
|
23
25
|
{ stdio: 'ignore' }
|
|
24
26
|
);
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
const r2 = spawnSync(
|
|
28
|
-
process.platform === 'win32' ? 'cmd' : 'sh',
|
|
29
|
-
[process.platform === 'win32' ? '/c' : '-c', 'docker-compose version'],
|
|
30
|
-
{ stdio: 'ignore' }
|
|
31
|
-
);
|
|
32
|
-
if (r2.status === 0) return 'docker-compose';
|
|
27
|
+
if (r.status === 0) return 'docker compose';
|
|
33
28
|
|
|
34
|
-
console.error('el-contador:
|
|
29
|
+
console.error('el-contador: "docker compose" (Compose V2) is required.');
|
|
30
|
+
console.error('Install the plugin: apt install docker-compose-plugin (Debian/Ubuntu)');
|
|
31
|
+
console.error('Or use the Docker Desktop built-in Compose. Legacy "docker-compose" (V1) is not supported.');
|
|
35
32
|
process.exit(1);
|
|
36
33
|
}
|
|
37
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "el-contador",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "Bookkeeping and expense management – run with Docker",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bookkeeping",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"server/db",
|
|
19
|
+
"server/lib",
|
|
19
20
|
"server/middleware",
|
|
20
21
|
"server/routes",
|
|
21
22
|
"server/scripts",
|
package/server/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
// Fail fast with a clear message if server/lib is missing (e.g. incomplete deploy)
|
|
5
|
+
const libFiscal = path.join(__dirname, 'lib', 'fiscal.js');
|
|
6
|
+
if (!fs.existsSync(libFiscal)) {
|
|
7
|
+
console.error('Missing server/lib/fiscal.js. Deploy the full server directory including server/lib/.');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
3
10
|
const express = require('express');
|
|
4
11
|
const session = require('express-session');
|
|
5
12
|
const cookieParser = require('cookie-parser');
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { pool } = require('../db/pool');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load fiscal config from invoice_settings. Returns { enabled, startMonth, startDay }.
|
|
5
|
+
* Default: natural year (enabled: false, startMonth: 1, startDay: 1).
|
|
6
|
+
*/
|
|
7
|
+
async function getFiscalConfig() {
|
|
8
|
+
const r = await pool.query('SELECT data FROM invoice_settings WHERE id = 1');
|
|
9
|
+
const raw = r.rows[0]?.data || {};
|
|
10
|
+
const enabled = Boolean(raw.fiscalYearEnabled);
|
|
11
|
+
const startMonth = Math.min(12, Math.max(1, Number(raw.fiscalYearStartMonth) || 1));
|
|
12
|
+
let startDay = Math.min(31, Math.max(1, Number(raw.fiscalYearStartDay) || 1));
|
|
13
|
+
const maxDay = new Date(2024, startMonth, 0).getDate();
|
|
14
|
+
if (startDay > maxDay) startDay = maxDay;
|
|
15
|
+
return { enabled, startMonth, startDay };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function pad(n) {
|
|
19
|
+
return String(n).padStart(2, '0');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Return { startDate, endDate } (ISO YYYY-MM-DD) for the given fiscal/calendar year number.
|
|
24
|
+
* When custom fiscal: year 2025 = 2025-startMonth-startDay to day before 2026-startMonth-startDay.
|
|
25
|
+
*/
|
|
26
|
+
function getFiscalYearBounds(yearNumber, config) {
|
|
27
|
+
const { enabled, startMonth, startDay } = config;
|
|
28
|
+
if (!enabled || (startMonth === 1 && startDay === 1)) {
|
|
29
|
+
return {
|
|
30
|
+
startDate: `${yearNumber}-01-01`,
|
|
31
|
+
endDate: `${yearNumber}-12-31`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const startDate = `${yearNumber}-${pad(startMonth)}-${pad(startDay)}`;
|
|
35
|
+
const endYear = yearNumber + 1;
|
|
36
|
+
const endDate = new Date(endYear, startMonth - 1, startDay - 1);
|
|
37
|
+
const endDateStr = `${endYear}-${pad(endDate.getMonth() + 1)}-${pad(endDate.getDate())}`;
|
|
38
|
+
return { startDate, endDate: endDateStr };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Return { startDate, endDate } for the given quarter (1-4) of the fiscal/calendar year.
|
|
43
|
+
* Custom fiscal: Q1 = first 3 months of fiscal year, Q2 = next 3, etc.
|
|
44
|
+
*/
|
|
45
|
+
function getFiscalQuarterBounds(yearNumber, quarter, config) {
|
|
46
|
+
const { enabled, startMonth, startDay } = config;
|
|
47
|
+
if (!enabled || (startMonth === 1 && startDay === 1)) {
|
|
48
|
+
const startMonthQ = (quarter - 1) * 3 + 1;
|
|
49
|
+
const startDate = `${yearNumber}-${pad(startMonthQ)}-01`;
|
|
50
|
+
const endMonthQ = quarter * 3;
|
|
51
|
+
const endDate = new Date(yearNumber, endMonthQ, 0);
|
|
52
|
+
return {
|
|
53
|
+
startDate,
|
|
54
|
+
endDate: `${yearNumber}-${pad(endMonthQ)}-${pad(endDate.getDate())}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const yearStart = getFiscalYearBounds(yearNumber, config).startDate;
|
|
58
|
+
const start = new Date(yearStart);
|
|
59
|
+
start.setMonth(start.getMonth() + (quarter - 1) * 3);
|
|
60
|
+
const end = new Date(start);
|
|
61
|
+
end.setMonth(end.getMonth() + 3);
|
|
62
|
+
end.setDate(end.getDate() - 1);
|
|
63
|
+
const startDate = `${start.getFullYear()}-${pad(start.getMonth() + 1)}-${pad(start.getDate())}`;
|
|
64
|
+
const endDate = `${end.getFullYear()}-${pad(end.getMonth() + 1)}-${pad(end.getDate())}`;
|
|
65
|
+
return { startDate, endDate };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Return { startDate, endDate } for the given month (1-12) of the fiscal/calendar year.
|
|
70
|
+
* Month 1 = first month of fiscal year.
|
|
71
|
+
*/
|
|
72
|
+
function getFiscalMonthBounds(yearNumber, month, config) {
|
|
73
|
+
const { enabled, startMonth, startDay } = config;
|
|
74
|
+
if (!enabled || (startMonth === 1 && startDay === 1)) {
|
|
75
|
+
const startDate = `${yearNumber}-${pad(month)}-01`;
|
|
76
|
+
const endDate = new Date(yearNumber, month, 0);
|
|
77
|
+
return {
|
|
78
|
+
startDate,
|
|
79
|
+
endDate: `${yearNumber}-${pad(month)}-${pad(endDate.getDate())}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const yearStart = getFiscalYearBounds(yearNumber, config).startDate;
|
|
83
|
+
const start = new Date(yearStart);
|
|
84
|
+
start.setMonth(start.getMonth() + (month - 1));
|
|
85
|
+
const end = new Date(start);
|
|
86
|
+
end.setMonth(end.getMonth() + 1);
|
|
87
|
+
end.setDate(end.getDate() - 1);
|
|
88
|
+
const startDate = `${start.getFullYear()}-${pad(start.getMonth() + 1)}-${pad(start.getDate())}`;
|
|
89
|
+
const endDate = `${end.getFullYear()}-${pad(end.getMonth() + 1)}-${pad(end.getDate())}`;
|
|
90
|
+
return { startDate, endDate };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Which fiscal year number contains the given date? Returns the start-year of that fiscal year.
|
|
95
|
+
*/
|
|
96
|
+
function getFiscalYearContainingDateSync(dateStr, config) {
|
|
97
|
+
const d = new Date(dateStr);
|
|
98
|
+
const y = d.getFullYear();
|
|
99
|
+
const m = d.getMonth() + 1;
|
|
100
|
+
const day = d.getDate();
|
|
101
|
+
const { enabled, startMonth, startDay } = config;
|
|
102
|
+
if (!enabled || (startMonth === 1 && startDay === 1)) return y;
|
|
103
|
+
if (m < startMonth || (m === startMonth && day < startDay)) return y - 1;
|
|
104
|
+
return y;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
getFiscalConfig,
|
|
109
|
+
getFiscalYearBounds,
|
|
110
|
+
getFiscalQuarterBounds,
|
|
111
|
+
getFiscalMonthBounds,
|
|
112
|
+
getFiscalYearContainingDateSync,
|
|
113
|
+
};
|
package/server/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "el-contador-server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "el-contador-server",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.10",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@google/genai": "^0.10.0",
|
|
12
12
|
"bcrypt": "^5.1.1",
|