feat: NAS storage for invoices/offers, code cleanup, date/time fixes
- NAS storage for created invoices (PDF via puppeteer), received invoices, and offers with auto-save on create/edit - Deterministic file paths derived from DB fields (no file_path column needed) - Separate NAS mount points: NAS_FINANCIALS_PATH, NAS_OFFERS_PATH - Invoice language field (cs/en) stored per invoice, replaces lang modal - Invoices list filtered by month/year matching KPI card selection - Centralized date helpers (src/utils/date.ts) replacing all .toISOString() calls that returned UTC instead of local time - Attendance project switching uses exact time (not rounded) - Comment cleanup: removed ~100 unnecessary/Czech comments - Removed as-any casts in orders and attendance - Prisma migrations: add invoice language, drop received_invoices BLOB columns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { config } from "../config/env";
|
||||
import { localDateStr, localTimeStr } from "../utils/date";
|
||||
|
||||
const FileType = require("file-type") as typeof import("file-type");
|
||||
|
||||
@@ -223,16 +224,7 @@ export class NasFileManager {
|
||||
}
|
||||
|
||||
const modified = lstat.mtime;
|
||||
const modifiedStr =
|
||||
modified.getFullYear() +
|
||||
"-" +
|
||||
String(modified.getMonth() + 1).padStart(2, "0") +
|
||||
"-" +
|
||||
String(modified.getDate()).padStart(2, "0") +
|
||||
" " +
|
||||
String(modified.getHours()).padStart(2, "0") +
|
||||
":" +
|
||||
String(modified.getMinutes()).padStart(2, "0");
|
||||
const modifiedStr = `${localDateStr(modified)} ${localTimeStr(modified)}`;
|
||||
|
||||
const item: FileItem = {
|
||||
name: entry,
|
||||
@@ -284,7 +276,6 @@ export class NasFileManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Real path on disk for UI display
|
||||
let realDirPath: string;
|
||||
try {
|
||||
realDirPath = fs.realpathSync(dirPath);
|
||||
@@ -331,7 +322,6 @@ export class NasFileManager {
|
||||
return "Tento typ souboru není povolen";
|
||||
}
|
||||
|
||||
// MIME validation via file-type
|
||||
try {
|
||||
const typeResult = await FileType.fromBuffer(fileBuffer);
|
||||
if (typeResult && this.isSuspiciousMime(typeResult.mime, ext)) {
|
||||
@@ -343,7 +333,6 @@ export class NasFileManager {
|
||||
|
||||
let destPath = dirPath + "/" + safeName;
|
||||
|
||||
// If file exists, append counter
|
||||
if (fs.existsSync(destPath)) {
|
||||
const base = path.basename(safeName, ext ? "." + ext : "");
|
||||
let counter = 1;
|
||||
@@ -456,7 +445,6 @@ export class NasFileManager {
|
||||
return "Cílový soubor již existuje";
|
||||
}
|
||||
|
||||
// Validate target name
|
||||
const targetName = path.basename(toPath);
|
||||
if (this.sanitizeFilename(targetName) !== targetName) {
|
||||
return "Neplatný cílový název";
|
||||
@@ -513,7 +501,6 @@ export class NasFileManager {
|
||||
|
||||
public sanitizeFilename(name: string): string {
|
||||
let safe = path.basename(name);
|
||||
// Strip control chars and special chars
|
||||
safe = safe.replace(/[\x00-\x1f\x7f<>:"/\\|?*]/g, "");
|
||||
safe = safe.replace(/^[. ]+|[. ]+$/g, "");
|
||||
|
||||
@@ -583,7 +570,6 @@ export class NasFileManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize separators and trim
|
||||
const normalized = subPath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
||||
const candidate = path.resolve(folderPath, normalized).replace(/\\/g, "/");
|
||||
|
||||
@@ -618,7 +604,6 @@ export class NasFileManager {
|
||||
const normalFull = fullPath.replace(/\\/g, "/");
|
||||
const normalBase = basePath.replace(/\\/g, "/");
|
||||
|
||||
// Get the relative portion after basePath
|
||||
if (!normalFull.startsWith(normalBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user