u
This commit is contained in:
@@ -1,34 +1,94 @@
|
||||
-- FibDash Database Schema
|
||||
-- Run these commands in your MSSQL database
|
||||
|
||||
-- Create Users table
|
||||
CREATE TABLE Users (
|
||||
-- Create Kreditor table
|
||||
CREATE TABLE Kreditor (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
google_id NVARCHAR(255) UNIQUE NOT NULL,
|
||||
email NVARCHAR(255) UNIQUE NOT NULL,
|
||||
iban NVARCHAR(34) NOT NULL,
|
||||
name NVARCHAR(255) NOT NULL,
|
||||
picture NVARCHAR(500),
|
||||
created_at DATETIME2 DEFAULT GETDATE(),
|
||||
last_login DATETIME2,
|
||||
is_active BIT DEFAULT 1
|
||||
kreditorId NVARCHAR(50) NOT NULL
|
||||
);
|
||||
|
||||
-- Create UserPreferences table
|
||||
CREATE TABLE UserPreferences (
|
||||
-- Ensure kreditorId is unique to support FK references
|
||||
ALTER TABLE Kreditor
|
||||
ADD CONSTRAINT UQ_Kreditor_kreditorId UNIQUE (kreditorId);
|
||||
|
||||
-- Create AccountingItems table
|
||||
-- Based on CSV structure: umsatz brutto, soll/haben kz, konto, gegenkonto, bu, buchungsdatum, rechnungsnummer, buchungstext, beleglink
|
||||
CREATE TABLE AccountingItems (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
theme NVARCHAR(50) DEFAULT 'light',
|
||||
language NVARCHAR(10) DEFAULT 'en',
|
||||
notifications_enabled BIT DEFAULT 1,
|
||||
created_at DATETIME2 DEFAULT GETDATE(),
|
||||
updated_at DATETIME2 DEFAULT GETDATE(),
|
||||
FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE
|
||||
umsatz_brutto DECIMAL(15,2) NOT NULL, -- gross turnover amount
|
||||
soll_haben_kz CHAR(1) NOT NULL CHECK (soll_haben_kz IN ('S', 'H')), -- S = eingang (debit), H = ausgang (credit)
|
||||
konto NVARCHAR(10) NOT NULL, -- account (e.g. 5400 = wareneingang 19%)
|
||||
gegenkonto NVARCHAR(50) NOT NULL, -- counter account references Kreditor(kreditorId)
|
||||
bu NVARCHAR(10), -- tax code (9 = 19%vst, 8 = 7%vst, 506 = dienstleistung aus EU, 511 = dienstleistung ausserhalb EU)
|
||||
buchungsdatum DATE NOT NULL, -- booking date
|
||||
rechnungsnummer NVARCHAR(100), -- invoice number (belegfeld 1)
|
||||
buchungstext NVARCHAR(500), -- booking text (supplier/customer name + purpose)
|
||||
beleglink NVARCHAR(500) -- document link
|
||||
);
|
||||
|
||||
-- Create Konto table
|
||||
CREATE TABLE Konto (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
konto NVARCHAR(10) NOT NULL,
|
||||
name NVARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
-- Create BU table
|
||||
CREATE TABLE BU (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
bu NVARCHAR(10) NOT NULL
|
||||
name NVARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
/*
|
||||
CSV
|
||||
umsatz brutto ,
|
||||
soll / haben kz ( S = eingang, H = ausgang),
|
||||
,,,
|
||||
konto (XXXX , z.b. 5400 = wareneingang 19%),
|
||||
gegenkonto (70XXX),
|
||||
bu (9 = 19%vst , 8 = 7%vst, 506 = dienstleistung aus EU, 511 = dienstleistung ausserhalb EU),
|
||||
buchungsdatum, (MDD)
|
||||
rechnungsnummer (belegfeld 1),
|
||||
,,
|
||||
buchungstext (lierferantenname / kundenname , + verwendungszweck)
|
||||
,,,,,
|
||||
beleglink
|
||||
|
||||
|
||||
--
|
||||
nicht abziehbare vorstreuer buchen auf 5600
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX IX_Users_Email ON Users(email);
|
||||
CREATE INDEX IX_Users_GoogleId ON Users(google_id);
|
||||
CREATE INDEX IX_UserPreferences_UserId ON UserPreferences(user_id);
|
||||
CREATE INDEX IX_Kreditor_IBAN ON Kreditor(iban);
|
||||
CREATE INDEX IX_Kreditor_KreditorId ON Kreditor(kreditorId);
|
||||
CREATE INDEX IX_AccountingItems_Buchungsdatum ON AccountingItems(buchungsdatum);
|
||||
CREATE INDEX IX_AccountingItems_Konto ON AccountingItems(konto);
|
||||
CREATE INDEX IX_AccountingItems_Rechnungsnummer ON AccountingItems(rechnungsnummer);
|
||||
CREATE INDEX IX_AccountingItems_SollHabenKz ON AccountingItems(soll_haben_kz);
|
||||
|
||||
-- Add FK from AccountingItems.bu -> BU(bu)
|
||||
ALTER TABLE AccountingItems
|
||||
ADD CONSTRAINT FK_AccountingItems_BU_BU
|
||||
FOREIGN KEY (bu) REFERENCES BU(bu);
|
||||
|
||||
-- Add FK from AccountingItems.gegenkonto -> Kreditor(kreditorId)
|
||||
ALTER TABLE AccountingItems
|
||||
ADD CONSTRAINT FK_AccountingItems_Gegenkonto_Kreditor
|
||||
FOREIGN KEY (gegenkonto) REFERENCES Kreditor(kreditorId);
|
||||
|
||||
-- Add FK from AccountingItems.konto -> Konto(konto)
|
||||
ALTER TABLE AccountingItems
|
||||
ADD CONSTRAINT FK_AccountingItems_Konto_Konto
|
||||
FOREIGN KEY (konto) REFERENCES Konto(konto);
|
||||
|
||||
-- Insert sample data (optional)
|
||||
-- Note: This will only work after you have real Google user data
|
||||
|
||||
@@ -558,4 +558,183 @@ router.get('/pdf/pdfobject/:kPdfObjekt', authenticateToken, async (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
// Kreditor API endpoints
|
||||
|
||||
// Get all kreditors
|
||||
router.get('/kreditors', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { executeQuery } = require('../config/database');
|
||||
const query = `
|
||||
SELECT id, iban, name, kreditorId, created_at, updated_at
|
||||
FROM Kreditor
|
||||
WHERE is_active = 1
|
||||
ORDER BY name ASC
|
||||
`;
|
||||
|
||||
const result = await executeQuery(query);
|
||||
res.json(result.recordset || []);
|
||||
} catch (error) {
|
||||
console.error('Error fetching kreditors:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch kreditors' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get kreditor by ID
|
||||
router.get('/kreditors/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { executeQuery } = require('../config/database');
|
||||
const { id } = req.params;
|
||||
|
||||
const query = `
|
||||
SELECT id, iban, name, kreditorId, created_at, updated_at
|
||||
FROM Kreditor
|
||||
WHERE id = @id AND is_active = 1
|
||||
`;
|
||||
|
||||
const result = await executeQuery(query, [
|
||||
{ name: 'id', type: 'int', value: parseInt(id) }
|
||||
]);
|
||||
|
||||
if (result.recordset.length === 0) {
|
||||
return res.status(404).json({ error: 'Kreditor not found' });
|
||||
}
|
||||
|
||||
res.json(result.recordset[0]);
|
||||
} catch (error) {
|
||||
console.error('Error fetching kreditor:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch kreditor' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create new kreditor
|
||||
router.post('/kreditors', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { executeQuery } = require('../config/database');
|
||||
const { iban, name, kreditorId } = req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!iban || !name || !kreditorId) {
|
||||
return res.status(400).json({ error: 'IBAN, name, and kreditorId are required' });
|
||||
}
|
||||
|
||||
// Check if kreditor with same IBAN or kreditorId already exists
|
||||
const checkQuery = `
|
||||
SELECT id FROM Kreditor
|
||||
WHERE (iban = @iban OR kreditorId = @kreditorId) AND is_active = 1
|
||||
`;
|
||||
|
||||
const checkResult = await executeQuery(checkQuery, [
|
||||
{ name: 'iban', type: 'nvarchar', value: iban },
|
||||
{ name: 'kreditorId', type: 'nvarchar', value: kreditorId }
|
||||
]);
|
||||
|
||||
if (checkResult.recordset.length > 0) {
|
||||
return res.status(409).json({ error: 'Kreditor with this IBAN or kreditorId already exists' });
|
||||
}
|
||||
|
||||
const insertQuery = `
|
||||
INSERT INTO Kreditor (iban, name, kreditorId, created_at, updated_at)
|
||||
OUTPUT INSERTED.id, INSERTED.iban, INSERTED.name, INSERTED.kreditorId, INSERTED.created_at, INSERTED.updated_at
|
||||
VALUES (@iban, @name, @kreditorId, GETDATE(), GETDATE())
|
||||
`;
|
||||
|
||||
const result = await executeQuery(insertQuery, [
|
||||
{ name: 'iban', type: 'nvarchar', value: iban },
|
||||
{ name: 'name', type: 'nvarchar', value: name },
|
||||
{ name: 'kreditorId', type: 'nvarchar', value: kreditorId }
|
||||
]);
|
||||
|
||||
res.status(201).json(result.recordset[0]);
|
||||
} catch (error) {
|
||||
console.error('Error creating kreditor:', error);
|
||||
res.status(500).json({ error: 'Failed to create kreditor' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update kreditor
|
||||
router.put('/kreditors/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { executeQuery } = require('../config/database');
|
||||
const { id } = req.params;
|
||||
const { iban, name, kreditorId } = req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!iban || !name || !kreditorId) {
|
||||
return res.status(400).json({ error: 'IBAN, name, and kreditorId are required' });
|
||||
}
|
||||
|
||||
// Check if kreditor exists
|
||||
const checkQuery = `SELECT id FROM Kreditor WHERE id = @id AND is_active = 1`;
|
||||
const checkResult = await executeQuery(checkQuery, [
|
||||
{ name: 'id', type: 'int', value: parseInt(id) }
|
||||
]);
|
||||
|
||||
if (checkResult.recordset.length === 0) {
|
||||
return res.status(404).json({ error: 'Kreditor not found' });
|
||||
}
|
||||
|
||||
// Check for conflicts with other kreditors
|
||||
const conflictQuery = `
|
||||
SELECT id FROM Kreditor
|
||||
WHERE (iban = @iban OR kreditorId = @kreditorId) AND id != @id AND is_active = 1
|
||||
`;
|
||||
|
||||
const conflictResult = await executeQuery(conflictQuery, [
|
||||
{ name: 'iban', type: 'nvarchar', value: iban },
|
||||
{ name: 'kreditorId', type: 'nvarchar', value: kreditorId },
|
||||
{ name: 'id', type: 'int', value: parseInt(id) }
|
||||
]);
|
||||
|
||||
if (conflictResult.recordset.length > 0) {
|
||||
return res.status(409).json({ error: 'Another kreditor with this IBAN or kreditorId already exists' });
|
||||
}
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE Kreditor
|
||||
SET iban = @iban, name = @name, kreditorId = @kreditorId, updated_at = GETDATE()
|
||||
OUTPUT INSERTED.id, INSERTED.iban, INSERTED.name, INSERTED.kreditorId, INSERTED.created_at, INSERTED.updated_at
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
const result = await executeQuery(updateQuery, [
|
||||
{ name: 'iban', type: 'nvarchar', value: iban },
|
||||
{ name: 'name', type: 'nvarchar', value: name },
|
||||
{ name: 'kreditorId', type: 'nvarchar', value: kreditorId },
|
||||
{ name: 'id', type: 'int', value: parseInt(id) }
|
||||
]);
|
||||
|
||||
res.json(result.recordset[0]);
|
||||
} catch (error) {
|
||||
console.error('Error updating kreditor:', error);
|
||||
res.status(500).json({ error: 'Failed to update kreditor' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete kreditor (soft delete)
|
||||
router.delete('/kreditors/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { executeQuery } = require('../config/database');
|
||||
const { id } = req.params;
|
||||
|
||||
const query = `
|
||||
UPDATE Kreditor
|
||||
SET is_active = 0, updated_at = GETDATE()
|
||||
WHERE id = @id AND is_active = 1
|
||||
`;
|
||||
|
||||
const result = await executeQuery(query, [
|
||||
{ name: 'id', type: 'int', value: parseInt(id) }
|
||||
]);
|
||||
|
||||
if (result.rowsAffected[0] === 0) {
|
||||
return res.status(404).json({ error: 'Kreditor not found' });
|
||||
}
|
||||
|
||||
res.json({ message: 'Kreditor deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting kreditor:', error);
|
||||
res.status(500).json({ error: 'Failed to delete kreditor' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user