finanzen hinzugefügt.
This commit is contained in:
parent
95f4d9f271
commit
6927d0be3e
@ -16,7 +16,8 @@
|
||||
"dotenv": "^16.3.1",
|
||||
"dns": "^0.2.2",
|
||||
"pdfkit": "^0.14.0",
|
||||
"node-cron": "^3.0.3"
|
||||
"node-cron": "^3.0.3",
|
||||
"multer": "^1.4.5-lts.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
|
||||
@ -1248,3 +1248,44 @@ body:not(.admin-body) > * {
|
||||
.expiry-urgent { background:#fee2e2; color:var(--error); }
|
||||
.expiry-warning { background:#fffbeb; color:var(--warning); }
|
||||
.expiry-normal { background:#f0fdf4; color:var(--success); }
|
||||
|
||||
/* ---- CSV File Upload ---- */
|
||||
.file-upload-wrap { position: relative; }
|
||||
.file-upload-wrap input[type="file"] {
|
||||
position: absolute; width: 1px; height: 1px; opacity: 0;
|
||||
}
|
||||
.file-upload-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
border: 1.5px dashed var(--border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
transition: all 0.2s;
|
||||
background: var(--bg);
|
||||
}
|
||||
.file-upload-label:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
background: #f0f0ff;
|
||||
}
|
||||
.file-upload-label::before { content: '📁'; font-size: 1.1rem; }
|
||||
|
||||
.import-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.import-divider::before,
|
||||
.import-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 1024*1024 } });
|
||||
const router = express.Router();
|
||||
const db = require('../config/database');
|
||||
const { requireAdmin } = require('../middleware/auth');
|
||||
@ -175,11 +177,17 @@ router.post('/chargebacks/:id/resolve', requireAdmin, async (req, res) => {
|
||||
});
|
||||
|
||||
// CSV Import Rückläufer
|
||||
router.post('/chargebacks/import', requireAdmin, async (req, res) => {
|
||||
const { csv_data } = req.body;
|
||||
if (!csv_data || !csv_data.trim()) return res.redirect('/admin/finance?error=Keine+Daten+eingegeben');
|
||||
router.post('/chargebacks/import', requireAdmin, upload.single('csv_file'), async (req, res) => {
|
||||
// Datei hat Vorrang, sonst Textfeld
|
||||
let rawData = '';
|
||||
if (req.file && req.file.buffer.length > 0) {
|
||||
rawData = req.file.buffer.toString('utf-8').replace(/\r/g, '');
|
||||
} else if (req.body.csv_data && req.body.csv_data.trim()) {
|
||||
rawData = req.body.csv_data.trim();
|
||||
}
|
||||
if (!rawData) return res.redirect('/admin/finance?error=Keine+Daten+eingegeben');
|
||||
try {
|
||||
const lines = csv_data.trim().split('\n').filter(l => l.trim());
|
||||
const lines = rawData.trim().split('\n').filter(l => l.trim());
|
||||
let imported = 0;
|
||||
for (const line of lines) {
|
||||
const cols = line.split(';').map(c => c.trim().replace(/"/g, ''));
|
||||
|
||||
@ -365,16 +365,34 @@
|
||||
<h3>Rückläufer CSV Import</h3>
|
||||
<button onclick="toggleModal('importChargebackModal')" class="modal-close">✕</button>
|
||||
</div>
|
||||
<form method="POST" action="/admin/finance/chargebacks/import">
|
||||
<form method="POST" action="/admin/finance/chargebacks/import"
|
||||
enctype="multipart/form-data">
|
||||
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:16px">
|
||||
Format pro Zeile: <code>IBAN;Betrag;Datum;Grund</code><br>
|
||||
Beispiel: <code>DE89370400440532013000;29,95;2026-04-05;Rücklastschrift</code>
|
||||
</p>
|
||||
|
||||
<!-- Option 1: Datei -->
|
||||
<div class="form-group">
|
||||
<label>CSV-Daten einfügen</label>
|
||||
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:8px">
|
||||
Format pro Zeile: <code>IBAN;Betrag;Datum;Grund</code><br>
|
||||
Beispiel: <code>DE89370400440532013000;29,95;2026-04-05;Rücklastschrift</code>
|
||||
</p>
|
||||
<textarea name="csv_data" class="form-control" rows="6"
|
||||
placeholder="IBAN;Betrag;Datum;Grund"></textarea>
|
||||
<label>📁 CSV-Datei hochladen</label>
|
||||
<div class="file-upload-wrap">
|
||||
<input type="file" name="csv_file" id="csvFile" accept=".csv,.txt"
|
||||
onchange="showFileName(this)">
|
||||
<label for="csvFile" class="file-upload-label">
|
||||
<span id="fileNameDisplay">Datei auswählen...</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="import-divider"><span>oder</span></div>
|
||||
|
||||
<!-- Option 2: Textfeld -->
|
||||
<div class="form-group">
|
||||
<label>📋 Daten einfügen</label>
|
||||
<textarea name="csv_data" class="form-control" rows="5"
|
||||
placeholder="IBAN;Betrag;Datum;Grund DE89370400440532013000;29,95;2026-04-05;Rücklastschrift"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="toggleModal('importChargebackModal')" class="btn btn-outline">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">📥 Importieren</button>
|
||||
@ -480,6 +498,11 @@ function showTab(name) {
|
||||
function toggleModal(id) {
|
||||
document.getElementById(id).classList.toggle('hidden');
|
||||
}
|
||||
|
||||
function showFileName(input) {
|
||||
const display = document.getElementById('fileNameDisplay');
|
||||
display.textContent = input.files.length > 0 ? input.files[0].name : 'Datei auswählen...';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user