attachments

This commit is contained in:
2026-05-07 11:04:24 -05:00
parent 5b3da47d87
commit 82bc055c4c
4 changed files with 331 additions and 6 deletions

View File

@@ -6,7 +6,31 @@
const express = require('express');
const router = express.Router();
const accountingService = require('../services/accounting-service');
const multer = require('multer');
// Limit aus ENV, default 5 MB. Akzeptierte Werte z.B. "5", "10", "20"
const ATTACHMENT_MAX_MB = parseInt(process.env.EXPENSE_ATTACHMENT_MAX_MB || '5', 10);
const ATTACHMENT_MAX_BYTES = ATTACHMENT_MAX_MB * 1024 * 1024;
const ALLOWED_MIME = new Set([
'image/png',
'image/jpeg',
'image/jpg',
'image/heic',
'image/heif',
'application/pdf'
]);
const attachUpload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: ATTACHMENT_MAX_BYTES },
fileFilter: (req, file, cb) => {
if (!ALLOWED_MIME.has(file.mimetype)) {
return cb(new Error(`Unsupported file type: ${file.mimetype}. Allowed: PNG, JPG, HEIC, PDF`));
}
cb(null, true);
}
});
// ────────────────────────────────────────────────────────────────────
function handleQboError(err, res, context) {
const statusCode = err.statusCode || 500;
@@ -206,5 +230,50 @@ router.get('/expenses', async (req, res) => {
res.json(expenses);
} catch (err) { handleQboError(err, res, 'expense-list'); }
});
// ─── POST /api/accounting/expenses/:id/attach ───────────────────────
// Hängt eine Datei (Bild oder PDF) an ein bestehendes QBO Purchase.
// Body: multipart/form-data, field "file"
router.post('/expenses/:id/attach', (req, res, next) => {
// multer-Wrapper, damit wir Multer-Fehler hübsch zurückgeben können
attachUpload.single('file')(req, res, (err) => {
if (err) {
// Spezifische Multer-Fehler übersetzen
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({
error: `File too large. Max ${ATTACHMENT_MAX_MB} MB.`,
context: 'attach'
});
}
return res.status(400).json({ error: err.message, context: 'attach' });
}
next();
});
}, async (req, res) => {
const { id } = req.params;
const file = req.file;
if (!file) {
return res.status(400).json({ error: 'No file uploaded (field name must be "file")' });
}
try {
const result = await accountingService.attachFileToEntity({
entityType: 'Purchase',
entityId: id,
fileBuffer: file.buffer,
fileName: file.originalname,
contentType: file.mimetype,
note: req.body && req.body.note ? String(req.body.note).slice(0, 200) : undefined
});
res.json(result);
} catch (err) { handleQboError(err, res, 'attach'); }
});
router.get('/attachments/limits', (req, res) => {
res.json({
maxBytes: ATTACHMENT_MAX_BYTES,
maxMb: ATTACHMENT_MAX_MB,
allowedMimeTypes: Array.from(ALLOWED_MIME),
allowedExtensions: ['.png', '.jpg', '.jpeg', '.heic', '.heif', '.pdf']
});
});
module.exports = router;