Gutschriften Phase 3a: Firmenstammdaten (Absender) fuer PDF/ZUGFeRD
Neues CompanyProfile-Modell (Einzel-Zeile) + Migration (IF NOT EXISTS): Firmenname, Anschrift, USt-IdNr, Steuernummer, Handelsregister, Geschaeftsfuehrer, Kontakt, IBAN/BIC/Bank. Fliesst spaeter in Gutschrift-PDF + ZUGFeRD-Verkaeuferdaten ein. Backend: Service (getOrCreate/update mit Feld-Whitelist), Controller, Routes GET/PUT /api/company-profile (settings:read/update), auditiert. Frontend: Settings-Seite 'Firmenstammdaten (Absender)' (/settings/company-profile) mit Firma/Anschrift, Steuer/Register, Kontakt/Bank. Typ + companyProfileApi + Menue-Eintrag. Phase 3b (PDF + ZUGFeRD-Erzeugung) folgt darauf auf. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { Response } from 'express';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import * as companyProfileService from '../services/companyProfile.service.js';
|
||||
|
||||
export async function getProfile(_req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const data = await companyProfileService.getOrCreateProfile();
|
||||
res.json({ success: true, data } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: 'Fehler beim Laden der Firmendaten' } as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProfile(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const updated = await companyProfileService.updateProfile(req.body ?? {});
|
||||
await logChange({
|
||||
req,
|
||||
action: 'UPDATE',
|
||||
resourceType: 'CompanyProfile',
|
||||
resourceId: updated.id.toString(),
|
||||
label: 'Firmenstammdaten (Absender) geändert',
|
||||
});
|
||||
res.json({ success: true, data: updated } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: 'Fehler beim Speichern der Firmendaten' } as ApiResponse);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import meterRoutes from './routes/meter.routes.js';
|
||||
import stressfreiEmailRoutes from './routes/stressfreiEmail.routes.js';
|
||||
import contractRoutes from './routes/contract.routes.js';
|
||||
import creditNoteRoutes from './routes/creditNote.routes.js';
|
||||
import companyProfileRoutes from './routes/companyProfile.routes.js';
|
||||
import platformRoutes from './routes/platform.routes.js';
|
||||
import cancellationPeriodRoutes from './routes/cancellation-period.routes.js';
|
||||
import contractDurationRoutes from './routes/contract-duration.routes.js';
|
||||
@@ -357,6 +358,7 @@ app.use('/api/meters', meterRoutes);
|
||||
app.use('/api/stressfrei-emails', stressfreiEmailRoutes);
|
||||
app.use('/api/contracts', contractRoutes);
|
||||
app.use('/api/credit-notes', creditNoteRoutes);
|
||||
app.use('/api/company-profile', companyProfileRoutes);
|
||||
app.use('/api/platforms', platformRoutes);
|
||||
app.use('/api/cancellation-periods', cancellationPeriodRoutes);
|
||||
app.use('/api/contract-durations', contractDurationRoutes);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Router } from 'express';
|
||||
import * as companyProfileController from '../controllers/companyProfile.controller.js';
|
||||
import { authenticate, requirePermission } from '../middleware/auth.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/', authenticate, requirePermission('settings:read'), companyProfileController.getProfile);
|
||||
router.put('/', authenticate, requirePermission('settings:update'), companyProfileController.updateProfile);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,28 @@
|
||||
// ==================== FIRMENSTAMMDATEN (ABSENDER) ====================
|
||||
// Einzel-Zeile. Fließt in Gutschrift-PDF + ZUGFeRD-XML (Verkäufer) ein.
|
||||
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
const EDITABLE_STRING_FIELDS = [
|
||||
'name', 'street', 'houseNumber', 'postalCode', 'city', 'country',
|
||||
'vatId', 'taxNumber', 'commercialRegister', 'managingDirector',
|
||||
'email', 'phone', 'website', 'iban', 'bic', 'bankName',
|
||||
] as const;
|
||||
|
||||
export async function getOrCreateProfile() {
|
||||
const existing = await prisma.companyProfile.findFirst();
|
||||
if (existing) return existing;
|
||||
return prisma.companyProfile.create({ data: {} });
|
||||
}
|
||||
|
||||
export async function updateProfile(input: Record<string, unknown>) {
|
||||
const profile = await getOrCreateProfile();
|
||||
const data: Prisma.CompanyProfileUpdateInput = {};
|
||||
for (const field of EDITABLE_STRING_FIELDS) {
|
||||
if (typeof input[field] === 'string') {
|
||||
(data as Record<string, unknown>)[field] = (input[field] as string).slice(0, 191).trim();
|
||||
}
|
||||
}
|
||||
return prisma.companyProfile.update({ where: { id: profile.id }, data });
|
||||
}
|
||||
Reference in New Issue
Block a user