Compare commits

..

5 Commits

Author SHA1 Message Date
seaznCode
002dbc78c1 feat: add updateContent method for abonements and corresponding route 2026-02-20 21:45:02 +01:00
seaznCode
b7d9ef8371 package lock 2026-02-20 20:57:39 +01:00
DeathKaioken
04a032992a feat: abo 2026-02-18 11:16:54 +01:00
DeathKaioken
09a6004875 feat: dependecy fix + Abo Mailing Template 2026-02-18 10:24:04 +01:00
seaznCode
4ce8507858 feat: enhance pool management with subscription linking and inflow diagnostics 2026-02-17 18:13:27 +01:00
30 changed files with 4317 additions and 1850 deletions

View File

@ -15,6 +15,10 @@ module.exports = {
billingInterval: req.body.billing_interval, billingInterval: req.body.billing_interval,
intervalCount: req.body.interval_count, intervalCount: req.body.interval_count,
isAutoRenew: req.body.is_auto_renew, isAutoRenew: req.body.is_auto_renew,
isForSelf: req.body.is_for_self,
recipientName: req.body.recipient_name,
recipientEmail: req.body.recipient_email,
recipientNotes: req.body.recipient_notes,
firstName: req.body.firstName, firstName: req.body.firstName,
lastName: req.body.lastName, lastName: req.body.lastName,
email: req.body.email, email: req.body.email,
@ -70,6 +74,28 @@ module.exports = {
} }
}, },
async updateContent(req, res) {
try {
const rawUser = req.user || {};
const actorUser = { ...rawUser, id: rawUser.id ?? rawUser.userId ?? null };
const data = await service.updateContent({
abonementId: req.params.id,
actorUser,
items: req.body.items,
});
return res.json({ success: true, data });
} catch (err) {
console.error('[ABONEMENT UPDATE CONTENT]', err);
if (err?.message === 'Not found') {
return res.status(404).json({ success: false, message: 'Abonement not found' });
}
if (err?.message === 'Forbidden') {
return res.status(403).json({ success: false, message: 'Forbidden' });
}
return res.status(400).json({ success: false, message: err.message });
}
},
async renew(req, res) { async renew(req, res) {
try { try {
const rawUser = req.user || {}; const rawUser = req.user || {};
@ -86,6 +112,9 @@ module.exports = {
try { try {
const rawUser = req.user || {}; const rawUser = req.user || {};
const id = rawUser.id ?? rawUser.userId; const id = rawUser.id ?? rawUser.userId;
if (!id) {
return res.status(401).json({ success: false, message: 'Unauthorized: missing user id' });
}
console.log('[CONTROLLER GET MINE] Using user id:', id); console.log('[CONTROLLER GET MINE] Using user id:', id);
const data = await service.getMyAbonements({ userId: id }); const data = await service.getMyAbonements({ userId: id });
return res.json({ success: true, data }); return res.json({ success: true, data });
@ -95,6 +124,42 @@ module.exports = {
} }
}, },
async getMineStatus(req, res) {
try {
const rawUser = req.user || {};
const id = rawUser.id ?? rawUser.userId;
if (!id) {
return res.status(401).json({ success: false, message: 'Unauthorized: missing user id' });
}
const data = await service.getMyAbonementStatus({ userId: id });
return res.json({ success: true, data });
} catch (err) {
console.error('[ABONEMENT MINE STATUS]', err);
return res.status(500).json({ success: false, message: 'Internal error' });
}
},
async getInvoices(req, res) {
try {
const rawUser = req.user || {};
const actorUser = { ...rawUser, id: rawUser.id ?? rawUser.userId ?? null };
const data = await service.getInvoicesForAbonement({
abonementId: req.params.id,
actorUser,
});
return res.json({ success: true, data });
} catch (err) {
console.error('[ABONEMENT INVOICES]', err);
if (err?.message === 'Not found') {
return res.status(404).json({ success: false, message: 'Abonement not found' });
}
if (err?.message === 'Forbidden') {
return res.status(403).json({ success: false, message: 'Forbidden' });
}
return res.status(400).json({ success: false, message: err.message });
}
},
async getHistory(req, res) { async getHistory(req, res) {
try { try {
return res.json({ success: true, data: await service.getHistory({ abonementId: req.params.id }) }); return res.json({ success: true, data: await service.getHistory({ abonementId: req.params.id }) });

View File

@ -1,17 +1,29 @@
const { createPool, listPools, updatePoolState } = require('../../services/pool/PoolService'); const { createPool, listPools, updatePoolState, updatePoolSubscription } = require('../../services/pool/PoolService');
const PoolMemberService = require('../../services/pool/PoolMemberService'); const PoolMemberService = require('../../services/pool/PoolMemberService');
const PoolInflowService = require('../../services/pool/PoolInflowService');
module.exports = { module.exports = {
async create(req, res) { async create(req, res) {
try { try {
const { pool_name, description, price, pool_type, is_active } = req.body || {}; const { pool_name, description, price, price_net, subscription_coffee_id, pool_type, is_active } = req.body || {};
const normalizedNetPrice = Number(price_net ?? price);
const actorUserId = req.user && req.user.userId; const actorUserId = req.user && req.user.userId;
if (!pool_name) return res.status(400).json({ success: false, message: 'Pool name is required' }); if (!pool_name) return res.status(400).json({ success: false, message: 'Pool name is required' });
if (!price || price < 0) return res.status(400).json({ success: false, message: 'Valid price is required' }); if (!Number.isFinite(normalizedNetPrice) || normalizedNetPrice < 0) {
return res.status(400).json({ success: false, message: 'Valid net price per capsule is required' });
}
if (pool_type && !['coffee', 'other'].includes(pool_type)) { if (pool_type && !['coffee', 'other'].includes(pool_type)) {
return res.status(400).json({ success: false, message: 'Invalid pool_type. Allowed: coffee, other' }); return res.status(400).json({ success: false, message: 'Invalid pool_type. Allowed: coffee, other' });
} }
const pool = await createPool({ pool_name, description, price, pool_type, is_active, created_by: actorUserId }); const pool = await createPool({
pool_name,
description,
price: Number(normalizedNetPrice.toFixed(2)),
subscription_coffee_id,
pool_type,
is_active,
created_by: actorUserId
});
return res.status(201).json({ success: true, data: pool }); return res.status(201).json({ success: true, data: pool });
} catch (e) { } catch (e) {
if (e && (e.code === 'ER_DUP_ENTRY' || e.errno === 1062)) { if (e && (e.code === 'ER_DUP_ENTRY' || e.errno === 1062)) {
@ -57,6 +69,28 @@ module.exports = {
} }
}, },
async updateSubscription(req, res) {
try {
const { id } = req.params || {};
const { subscription_coffee_id } = req.body || {};
const actorUserId = req.user && req.user.userId;
if (!id) return res.status(400).json({ success: false, message: 'id is required' });
const updated = await updatePoolSubscription({
id,
subscription_coffee_id,
actorUserId,
});
return res.status(200).json({ success: true, data: updated });
} catch (e) {
if (e && e.status === 400) {
return res.status(400).json({ success: false, message: e.message });
}
console.error('[PoolController.updateSubscription]', e);
return res.status(500).json({ success: false, message: 'Internal server error' });
}
},
async listMembers(req, res) { async listMembers(req, res) {
try { try {
const { id } = req.params || {}; const { id } = req.params || {};
@ -101,5 +135,24 @@ module.exports = {
console.error('[PoolController.removeMembers]', e); console.error('[PoolController.removeMembers]', e);
return res.status(500).json({ success: false, message: 'Internal server error' }); return res.status(500).json({ success: false, message: 'Internal server error' });
} }
},
async inflowDiagnostics(req, res) {
try {
const invoiceId = Number(req.query?.invoiceId);
if (!Number.isFinite(invoiceId) || invoiceId <= 0) {
return res.status(400).json({ success: false, message: 'invoiceId is required and must be a positive number' });
}
const data = await PoolInflowService.getInvoiceInflowDiagnostics({
invoiceId,
paidAt: req.query?.paidAt,
});
return res.status(200).json({ success: true, data });
} catch (e) {
console.error('[PoolController.inflowDiagnostics]', e);
return res.status(500).json({ success: false, message: 'Internal server error' });
}
} }
}; };

View File

@ -1,7 +1,10 @@
const UnitOfWork = require('../../database/UnitOfWork'); const UnitOfWork = require('../../database/UnitOfWork');
const ReferralService = require('../../services/referral/ReferralService'); const ReferralService = require('../../services/referral/ReferralService');
const AbonemmentService = require('../../services/abonemments/AbonemmentService');
const { logger } = require('../../middleware/logger'); const { logger } = require('../../middleware/logger');
const abonemmentService = new AbonemmentService();
class ReferralRegistrationController { class ReferralRegistrationController {
static async getReferrerInfo(req, res) { static async getReferrerInfo(req, res) {
const { token } = req.params; const { token } = req.params;
@ -53,6 +56,7 @@ class ReferralRegistrationController {
{ ...registrationData, lang }, refToken, unitOfWork { ...registrationData, lang }, refToken, unitOfWork
); );
await unitOfWork.commit(); await unitOfWork.commit();
await abonemmentService.linkGiftFlagsToUser(user.email, user.id);
logger.info('ReferralRegistrationController:registerPersonalReferral:success', { userId: user.id, email: user.email }); logger.info('ReferralRegistrationController:registerPersonalReferral:success', { userId: user.id, email: user.email });
res.json({ success: true, userId: user.id, email: user.email }); res.json({ success: true, userId: user.id, email: user.email });
} catch (error) { } catch (error) {
@ -97,6 +101,7 @@ class ReferralRegistrationController {
lang lang
}, refToken, unitOfWork); }, refToken, unitOfWork);
await unitOfWork.commit(); await unitOfWork.commit();
await abonemmentService.linkGiftFlagsToUser(user.email, user.id);
logger.info('ReferralRegistrationController:registerCompanyReferral:success', { userId: user.id, email: user.email }); logger.info('ReferralRegistrationController:registerCompanyReferral:success', { userId: user.id, email: user.email });
res.json({ success: true, userId: user.id, email: user.email }); res.json({ success: true, userId: user.id, email: user.email });
} catch (error) { } catch (error) {

View File

@ -1,6 +1,9 @@
const CompanyUserService = require('../../services/user/company/CompanyUserService'); const CompanyUserService = require('../../services/user/company/CompanyUserService');
const AbonemmentService = require('../../services/abonemments/AbonemmentService');
const { logger } = require('../../middleware/logger'); // add logger import const { logger } = require('../../middleware/logger'); // add logger import
const abonemmentService = new AbonemmentService();
class CompanyRegisterController { class CompanyRegisterController {
static async register(req, res) { static async register(req, res) {
logger.info('CompanyRegisterController.register:start'); logger.info('CompanyRegisterController.register:start');
@ -70,6 +73,8 @@ class CompanyRegisterController {
password password
}); });
await abonemmentService.linkGiftFlagsToUser(companyEmail, newCompany.id);
logger.info('CompanyRegisterController.register:success', { companyId: newCompany.id }); logger.info('CompanyRegisterController.register:success', { companyId: newCompany.id });
console.log('✅ Company user created successfully:', { console.log('✅ Company user created successfully:', {
companyId: newCompany.id, companyId: newCompany.id,

View File

@ -1,6 +1,9 @@
const PersonalUserService = require('../../services/user/personal/PersonalUserService'); const PersonalUserService = require('../../services/user/personal/PersonalUserService');
const AbonemmentService = require('../../services/abonemments/AbonemmentService');
const { logger } = require('../../middleware/logger'); // add logger import const { logger } = require('../../middleware/logger'); // add logger import
const abonemmentService = new AbonemmentService();
class PersonalRegisterController { class PersonalRegisterController {
static async register(req, res) { static async register(req, res) {
logger.info('PersonalRegisterController.register:start'); logger.info('PersonalRegisterController.register:start');
@ -70,6 +73,8 @@ class PersonalRegisterController {
referralEmail referralEmail
}); });
await abonemmentService.linkGiftFlagsToUser(email, newUser.id);
logger.info('PersonalRegisterController.register:success', { userId: newUser.id }); logger.info('PersonalRegisterController.register:success', { userId: newUser.id });
console.log('✅ Personal user created successfully:', { console.log('✅ Personal user created successfully:', {
userId: newUser.id, userId: newUser.id,

View File

@ -418,6 +418,22 @@ const createDatabase = async () => {
`); `);
console.log('✅ Document templates table created/verified'); console.log('✅ Document templates table created/verified');
await connection.query(`
CREATE TABLE IF NOT EXISTS no_user_abo_mails (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
abonement_id INT NOT NULL,
status ENUM('pending','linked') DEFAULT 'pending',
source VARCHAR(50) DEFAULT 'subscribe',
created_by_user_id INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_no_user_abo_email_abonement (email, abonement_id),
INDEX idx_no_user_abo_email_status (email, status)
);
`);
console.log('✅ no_user_abo_mails table created/verified');
// 8b. user_id_documents table: Stores ID-specific metadata (front/back object storage IDs) // 8b. user_id_documents table: Stores ID-specific metadata (front/back object storage IDs)
await connection.query(` await connection.query(`
CREATE TABLE IF NOT EXISTS user_id_documents ( CREATE TABLE IF NOT EXISTS user_id_documents (
@ -836,6 +852,38 @@ const createDatabase = async () => {
`); `);
console.log('✅ Coffee abonements table updated'); console.log('✅ Coffee abonements table updated');
// Ownership columns for "self" and "gift" subscriptions
await addColumnIfMissing(
connection,
'coffee_abonements',
'user_id',
`INT NULL AFTER referred_by`
);
await addColumnIfMissing(
connection,
'coffee_abonements',
'purchaser_user_id',
`INT NULL AFTER user_id`
);
await ensureIndex(connection, 'coffee_abonements', 'idx_abon_user_id', '`user_id`');
await ensureIndex(connection, 'coffee_abonements', 'idx_abon_purchaser_user_id', '`purchaser_user_id`');
await addForeignKeyIfMissing(
connection,
'coffee_abonements',
'fk_abon_user',
`ALTER TABLE \`coffee_abonements\`
ADD CONSTRAINT \`fk_abon_user\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE SET NULL ON UPDATE CASCADE`
);
await addForeignKeyIfMissing(
connection,
'coffee_abonements',
'fk_abon_purchaser_user',
`ALTER TABLE \`coffee_abonements\`
ADD CONSTRAINT \`fk_abon_purchaser_user\` FOREIGN KEY (\`purchaser_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE SET NULL ON UPDATE CASCADE`
);
// --- Coffee Abonement History --- // --- Coffee Abonement History ---
await connection.query(` await connection.query(`
CREATE TABLE IF NOT EXISTS coffee_abonement_history ( CREATE TABLE IF NOT EXISTS coffee_abonement_history (
@ -1013,6 +1061,7 @@ const createDatabase = async () => {
pool_name VARCHAR(255) NOT NULL, pool_name VARCHAR(255) NOT NULL,
description TEXT NULL, description TEXT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00, price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
subscription_coffee_id BIGINT NULL,
pool_type ENUM('coffee','other') NOT NULL DEFAULT 'other', pool_type ENUM('coffee','other') NOT NULL DEFAULT 'other',
is_active TINYINT(1) NOT NULL DEFAULT 1, is_active TINYINT(1) NOT NULL DEFAULT 1,
created_by INT NULL, created_by INT NULL,
@ -1020,14 +1069,30 @@ const createDatabase = async () => {
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT uq_pools_name UNIQUE (pool_name), CONSTRAINT uq_pools_name UNIQUE (pool_name),
CONSTRAINT fk_pools_subscription_coffee FOREIGN KEY (subscription_coffee_id) REFERENCES coffee_table(id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT fk_pools_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT fk_pools_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT fk_pools_updated_by FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT fk_pools_updated_by FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
INDEX idx_pools_active (is_active), INDEX idx_pools_active (is_active),
INDEX idx_pools_type (pool_type) INDEX idx_pools_type (pool_type),
INDEX idx_pools_subscription_coffee (subscription_coffee_id)
); );
`); `);
console.log('✅ Pools table created/verified'); console.log('✅ Pools table created/verified');
// Backward-compatible migration for existing pools table
await addColumnIfMissing(connection, 'pools', 'subscription_coffee_id', `BIGINT NULL`);
await ensureIndex(connection, 'pools', 'idx_pools_subscription_coffee', '`subscription_coffee_id`');
await addForeignKeyIfMissing(
connection,
'pools',
'fk_pools_subscription_coffee',
`
ALTER TABLE pools
ADD CONSTRAINT fk_pools_subscription_coffee FOREIGN KEY (subscription_coffee_id)
REFERENCES coffee_table(id) ON DELETE SET NULL ON UPDATE CASCADE
`
);
await connection.query(` await connection.query(`
CREATE TABLE IF NOT EXISTS pool_members ( CREATE TABLE IF NOT EXISTS pool_members (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
@ -1046,6 +1111,70 @@ const createDatabase = async () => {
`); `);
console.log('✅ pool_members table created/verified'); console.log('✅ pool_members table created/verified');
// Track money inflow into pools from subscriptions/invoices
await connection.query(`
CREATE TABLE IF NOT EXISTS pool_inflows (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pool_id INT NOT NULL,
invoice_id BIGINT NULL,
abonement_id BIGINT NOT NULL,
coffee_table_id BIGINT NOT NULL,
event_type ENUM('invoice_paid','subscription_created','renewal_paid','manual_adjustment') NOT NULL DEFAULT 'invoice_paid',
capsules_count INT NOT NULL,
price_per_capsule_net DECIMAL(10,4) NOT NULL,
amount_net DECIMAL(12,2) NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'EUR',
details JSON NULL,
created_by_user_id INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_pool_inflows_pool FOREIGN KEY (pool_id) REFERENCES pools(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_pool_inflows_invoice FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_pool_inflows_abon FOREIGN KEY (abonement_id) REFERENCES coffee_abonements(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_pool_inflows_coffee FOREIGN KEY (coffee_table_id) REFERENCES coffee_table(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_pool_inflows_created_by FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT uq_pool_inflow_invoice_event UNIQUE (pool_id, invoice_id, coffee_table_id, event_type),
INDEX idx_pool_inflows_pool_created (pool_id, created_at),
INDEX idx_pool_inflows_abon (abonement_id),
INDEX idx_pool_inflows_invoice (invoice_id)
);
`);
console.log('✅ pool_inflows table created/verified');
// Backward-compatible migration for existing pool_inflows table
await addColumnIfMissing(connection, 'pool_inflows', 'invoice_id', `BIGINT NULL`);
await ensureIndex(connection, 'pool_inflows', 'idx_pool_inflows_invoice', '`invoice_id`');
await addForeignKeyIfMissing(
connection,
'pool_inflows',
'fk_pool_inflows_invoice',
`
ALTER TABLE pool_inflows
ADD CONSTRAINT fk_pool_inflows_invoice FOREIGN KEY (invoice_id)
REFERENCES invoices(id) ON DELETE CASCADE ON UPDATE CASCADE
`
);
try {
await connection.query(`
ALTER TABLE pool_inflows
MODIFY COLUMN event_type ENUM('invoice_paid','subscription_created','renewal_paid','manual_adjustment') NOT NULL DEFAULT 'invoice_paid'
`);
} catch (e) {
console.log(' pool_inflows.event_type enum alignment skipped:', e.message);
}
try {
await connection.query(`ALTER TABLE pool_inflows DROP INDEX uq_pool_inflow_event`);
} catch (e) {
console.log(' old pool inflow unique index drop skipped:', e.message);
}
try {
await connection.query(`
ALTER TABLE pool_inflows
ADD CONSTRAINT uq_pool_inflow_invoice_event UNIQUE (pool_id, invoice_id, coffee_table_id, event_type)
`);
} catch (e) {
console.log(' new pool inflow unique index creation skipped:', e.message);
}
// --- user_matrix_metadata: add matrix_instance_id + alter PK --- // --- user_matrix_metadata: add matrix_instance_id + alter PK ---
await connection.query(` await connection.query(`
CREATE TABLE IF NOT EXISTS user_matrix_metadata ( CREATE TABLE IF NOT EXISTS user_matrix_metadata (

View File

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Invoice {{invoiceNumber}}</title>
<style>
body {
font-family: Arial, sans-serif;
color: #1f2937;
background: #f8fafc;
margin: 0;
padding: 24px;
}
.card {
max-width: 760px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
}
.header {
background: #0f172a;
color: #ffffff;
padding: 20px 24px;
}
.header h1 {
margin: 0;
font-size: 22px;
}
.content {
padding: 24px;
}
.meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 20px;
}
.box {
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 12px;
background: #f9fafb;
}
.label {
font-size: 12px;
color: #6b7280;
margin-bottom: 4px;
}
.value {
font-size: 14px;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
margin-bottom: 20px;
}
th, td {
text-align: left;
padding: 10px 8px;
border-bottom: 1px solid #e5e7eb;
font-size: 14px;
}
th {
background: #f3f4f6;
font-weight: 700;
}
.totals {
margin-left: auto;
width: 320px;
border: 1px solid #e5e7eb;
border-radius: 10px;
overflow: hidden;
}
.totals-row {
display: flex;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid #e5e7eb;
font-size: 14px;
}
.totals-row:last-child {
border-bottom: none;
background: #f3f4f6;
font-weight: 800;
}
.footer {
font-size: 12px;
color: #6b7280;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="card">
<div class="header">
<h1>Invoice</h1>
</div>
<div class="content">
<div class="meta">
<div class="box">
<div class="label">Invoice Number</div>
<div class="value">{{invoiceNumber}}</div>
</div>
<div class="box">
<div class="label">Issued At</div>
<div class="value">{{issuedAt}}</div>
</div>
<div class="box" style="grid-column: 1 / -1;">
<div class="label">Customer</div>
<div class="value">{{customerName}}</div>
</div>
</div>
<table>
<thead>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Unit Net</th>
<th>Line Net</th>
</tr>
</thead>
<tbody>
{{itemsHtml}}
</tbody>
</table>
<div class="totals">
<div class="totals-row">
<span>Total Net</span>
<span>{{totalNet}}</span>
</div>
<div class="totals-row">
<span>Total Tax</span>
<span>{{totalTax}}</span>
</div>
<div class="totals-row">
<span>Total Gross</span>
<span>{{totalGross}}</span>
</div>
</div>
<div class="footer">
Thank you for your subscription.
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Invoice {{invoiceNumber}}</title>
<style>
body {
font-family: Arial, sans-serif;
color: #1f2937;
background: #f8fafc;
margin: 0;
padding: 24px;
}
.card {
max-width: 760px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
}
.header {
background: #0f172a;
color: #ffffff;
padding: 20px 24px;
}
.header h1 {
margin: 0;
font-size: 22px;
}
.content {
padding: 24px;
}
.meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 20px;
}
.box {
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 12px;
background: #f9fafb;
}
.label {
font-size: 12px;
color: #6b7280;
margin-bottom: 4px;
}
.value {
font-size: 14px;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
margin-bottom: 20px;
}
th, td {
text-align: left;
padding: 10px 8px;
border-bottom: 1px solid #e5e7eb;
font-size: 14px;
}
th {
background: #f3f4f6;
font-weight: 700;
}
.totals {
margin-left: auto;
width: 320px;
border: 1px solid #e5e7eb;
border-radius: 10px;
overflow: hidden;
}
.totals-row {
display: flex;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid #e5e7eb;
font-size: 14px;
}
.totals-row:last-child {
border-bottom: none;
background: #f3f4f6;
font-weight: 800;
}
.footer {
font-size: 12px;
color: #6b7280;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="card">
<div class="header">
<h1>Invoice</h1>
</div>
<div class="content">
<div class="meta">
<div class="box">
<div class="label">Invoice Number</div>
<div class="value">{{invoiceNumber}}</div>
</div>
<div class="box">
<div class="label">Issued At</div>
<div class="value">{{issuedAt}}</div>
</div>
<div class="box" style="grid-column: 1 / -1;">
<div class="label">Customer</div>
<div class="value">{{customerName}}</div>
</div>
</div>
<table>
<thead>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Unit Net</th>
<th>Line Net</th>
</tr>
</thead>
<tbody>
{{itemsHtml}}
</tbody>
</table>
<div class="totals">
<div class="totals-row">
<span>Total Net</span>
<span>{{totalNet}}</span>
</div>
<div class="totals-row">
<span>Total Tax</span>
<span>{{totalTax}}</span>
</div>
<div class="totals-row">
<span>Total Gross</span>
<span>{{totalGross}}</span>
</div>
</div>
<div class="footer">
Thank you for your subscription.
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG</title>
<style>
/* PDF friendly page setup */
@page { size:A4; margin:15mm 12mm 18mm 12mm; }
body { font-family:Arial,Helvetica,sans-serif; line-height:1.4; font-size:13px; counter-reset: page; margin:0; }
h1 { text-align:center; font-size:18px; margin:0 0 8px; }
h2 { margin:16px 0 6px; font-size:13px; }
.page { page-break-after:always; padding:12px 20px 18px; }
.page:last-child { page-break-after:auto; }
.page-header { display:flex; justify-content:flex-end; font-size:0.65em; counter-increment:page; }
.page-header:after { content:"Seite " counter(page); }
.meta-info { border:1px solid #000; padding:8px 12px; margin:12px 0 25px; font-size:0.9em; }
.meta-info table { width:100%; border-collapse:collapse; }
.meta-info td { padding:3px 6px; vertical-align:top; border-bottom:1px solid #ccc; }
.meta-info td:first-child { font-weight:bold; width:28%; white-space:nowrap; }
.meta-info tr:last-child td { border-bottom:0; }
/* Unified signatures */
.signatures { display:flex; gap:30px; margin-top:38px; }
.signature { flex:1; text-align:center; }
/* Added: extra space below company stamp so date sits clearly underneath */
.signature-company .sig-image { margin-bottom:22px !important; }
.signature-company .sig-date { margin-top:0 !important; }
.sig-block { display:flex; flex-direction:column; align-items:center; gap:6px; min-height:120px; }
.sig-image { display:block; max-width:180px !important; max-height:70px !important; height:auto !important; margin:0 auto 6px; }
.sig-date { margin-top:4px; display:block; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:6px;}
.print-date{font-size:0.7em;}
@media print {
body { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}
/* ADDED: full size Profit Planet signature */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
</style>
</head>
<body>
<div class="page">
<div class="page-header-block">
<div class="page-header"></div>
<div class="print-date">Erstellt am: {{currentDate}}</div>
</div>
<h1>VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG</h1>
<p style="text-align:center;margin:0 0 6px;">idF 21.05.2025</p>
<p>abgeschlossen zwischen</p>
<p>Profit Planet GmbH (kurz PROFIT PLANET)<br>FN 649474i<br>Liebenauer Hauptstraße 82c<br>A-8041 Graz</p>
<p>und</p>
<p>Vertriebspartner / Businesspartner / Affiliate (kurz VP)</p>
<div class="meta-info">
<table class="meta-grid">
<tr>
<td>Vertriebspartner</td>
<td></td>
</tr>
<tr>
<td>Adresse</td>
<td></td>
</tr>
<tr>
<td>PLZ / Ort</td>
<td> </td>
</tr>
<tr>
<td>Vollständige Adresse</td>
<td></td>
</tr>
<tr>
<td>E-Mail / Telefon</td>
<td> / </td>
</tr>
</table>
</div>
<h2>1. Präambel und Vertragsgegenstand</h2>
<p>1.1. Dieser Vertrag regelt die Zusammenarbeit zwischen PROFIT PLANET und VP als Grundlage einer fairen, langfristigen und erfolgreichen Kooperation. Die VP unterstützen einander im Sinne der Ziele der Zusammenarbeit und unterrichten sich gegenseitig über alle Vorgänge, die für ihre Leistungen im Rahmen der Kooperation von Interesse sind.</p>
<p>1.2. PROFIT PLANET bietet über ein Vertriebspartner / Businesspartner / Affiliate-Netzwerk den Vertrieb verschiedener Dienstleistungen und Produkte, vornehmlich aus den Bereichen Nachhaltigkeit, Energie, Handel sowie Consulting und Coaching an.</p>
<p>1.3. Der VP vermittelt die jeweiligen Dienstleistungen, Produkte oder qualifizierten Leads, die zu einem Abschluss führen, und erhält dafür eine Provision. Für die Tätigkeit als VP ist es nicht erforderlich, weitere VP zu werben.</p>
<p>1.4. Der VP ist berechtigt, weitere Vertriebspartner / Businesspartner / Affiliate für den Vertrieb der Dienstleistungen und Produkte zu gewinnen. Für die Vermittlung und Betreuung der von ihm akquirierten Vertriebspartner / Businesspartner / Affiliate erhält der werbende VP eine Provision, die sich aus den erwirtschafteten Umsätzen der geworbenen VP ermittelt. Die Höhe der Provision ergibt sich aus der Provisionsübersicht.</p>
<p>1.5. Die Vertragsabschlüsse kommen nur zwischen dem Endkunden und dem jeweiligen Dienstleister und/oder Produktgeber (Energieversorgungs-, Handels-, Dienstleistungs- oder Coachingunternehmen) zustande, ohne dass dadurch eine Vertragsbeziehung zwischen dem VP und dem Endkunden entsteht. Ein Anspruch auf Abschluss des jeweiligen Vertrags seitens des Endkunden gegenüber PROFIT PLANET oder dem VP entsteht nicht; der Vertragsabschluss ist von der Annahme des entsprechenden Antrags durch den Dienstleister bzw. Produktgeber abhängig. PROFIT PLANET hat darauf keinen Einfluss.</p>
<p>1.6. PROFIT PLANET behält sich vor, die angebotenen Produkte zurückzuziehen, zu ändern, neue hinzuzufügen oder sonstige Anpassungen des Produktangebots vorzunehmen. PROFIT PLANET wird den VP über Änderungen von Produkten oder Tarifen nach Maßgabe der Möglichkeiten rechtzeitig vor Wirksamkeit der Änderungen informieren.</p>
<p>1.7. Die genauen Produktbestandteile und Konditionen ergeben sich aus dem jeweiligen Produktpartnerinformationsblatt, welches auf der Online-Plattform hinterlegt wird.</p>
<p>1.8. PROFIT PLANET ist berechtigt, nach eigenem Ermessen andere Personen und Unternehmen mit der Vermittlung von Produkten und Dienstleistungen von PROFIT PLANET bzw. Produktpartnern von PROFIT PLANET zu beauftragen. Es bestehen grundsätzlich keine Alleinvermittlungsaufträge und keine Exklusivität.</p>
<h2>2. Vertriebspartner / Businesspartner / Affiliate werden</h2>
<p>2.1. Kapitalgesellschaften, Personengesellschaften und volljährige natürliche Personen können Vertriebspartner / Businesspartner / Affiliate des PROFIT PLANET werden; pro Entität ist die Registrierung nur eines VP-Vertrags vorgesehen. Natürliche Personen, die bloß als Verbraucher handeln (wollen), können nicht Vertriebspartner / Businesspartner / Affiliate von PROFIT PLANET werden.</p>
<p>2.2. Kapitalgesellschaften müssen ihrem VP-Antrag die Firmenbuchnummer und gegebenenfalls die Umsatzsteuer-Identifikationsnummer (UID) beilegen. Der Antrag muss von allen Zeichnungsbereichten der Gesellschaft derart gezeichnet werden, dass eine rechtwirksame Vertretung sichergestellt ist. Die Gesellschafter haften gegenüber PROFIT PLANET jeweils persönlich für das Verhalten der Gesellschaft.</p>
<p>2.3. Absatz 2.2 gilt inhaltsgemäß auch für Personengesellschaften.</p>
<p>2.4. Der VP ist verpflichtet, Änderungen seiner unternehmens- oder personenbezogenen Daten unverzüglich an PROFIT PLANET zu melden.</p>
<p>2.5. Für die Verwendung des Online-Systems gelten die allgemeinen Geschäftsbedingungen.</p>
<p>2.6. PROFIT PLANET kann Vertriebspartner / Businesspartner / Affiliate ohne Angabe von Gründen ablehnen.</p>
<h2>3. Leistungen / Pflichten des VP</h2>
<p>3.1. Der VP handelt unabhängig als selbständiger Unternehmer, er ist weder Arbeitnehmer noch Handelsvertreter oder Makler von PROFIT PLANET. Er ist bei der Vermittlung von Produktverträgen eigenverantwortlich tätig, handelt abgesehen von den Pflichten aus diesem Vertrag frei von Weisungen und ist nicht mit der ständigen Vermittlung von Geschäften betraut. Es bestehen seitens PROFIT PLANET keine Umsatzvorgaben und keine Abnahme- oder Vertriebspflichten. Der VP trägt alle mit der Kundenakquisition verbundenen Kosten und Risiken selbst und verwendet eigene Betriebsmittel. Er stellt im geschäftlichen Verkehr klar, dass er nicht im Auftrag oder im Namen von PROFIT PLANET handelt, sondern als unabhängiger Vertriebspartner / Businesspartner / Affiliate.</p>
<p>3.2. Der VP betreibt sein Unternehmen mit der Sorgfalt eines ordentlichen Kaufmanns und ist für die Einhaltung aller gesetzlichen sowie der steuer- und sozialrechtlichen Vorgaben selbst verantwortlich.</p>
<p>3.3. Der VP hält sich insbesondere auch an das Wettbewerbsrecht und nimmt Abstand von ungenehmigter, irreführender oder sonst unlauterer Werbung. Der VP verpflichtet sich auch, falsche oder irreführende Aussagen über Dienstleistungen, Produkte und Vertriebssystem der PROFIT PLANET zu unterlassen.</p>
<p>3.4. Grundsätzlich steht es dem VP frei, Produkte / Dienstleistungen auch für andere Unternehmen zu vertreiben. Falls es allerdings in der Zusammenarbeit mit einem anderen Dienstleister oder Produktgeber in räumlicher oder zeitlicher Nähe zu Überschneidungen im Vertrieb, insbesondere bei Terminisierungen, Promotion-Auftritten (POS) oder anderen dienstleistungs- oder produktspezifischen Werbetätigkeiten kommen, so wäre dies nur nach ausdrücklicher Zustimmung durch PROFIT PLANET zulässig.</p>
<p>3.5. Beim Abschluss von Kundenverträgen ist der VP verpflichtet, die von PROFIT PLANET zur Verfügung gestellten Originalunterlagen (zB Antragsformulare, AGB, sonstige Unterlagen der Dienstleister oder Produktgeber) in der jeweils aktuellen Version zu verwenden und dem Kunden bei Vertragsabschluss vorzulegen bzw. auszuhändigen. Die Originalunterlagen sind durch den VP nicht zu verändern, missbräuchliche Verwendung ist zu verhindern.</p>
<p>3.6. Kundenverträge in Papierform sind vom VP unverzüglich, spätestens jedoch binnen 1 Woche nach Aufforderung durch PROFIT PLANET oder den Produktgeber an PROFIT PLANET auszuhändigen.</p>
<p>3.7. Sämtliche Präsentations-, Werbe- und Schulungsmaterialien sowie label von PROFIT PLANET sind urheberrechtlich geschützt und dürfen ohne ausdrückliches Einverständnis von PROFIT PLANET weder ganz noch teilweise vervielfältigt, verbreitet oder öffentlich zugänglich gemacht werden. Die Herstellung, Verwendung und Verbreitung eigener Werbemittel, Schulungsmaterialien oder Produktbroschüren ist nur nach schriftlicher Genehmigung und Freigabe durch PROFIT PLANET gestattet.</p>
<p>3.8. Der VP ist während der Dauer dieser Vereinbarung und für die Dauer von 36 Monaten nach Beendigung dieses Vertrags aus welchem Grund immer, nicht berechtigt, unmittelbar selbst bzw. mittelbar über Dritte Kunden von PROFIT PLANET und ihrer Produktpartner, einschließlich der vom VP vermittelten Endkunden, durch direkte Ansprache abzuwerben. Als Abwerben gilt jede Form des direkten Herantretens an den Kunden mit der Absicht, ihn zum Wechsel zu einem anderen Energieversorgungs-, Dienstleistungs-, Handels-, und/oder Coachingunternehmen zu bewegen (beispielsweise etwa durch Anrufe beim Kunden, Direktmailing mit Absicht der Abwerbung, Haustürgeschäfte etc.).</p>
<h2>4. Geheimhaltung</h2>
<p>4.1. Der VP verpflichtet sich, Geschäfts- und Betriebsgeheimnisse und sonstige vertrauliche Informationen von PROFIT PLANET und dessen Struktur, Geschäftspartner, Vertriebspartner / Businesspartner / Affiliate, Produktgeber, Provisionen und Endkunden unter äußerster Geheimhaltung zu behandeln und zu verwahren und diese Daten nur nach erfolgter schriftlicher Zustimmung durch den PROFIT PLANET an Dritte weiterzugeben.</p>
<p>4.2. Diese Verpflichtung gilt auch für Mitarbeiter und Unter-Vertriebspartner / Businesspartner / Affiliate des VP. Der VP hat für das Verhalten allfälliger Erfüllungsgehilfen und/oder Subpartner einzustehen.</p>
<p>4.3. Zu den Geschäftsgeheimnissen gehören insbesondere auch Informationen zu internen Betriebsabläufen, Provisionen und Provisionsstrukturen, Produkt- und Preiskalkulationen, Vertriebspartner / Businesspartner / Affiliate-strukturen und -aktivitäten.</p>
<p>4.4. Dem VP ist es nicht gestattet, auf Presseanfragen zu PROFIT PLANET, dessen Provisionspläne, Produkte oder andere Leistungen zu antworten. Presseanfragen sind immer an PROFIT PLANET weiterzuleiten.</p>
<h2>5. Datenschutz</h2>
<p>5.1. Die Vertragspartner sind verpflichtet, die gesetzlichen Datenschutzbestimmungen vollumfänglich einzuhalten. Für Verstöße gegen datenschutzrechtliche Schutzbestimmungen haftet ausschließlich der jeweils die Bestimmung verletzende Vertragspartner, dieser wird den schuldlos handelnden Vertragspartner von allen entsprechenden Ansprüchen freistellen und schad- und klaglos halten.</p>
<p>5.2. Im Regelfall ist der VP ist hinsichtlich der Daten der von ihm vermittelten Endkunden und Akquisitionskontakte Subauftragsverarbeiter im Sinne der Datenschutzgesetze (DSG, DSGVO); PROFIT PLANET ist Auftragsverarbeiter im Sinne der DSGVO. Soweit durch die gesetzlichen Bestimmungen vorgesehen, werden zu dieser Vereinbarung entsprechende datenschutzrechtliche Zusatzverträge abgeschlossen.</p>
<p>5.3. PROFIT PLANET ist bezüglich der Daten des VP auf Datenschutz verpflichtet. Die Datenschutzerklärung ist Online jederzeit abrufbar.</p>
<h2>6. VP-Schutz</h2>
<p>6.1. Ein neu geworbener VP wird in die Struktur desjenigen VP zugewiesen, der ihn geworben hat (VP-Schutz). Wenn mehrere VP denselben VP neu melden, wird seitens PROFIT PLANET nur die zuerst erfolgte Meldung berücksichtigt, wobei das Eingangsdatum des Registrierungsantrags bei PROFIT PLANET für die Zuteilung maßgeblich ist.</p>
<p>6.2. Der meldende VP ist verantwortlich dafür, die Daten des geworbenen VP vollständig und ordentlich zu übermitteln. PROFIT PLANET ist berechtigt, die Daten eines geworbenen VP aus ihrem System zu löschen, wenn von diesem innerhalb einer angemessenen Frist keine Umsätze oder Rückmeldungen kommen.</p>
<p>6.3. Ein Wechsel von der Struktur eines VP in die eines anderen ist grundsätzlich ausgeschlossen und nur ausnahmsweise möglich, wenn der wechselwillige VP nachweist, dass der in der Struktur über ihm stehende VP versucht hat, ihn zu einem gesetzes- oder vertragswidrigen Verhalten zu veranlassen oder sonst schwerwiegende Vorfälle die weitere Zusammenarbeit in der Struktur dieses VP untragbar machen. Über einen entsprechenden schriftlichen Antrag entscheidet PROFIT PLANET nach freiem Ermessen.</p>
<p>6.4. Ein VP, der innerhalb der letzten 12 Monate bereits einen VP-Vertrag mit PROFIT PLANET hatte, kann nicht geworben werden.</p>
<p>6.5. Eine Umgehung des VP-Schutzes etwa durch Verwendung der Namen von Strohnamen, -personen oder -firmen ist untersagt.</p>
<p>6.6. PROFIT PLANET räumt ihren VP ausdrücklich keinen Gebietsschutz ein. Alle VP können europaweit ohne Einschränkungen tätig sein.</p>
<h2>7. Provision</h2>
<p>7.1. Für jedes vom VP erfolgreich vermittelte Vertragsverhältnis zwischen Produktgeber und Endkunden erwirbt der VP Anspruch auf Provision als Bearbeitungs- und Aufwandspauschale</p>
<p>7.2. Die Höhe der Provision richtet sich nach der jeweils aktuell gültigen Provisionsübersicht laut Marketingkonzept. Die jeweils gültige Fassung dieser Provisionsübersicht ist jederzeit auf der Website von PROFIT PLANET (www.profit-planet.com) im internen Bereich abrufbar, einsehbar, downloadbar und kann dort auch auf Anfrage zur Verfügung gestellt werden. Änderungen der Provisionsübersicht werden dem VP rechtzeitig bekannt gegeben. Es gelten jeweils die zum Zeitpunkt der Vermittlung gültigen Provisionssätze.</p>
<p>7.3. Als erfolgreiche Vermittlung im Sinne dieses Vertrages gilt, wenn das Vertragsverhältnis zwischen Endkunden und Produktpartner tatsächlich zustande gekommen ist. Insbesondere entsteht kein Provisionsanspruch, wenn</p>
<ul>
<li>der Kunde von seinen Widerrufs- oder Rücktrittsrechten Gebrauch macht,</li>
<li>der Vertrag rechtswirksam angefochten wird,</li>
<li>der Kunde vom Dienstleister oder Produktpartner aus welchem Grund auch immer nicht angenommen wird,</li>
<li>fehlerhafte oder unvollständige Kundenanträge eingereicht werden,</li>
<li>der Vertrag widerrechtlich zustande gekommen ist oder</li>
<li>der Dienstleister oder Produktgeber die Auszahlung der Provision an PROFIT PLANET aus Gründen, die nicht von PROFIT PLANET zu verantworten sind, verweigert.</li>
</ul>
<p>7.4. Anspruch auf Auszahlung der Provision entsteht gegenüber PROFIT PLANET grundsätzlich erst dann, wenn die Zahlungen seitens des Geschäftspartners / Produktgebers bei PROFIT PLANET eingelangt sind und alle sonstigen Auszahlungsvoraussetzungen vorliegen. Der VP nimmt zur Kenntnis, dass die exakten Zahlungsmodalitäten bei den verschiedenen Dienstleistern oder Produktgebern voneinander abweichen können und PROFIT PLANET diese Unterschiede bei der Auszahlung berücksichtigt. Die unterschiedlichen Zeitspannen divergieren je nach Partnerunternehmen derzeit durchschnittlich zwischen 30 bis 100 Tage. Die genauen Anforderungen und Konditionen ergeben sich aus dem jeweiligen Produktpartnerinformationsblatt und dem Marketingkonzept.</p>
<p>7.5. Die Auszahlung durch PROFIT PLANET erfolgt einmal monatlich, ungefähr um den 20. des auf den Zahlungseingang bei PROFIT PLANET folgenden Monats. Die Auszahlung erfolgt bargeldlos per Überweisung auf das vom VP genannte Konto. PROFIT PLANET kann Zahlungen bis zu einer Höhe von EUR 100,00 von der Auszahlung ausschließen (Mindestauszahlungshöhe); die nicht ausbezahlten Provisionsansprüche werden auf dem Provisionskonto des VP rechnerisch fortgeführt und im Folgemonat nach Erreichen der Mindestauszahlungshöhe ausbezahlt. Beträge unterhalb der Mindestauszahlungshöhe werden einmal jährlich zur Auszahlung gebracht.</p>
<p>7.6. Der Provisionsanspruch entfällt rückwirkend, wenn PROFIT PLANET, Provisionen an einen Produktgeber zurückzahlen muss, etwa weil ein Kunde den Vertrag widerruft oder andere Ausschlusskriterien seitens des Produktgebers vorliegen (Stornohaftung etc.). PROFIT PLANET ist berechtigt, Forderungen, die dem PROFIT PLANET gegen den VP zustehen, mit dessen Provisionsansprüchen ganz oder teilweise aufzurechnen.</p>
<p>7.7. Mit dieser Provision sind sämtliche Tätigkeiten des VP einschließlich aller ihm in Zusammenhang mit dieser Vereinbarung entstandenen Kosten, Auslagen und Aufwendungen, wie beispielsweise Fahrt- und Reisekosten, Bürokosten, Porto und Telefongebühren, abgegolten. Dasselbe gilt für Leistungen des VP in Hinblick auf Pflege und Herstellung eines VP-Bestandes und/oder Kundenstocks, sodass im Fall der Beendigung des Vertrags unbeachtet des Grundes der Auflösung keinesfalls Ansprüche auf Abfindungen oder Ausgleiche jedweder Art gegen PROFIT PLANET bestehen.</p>
<p>7.8. Fehlerhafte Provisionszahlungen oder sonstige Zahlungen sind vom VP binnen 60 Tagen schriftlich einzumahnen. Danach gelten die Zahlungen als genehmigt.</p>
<p>7.9. Wenn vom VP keine UID-Nummer bekannt gegeben wird, erfolgen alle Auszahlungen netto.</p>
<h2>8. Vertragsstrafe, Schadenersatz</h2>
<p>8.1. Bei einem ersten Verstoß gegen die in diesem Vertrag geregelten Pflichten durch den VP erfolgt eine schriftliche Abmahnung durch PROFIT PLANET. Die Pflichtverletzung ist unmittelbar zu beenden bzw. gegebenenfalls zu beheben.</p>
<p>8.2. Kommt es erneut zu einem Verstoß gegen diesen Vertrag oder wird der zuerst gemahnte Zustand nicht beseitigt, so verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe für jeden jeweiligen Verstoß in Höhe von EUR 5.000,00.</p>
<p>8.3. Bei Verstößen gegen die Geheimhaltungs- und Datenschutzpflichten, sowie bei besonders schwerwiegenden Verstößen, insbesondere gegen Punkt 10.2 dieses Vertrags, ist PROFIT PLANET auch ohne vorhergehende Abmahnung zur Geltendmachung der jeweiligen Vertragsstrafe berechtigt.</p>
<p>8.4. Für jede Zuwiderhandlung gegen Punkt 3.8 verpflichtet sich der VP zur Zahlung einer verschuldens- und schadensunabhängigen Konventionalstrafe an den PROFIT PLANET von EUR 5.000,00 pro Verstoß (z.B. pro an ein anderes Unternehmen oder sonstigen Dritten vermittelten Vertrags oder pro abgeworbenen Kunden). Die Geltendmachung darüber hinausgehender sonstiger Schadenersatzansprüche, der Vertragsstrafe nach 8.2 oder etwa von Erfüllungsansprüchen bleibt dadurch unberührt.</p>
<p>8.5. Für jeden Verstoß gegen die in Punkt 4. dieses Vertrags (Geheimhaltungsverpflichtung) normierten Pflichten, verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe in Höhe von EUR 7.000,00 pro Verstoß. Die Geltendmachung weitergehender zivilrechtlicher Ansprüche insbesondere auf Unterlassung und Schadenersatz bleibt davon unberührt.</p>
<p>8.6. Bei Handlungen, die dem Katalog außerordentlicher Kündigungsgründe gemäß Punkt 10.2 entsprechen, insbesondere bei treuwidrigem Verhalten im Sinne der dort beschriebenen Fallgruppen (z. B. unautorisierte Kaltakquise, rufschädigendes Verhalten, unbefugtes Auftreten im Namen von PROFIT PLANET), verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe in Höhe von EUR 10.000,00 pro Verstoß. Auch in diesen Fällen bleiben darüber hinausgehende Ansprüche insbesondere Schadenersatz oder außerordentliche Kündigung ausdrücklich vorbehalten.</p>
<h2>9. Haftungsausschluss</h2>
<p>9.1. Der VP führt seine Tätigkeiten nach bestem Wissen und Gewissen und in eigener Verantwortung, insbesondere auch in Bezug auf die korrekte Beratung der Endkunden aus. Eine Haftungsübernahme von PROFIT PLANET für Falschberatungen oder sonstiges Fehlverhalten des VP ist explizit ausgeschlossen.</p>
<p>9.2. Für Schäden haftet PROFIT PLANET nur, soweit diese auf Vorsatz oder grober Fahrlässigkeit oder auf grob schuldhafter Verletzung einer wesentlichen Vertragspflicht durch PROFIT PLANET, ihrer Mitarbeiter oder Erfüllungsgehilfen beruhen.</p>
<p>9.3. Eine Haftung von PROFIT PLANET für mittelbare Schäden, Folgeschäden, entgangenen Gewinn oder erwartete Ersparnis ist jedenfalls ausgeschlossen.</p>
<p>9.4. PROFIT PLANET übernimmt keine Haftung für Schäden, die durch Datenverlust auf den Servern auftreten, außer der Schaden beruht auf Vorsatz oder grober Fahrlässigkeit seitens PROFIT PLANET, ihrer Mitarbeiter oder Erfüllungsgehilfen.</p>
<p>9.5. Der Eintritt eines Schadens ist PROFIT PLANET unverzüglich mitzuteilen.</p>
<h2>10. Vertragsdauer & Kündigung</h2>
<p>10.1. Der Vertrag tritt mit Unterzeichnung oder im Fall einer Online-Registrierung, Online mit der Annahme des Vertrags durch PROFIT PLANET in Kraft und wird auf unbestimmte Zeit geschlossen. Er kann von beiden Parteien unter Einhaltung einer Frist von drei Monaten zum Ende jedes Kalendermonats schriftlich gekündigt werden.</p>
<p>10.2. Dessen ungeachtet kann der Vertrag seitens PROFIT PLANET aus wichtigem Grund ohne Einhaltung einer Kündigungsfrist gekündigt werden. Das Recht zur außerordentlichen Kündigung besteht ungeachtet weiterer Ansprüche. Folgende Gründe berechtigen insbesondere zur außerordentlichen Kündigung, die Aufzählung ist nicht abschließend:</p>
<ul>
<li>Akte treuwidrigen Verhaltens, die eine weitere Zusammenarbeit zw. den Vertragspartnern unzumutbar machen;</li>
<li>ein solches treuwidriges Verhalten liegt insbesondere etwa dann vor, wenn ein VP ohne ausdrückliche Zustimmung eines vertretungs- und zeichnungsbefugten Organs von PROFIT PLANET Handlungen setzt, welche nach außen den Anschein erwecken, im Namen oder Auftrag von PROFIT PLANET zu erfolgen insbesondere etwa durch Kaltakquise, Verwendung von Geschäftsdrucksorten oder -signaturen, Auftritt unter Verwendung der Marke PROFIT PLANET oder vergleichbare ruf- bzw. imageschädigende Aktivitäten.</li>
<li>die Anwendung unlauterer Praktiken oder ein grober oder wiederholter Verstoß gegen diesen Vertrag sowie der Verstoß gegen zwingende Rechtsnormen;</li>
<li>wenn über das Vermögen des jeweils anderen Vertragspartners die Einleitung eines Insolvenzverfahrens beantragt oder wenn die Eröffnung eines Insolvenzverfahrens mangels Masse abgelehnt wird;</li>
<li>Verletzung der vereinbarten oder gesetzlichen Datenschutz- oder Geheimhaltungspflichten;</li>
<li>wenn die Kooperation durch das Verhalten eines Vertragspartners oder dessen Ruf in der Öffentlichkeit den anderen Vertragspartnern einen Imageschaden zufügen würde;</li>
<li>wenn die Kooperation aufgrund der Gesetzeslage oder von dritter Seite als unzulässig untersagt wird;</li>
<li>Unzulässige Nebenabsprachen mit am Vertrieb beteiligten Dritten;</li>
</ul>
<p>10.3. Abgesehen von 10.2 kann PROFIT PLANET den VP auch außerordentlich kündigen, wenn dieser in den letzten 6 Monaten keine neuen Umsätze erzielt hat oder bei den durch seine Vermittlung zustande gekommenen Verträgen zwischen Endkunden und Produktgebern über einen Zeitraum von 2 Monaten überdurchschnittliche Stornoquoten von mehr als 30% der vermittelten Verträge bestehen. PROFIT PLANET wird den VP vor einer außerordentlichen Kündigung nach diesem Passus einmalig schriftlich verwarnen, so dass der VP die Möglichkeit hat, innerhalb einer Frist von 30 Tagen die erforderlichen neuen Umsätze zu generieren oder seine Stornoquote zu verbessern.</p>
<p>10.4. Mit der Beendigung des Vertrags steht dem VP mit Ausnahme der Provision für zu diesem Zeitpunkt bereits erfolgreich vermittelte Verträge, kein Recht auf Provision mehr zu. Ein Anspruch auf Handelsvertreterausgleich ist ausdrücklich ausgeschlossen, da der VP nicht als Handelsvertreter für den PROFIT PLANET tätig wird. Etwaige Ansprüche auf Folgeprovisionen für vermittelte Produkte bestehen für 12 Monate nach Vertragsbeendigung fort; im Falle einer außerordentlichen Kündigung verfallen Ansprüche auf Folgeprovisionen unmittelbar mit der Vertragsbeendigung.</p>
<p>10.5. Nach Beendigung des Vertrags sind vom VP sämtliche überlassenen Unterlagen und Werbematerialien unaufgefordert binnen einem Monat an PROFIT PLANET zurückzugeben. Die Verwendung der Marke PROFIT PLANET und entsprechender Logos etwa auf Briefpapier oder in E-Mail-Signaturen ist nach Beendigung des Vertrags untersagt.</p>
<h2>11. Übertragung</h2>
<p>11.1. PROFIT PLANET ist jederzeit berechtigt, den Geschäftsbetrieb ganz oder teilweise auf Dritte zu übertragen.</p>
<p>11.2. Der VP ist nur mit ausdrücklicher Zustimmung von PROFIT PLANET berechtigt, seine Vertriebsstruktur an einen Dritten zu übertragen.</p>
<p>11.3. Wenn eine als VP registrierte Kapital- oder Personengesellschaft einen neuen Gesellschafter aufnimmt, hat dies auf diesen Vertrag keine Auswirkung, sofern der/die Gesellschafter, die den VP-Antrag ursprünglich unterzeichnet haben, als Gesellschafter in der Gesellschaft verbleiben. Wenn ein Gesellschafter aus einer registrierten Gesellschaft ausscheidet oder seine Anteile an einen Dritten überträgt, so ist dies in Bezug auf diesen Vertrag zulässig, sofern er dies PROFIT PLANET schriftlich unter Vorlage der entsprechenden rechtsgültigen Urkunden anzeigt, und der Vorgang keinen anderen Bestimmungen dieses Vertrags widerspricht; anderenfalls behält PROFIT PLANET sich das Recht vor, den VP-Vertrag der betreffenden Kapital- oder Personengesellschaft aufzukündigen.</p>
<p>11.4. Bei Auflösung einer als VP registrierten Gemeinschaft (Kapital- oder Personengesellschaft, aber auch z.B. Ehepartnerschaften oder ähnliches, die einen gemeinsamen VP-Vertrag haben), bleibt nur ein VP-Vertrag bestehen. Die Mitglieder der aufzulösenden Gemeinschaft haben sich intern zu einigen, durch welches Mitglied/Gesellschafter die Vertriebspartner / Businesspartner / Affiliateschaft fortgesetzt werden soll, und dies PROFIT PLANET schriftlich anzuzeigen. Falls sich die Mitglieder der Gemeinschaft in Bezug auf die Fortsetzung des VP-vertrags nicht gütlich einigen können, behält sich PROFIT PLANET das Recht einer außerordentlichen Kündigung vor, insbesondere, wenn es durch die Uneinigkeit über die Folgen zur Vernachlässigung der Pflichten des VP, einem Verstoß gegen diesen Vertrag oder geltendes Recht oder zu einer übermäßigen Belastung der Vertriebsstruktur des VP kommt.</p>
</div>
<div class="page">
<div class="page-header"></div>
<h2>12. Schlussbestimmungen</h2>
<p>12.1. Änderungen und Ergänzungen dieser Vereinbarung bedürfen der Schriftform. Dies gilt auch für das Abgehen der Schriftformerfordernis. Mündliche Nebenabreden bestehen nicht.</p>
<p>12.2. Sollte eine Bestimmung dieser Vereinbarung unwirksam sein oder werden, gilt anstelle der unwirksamen Bestimmung jene Bestimmung als vereinbart, die dem wirtschaftlichen Zweck der unwirksamen Bestimmung am nächsten kommt.</p>
<p>12.3. Vereinbarter Gerichtsstand für alle Streitigkeiten aus oder in Zusammenhang mit dieser Vereinbarung ist das für Graz sachlich zuständige Gericht. Diese Vereinbarung unterliegt österreichischem Recht, nicht jedoch den nichtzwingenden Verweisungsnormen des IPR. Weiter- bzw. Rückverweisungen sind ausgeschlossen. Darüber hinaus steht es PROFIT PLANET frei, den VP auch seinem allgemeinen Gerichtsstand zu klagen.</p>
<div class="signatures">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block">
<span class="sig-image"></span>
<div style="font-size:0.75em;line-height:1.2;"></div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</title>
<style>
@page { size: A4; margin:15mm 12mm 18mm 12mm; } /* was 20mm 17mm 22mm 17mm */
body { counter-reset:page; font-size:13px; }
h1 { font-size:18px; margin:0 0 6px; }
h2 { font-size:13px; }
.page { page-break-after:always; }
.page:last-child { page-break-after:auto; }
.page-header {
display:flex;
justify-content:flex-end;
font-size:0.65em; /* slightly smaller */
counter-increment:page;
}
.page-header:after { content:"Seite " counter(page); }
.heading-block { text-align:center; margin:0 0 10px; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:4px;}
.print-date{font-size:0.7em;}
/* ADDED signature sizing fix */
.sig-block { min-height:120px !important; }
/* UPDATED: show Profit Planet signature full size */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
.sig-date { margin-top:4px; display:block; }
@media print {
.page { padding:12px 20px 18px !important; } /* was 25px 35px 35px */
}
</style>
</head>
<body style="margin:0;font-family:Arial,Helvetica,sans-serif;line-height:1.35;-webkit-print-color-adjust:exact;print-color-adjust:exact;">
<div class="document" style="margin:0;">
<!-- PAGE 1 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<!-- CHANGED header/date grouping -->
<div class="page-header-block">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<div class="print-date" style="font-size:0.7em;text-align:right;margin-top:4px;margin-bottom:8px;">Erstellt am: {{currentDate}}</div>
</div>
<div class="heading-block">
<h1 style="margin:0 0 8px;text-align:center;">SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</h1>
<p style="margin:0 0 6px;text-align:center;">i.S.d. Art. 28 Abs. 3 Datenschutz-Grundverordnung (DS-GVO)</p>
</div>
<p style="margin:0 0 6px;">abgeschlossen zwischen</p>
<p style="margin:0 0 6px;">Profit Planet GmbH (kurz Auftraggeber)<br>
FN 649474i<br>
Liebenauer Hauptstraße 82c<br>
A-8041 Graz</p>
<p style="margin:0 0 6px;">und</p>
<p style="margin:0 0 6px;">Vertriebspartner (kurz Auftragnehmer)</p>
<div class="meta-info" style="border:1px solid #000;padding:8px 12px;margin:12px 0 25px;font-size:0.9em;">
<table class="meta-grid" style="width:100%;border-collapse:collapse;">
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;width:28%;white-space:nowrap;">Vertriebspartner</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullName}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{address}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">PLZ / Ort</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{zip_code}} {{city}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Vollständige Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullAddress}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;font-weight:bold;white-space:nowrap;border-bottom:0;">E-Mail / Telefon</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:0;">{{email}} / {{phone}}</td>
</tr>
</table>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">1. PRÄAMBEL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">1.1. Diese Anlage konkretisiert die Verpflichtungen der Vertragsparteien zum Datenschutz, die sich aus der im bestehenden Vertriebspartner-Vertrag („Hauptvertrag“) und seinen Anlagen in ihren Einzelheiten beschriebenen Auftragsverarbeitung ergeben. Sie findet Anwendung auf alle Tätigkeiten, die mit dem Vertrag in Zusammenhang stehen, und bei denen Beschäftigte des Auftragnehmers oder durch den Auftragnehmer Beauftragte personenbezogene Daten („Daten“) des Auftraggebers verarbeiten.</p>
<p style="margin:0 0 6px;">1.2. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber als Auftragsverarbeiter für Dritte („Verantwortliche“ im Sinne des Art. 4 Nr. 7 DS-GVO) tätig ist. Im Rahmen des vorbezeichneten Hauptvertrags nimmt der Auftraggeber die Dienste des Auftragnehmers als „weiteren Auftragsverarbeiter“ im Sinne von Art. 28 Nr. 4 DS-GVO in Anspruch, um bestimmte Verarbeitungstätigkeiten im Namen des Dritten („Verantwortlicher“ iSd Art. 4 Nr. 7 DS-GVO) auszuführen.</p>
<p style="margin:0 0 6px;">1.3. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber gegenüber Dritten für die Einhaltung der Pflichten des Auftragnehmers haftet, falls der Auftragnehmer seinen Datenschutzpflichten nach diesem Vertrag und nach dem Gesetz nicht nachkommt.</p>
<p style="margin:0 0 6px;">1.4. Die Laufzeit dieser Anlage richtet sich nach der Laufzeit des Vertriebspartner-Vertrages, sofern sich aus den Bestimmungen dieser Anlage nicht darüber hinausgehende Verpflichtungen ergeben.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">2. DAUER, GEGENSTAND UND SPEZIFIZIERUNG DER AUFTRAGSVERARBEITUNG</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">2.1. Alle Daten dürfen nur so lange verarbeitet werden, als das durch die Vertragserfüllung oder den Zweck der Datenverarbeitung erforderlich ist.</p>
<p style="margin:0 0 6px;">2.2. Aus dem Vertrag ergeben sich Gegenstand und Dauer des Auftrags sowie Art und Zweck der Verarbeitung.</p>
<p style="margin:0 0 6px;">2.3. Im Einzelnen sind insbesondere die folgenden Daten Bestandteil der Datenverarbeitung:</p>
</div>
</div>
</div>
<!-- PAGE 2 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<table class="data-table" style="width:100%;border-collapse:collapse;border:1px solid #000;table-layout:fixed;font-size:0.85em;margin:0 0 12px;">
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;width:180px;">Art der Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Interessenten- und Kundendaten; Kontaktdaten beim Auftraggeber; Kontaktdaten des jeweiligen Datenverantwortlichen</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Art und Zweck der Datenverarbeitung</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Datenerfassung beim Interessenten (potenziellen Kunden); Datenübermittlung (auch elektronisch via E-Mail bzw. falls vorhanden über elektronische Schnittstellen der Verantwortlichen) an Auftraggeber bzw. Datenverantwortliche zur Legung eines Angebots bzw. zur Verwirklichung der Kundenbestellung; ggf. telefonischer Nachkontakt zur Qualitätskontrolle</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Kategorien betroffener Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Name, Vorname, Adresse, Geburtsdatum, SV-Nr., E-Mail, Kontodaten Ausweiskopie; Daten zur Energieversorgung (z.B. Zählpunkt, Zählernummer, Kilowattprognose, Jahresverbrauch); Aufzeichnung etwaiger Qualitätskontrollen; Aufzeichnung etwaiger Interessensgebiete im Bereich Versicherung, Kreditwirtschaft, Telekommunikation, Energieeffizienz (PV, Speicher, LED, Infrarotheizung, Kalkschutz…).</td>
</tr>
</table>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">3. ANWENDUNGSBEREICH UND VERANTWORTLICHKEIT</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">3.1. Der Auftragnehmer verarbeitet personenbezogene Daten im Auftrag des Auftraggebers. Dies umfasst Tätigkeiten, die im Vertrag und in der Leistungsbeschreibung konkretisiert sind.</p>
<p style="margin:0 0 6px;">3.2. Der Auftraggeber ist gegenüber dem/den Dritten als („Verantwortliche Person“ iSd Art. 4 Nr. 7 DS-GVO) für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe an den Auftragnehmer sowie für die Rechtmäßigkeit der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.3. Der Auftragnehmer ist gegenüber dem Auftraggeber im Rahmen dieses Vertrages für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe sowie der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.4. Die Weisungen werden anfänglich durch diese Vertragsanlage festgelegt und können vom Auftraggeber danach in schriftlicher Form oder in einem elektronischen Format (Textform) an die vom Auftragnehmer bezeichnete Stelle durch einzelne Weisungen geändert, ergänzt oder ersetzt werden (Einzelweisung). Weisungen, die in der Vertragsanlage nicht vorgesehen sind, werden als Antrag auf Leistungsänderung behandelt. Mündliche Weisungen sind unverzüglich schriftlich oder in Textform zu bestätigen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">4. PFLICHTEN DES AUFTRAGNEHMERS</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.1. Der Auftragnehmer darf Daten von betroffenen Personen nur im Rahmen des Auftrages und der Weisungen des Auftraggebers verarbeiten, außer es liegt ein Ausnahmefall iSd Art 28 Abs. 3 a) DS-GVO vor. Der Auftragnehmer informiert den Auftraggeber unverzüglich, wenn er der Auffassung ist, dass eine Weisung gegen anwendbare Gesetze verstößt. Der Auftragnehmer darf die Umsetzung der Weisung solange aussetzen, bis sie vom Auftraggeber bestätigt oder abgeändert wurde.</p>
<p style="margin:0 0 6px;">4.2. Der Auftragnehmer wird in seinem Verantwortungsbereich die innerbetriebliche Organisation so gestalten, dass sie den besonderen Anforderungen des Datenschutzes gerecht wird. Er wird technische und organisatorische Maßnahmen zum angemessenen Schutz der Daten des Auftraggebers treffen, die den Anforderungen der Datenschutz- Grundverordnung (Art. 32 DS-GVO) genügen. Der Auftragnehmer hat technische und organisatorische Maßnahmen zu treffen, die die Vertraulichkeit, Integrität, Verfügbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherstellen. Der Auftraggeber ist berechtigt, diese technischen und organisatorischen Maßnahmen dahingehend zu überprüfen, ob sie für die Risiken der zu verarbeitenden Daten ein angemessenes Schutzniveau bieten. Eine Änderung der getroffenen Sicherheitsmaßnahmen bleibt dem Auftragnehmer vorbehalten, wobei jedoch sichergestellt sein muss, dass das vertraglich vereinbarte Schutzniveau nicht unterschritten wird.</p>
<p style="margin:0 0 6px;">4.3. Der Auftragnehmer gewährleistet, seinen Pflichten nach Art. 32 Abs. 1 lit. d) DS-GVO nachzukommen, ein Verfahren zur regelmäßigen Überprüfung der Wirksamkeit der technischen und organisatorischen Maßnahmen zur Gewährleistung der Sicherheit der Verarbeitung einzusetzen.</p>
<p style="margin:0 0 6px;">4.4. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der Anfragen und Ansprüche betroffener Personen gem. Kapitel III der DS-GVO sowie bei der Einhaltung der in Art. 33 bis 36 DS-GVO genannten Pflichten.</p>
<p style="margin:0 0 6px;">4.5. Der Auftragnehmer gewährleistet, dass es den mit der Verarbeitung der Daten des Auftraggebers befassten Mitarbeiter und andere für den Auftragnehmer tätigen Personen untersagt ist, die Daten außerhalb der Weisung zu verarbeiten. Ferner gewährleistet der Auftragnehmer, dass sich die zur Verarbeitung der personenbezogenen Daten befugten Personen zur Vertraulichkeit verpflichtet haben oder einer angemessenen gesetzlichen Verschwiegenheitspflicht unterliegen. Die Vertraulichkeits-/ Verschwiegenheitspflicht besteht auch nach Beendigung des Auftrages fort.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<!-- FIX: reopen subsection and place paragraphs inside -->
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.6. Der Auftragnehmer unterrichtet den Auftraggeber unverzüglich, wenn ihm Verletzungen des Schutzes personenbezogener Daten des Auftraggebers bekannt werden. Der Auftragnehmer trifft die erforderlichen Maßnahmen zur Sicherung der Daten und zur Minderung möglicher nachteiliger Folgen der betroffenen Personen und spricht sich hierzu unverzüglich mit dem Auftraggeber ab.</p>
<p style="margin:0 0 6px;">4.7. Der Auftragnehmer nennt dem Auftraggeber den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
<p style="margin:0 0 6px;">4.8. Der Auftragnehmer berichtigt oder löscht die vertragsgegenständlichen Daten, wenn der Auftraggeber dies anweist und dies vom Weisungsrahmen umfasst ist. Ist eine datenschutzkonforme Löschung oder eine entsprechende Einschränkung der Datenverarbeitung nicht möglich, übernimmt der Auftragnehmer die datenschutzkonforme Vernichtung von Datenträgern und sonstigen Materialien auf Grund einer Einzelbeauftragung durch den Auftraggeber oder gibt diese Datenträger an den Auftraggeber zurück, sofern nicht im Vertrag bereits vereinbart.</p>
<p style="margin:0 0 6px;">4.9. Daten, Datenträger sowie sämtliche sonstige Materialien sind nach Auftragsende auf Verlangen des Auftraggebers entweder herauszugeben oder zu löschen.</p>
<p style="margin:0 0 6px;">4.10. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
<p style="margin:0 0 6px;">4.11. Im Falle einer Inanspruchnahme des Auftraggebers durch den Dritten, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">5. PFLICHTEN DES AUFTRAGGEBERS</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">5.1. Der Auftraggeber hat den Auftragnehmer unverzüglich und vollständig zu informieren, wenn er in den Auftragsergebnissen Fehler oder Unregelmäßigkeiten bzgl. datenschutzrechtlicher Bestimmungen feststellt.</p>
<p style="margin:0 0 6px;">5.2. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, gilt §3 Abs. 10 entsprechend.</p>
<p style="margin:0 0 6px;">5.3. Der Auftraggeber nennt dem Auftragnehmer den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">6. ANFRAGEN BETROFFENER PERSONEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">6.1. Wendet sich eine betroffene Person mit Forderungen zur Berichtigung, Löschung oder Auskunft an den Auftragnehmer, wird der Auftragnehmer die betroffene Person an den Auftraggeber verweisen und ggf. den Antrag der betroffenen Person unverzüglich an den Auftraggeber weiterleiten. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der jeweiligen Forderung.</p>
<p style="margin:0 0 6px;">6.2. Der Auftragnehmer haftet nicht, wenn das Ersuchen der betroffenen Person vom Auftraggeber nicht, nicht richtig oder nicht fristgerecht beantwortet wird.</p>
<p style="margin:0 0 6px;">6.3. Der Auftraggeber haftet nicht für Forderungen betroffener Personen, die dadurch entstehen, dass der Auftragnehmer das entsprechende Anliegen nicht zeitgerecht an den Auftraggeber übermittelt hat.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">7. NACHWEISMÖGLICHKEITEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">7.1. Der Auftragnehmer weist dem Auftraggeber die Einhaltung der in diesem Vertrag niedergelegten Pflichten mit geeigneten Mitteln nach.</p>
<p style="margin:0 0 6px;">7.2. Sollten im Einzelfall Inspektionen durch den Auftraggeber oder einen von diesem beauftragten Prüfer erforderlich sein, werden diese zu den üblichen Geschäftszeiten ohne Störung des Betriebsablaufs nach Anmeldung unter Berücksichtigung einer angemessenen Vorlaufzeit durchgeführt. Der Auftragnehmer darf diese von der Unterzeichnung einer Verschwiegenheitserklärung hinsichtlich der Daten anderer Kunden und der eingerichteten technischen und organisatorischen Maßnahmen abhängig machen. Sollte der durch den Auftraggeber beauftragte Prüfer in einem Wettbewerbsverhältnis zu dem Auftragnehmer stehen, hat der Auftragnehmer gegen diesen ein Einspruchsrecht</p>
</div>
</div>
<!-- MOVED UP: Sections 8 & 9 and signatures (formerly PAGE 4) -->
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">8. SUBUNTERNEHMER (WEITERE AUFTRAGSVERARBEITER)</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">8.1. Der Einsatz von Subunternehmern als weitere Auftragsverarbeiter ist nur zulässig, wenn der Auftraggeber vorher zugestimmt hat.</p>
<p style="margin:0 0 6px;">8.2. Ein zustimmungspflichtiges Subunternehmerverhältnis liegt vor, wenn der Auftragnehmer weitere Auftragnehmer mit der ganzen oder einer Teilleistung der im Vertrag vereinbarten Leistung beauftragt. Der Auftragnehmer wird mit diesen Dritten im erforderlichen Umfang Vereinbarungen treffen, um angemessene Datenschutz- und Informationssicherheitsmaßnahmen zu gewährleisten.</p>
<p style="margin:0 0 6px;">8.3. Erteilt der Auftragnehmer Aufträge an Subunternehmer, so obliegt es dem Auftragnehmer, seine datenschutzrechtlichen Pflichten aus diesem Vertrag dem Subunternehmer zu überbinden.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">9. INFORMATIONSPFLICHTEN, SCHRIFTFORMKLAUSEL, RECHTSWAHL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">9.1. Sollten die Daten des Auftraggebers beim Auftragnehmer durch Pfändung oder Beschlagnahme, durch ein Insolvenz- oder Vergleichsverfahren oder durch sonstige Ereignisse oder Maßnahmen Dritter gefährdet werden, so hat der Auftragnehmer den Auftraggeber unverzüglich darüber zu informieren. Der Auftragnehmer wird alle in diesem Zusammenhang Verantwortlichen unverzüglich darüber informieren, dass die Hoheit und das Eigentum an den Daten ausschließlich beim Dritten als verantwortliche Person im Sinne der Datenschutz-Grundverordnung liegen.</p>
<p style="margin:0 0 6px;">9.2. Änderungen und Ergänzungen dieser Anlage und aller ihrer Bestandteile einschließlich etwaiger Zusicherungen des Auftragnehmers bedürfen einer schriftlichen Vereinbarung, die auch in einem elektronischen Format (Textform) erfolgen kann, und des ausdrücklichen Hinweises darauf, dass es sich um eine Änderung bzw. Ergänzung dieser Bedingungen handelt. Dies gilt auch für den Verzicht auf dieses Formerfordernis.</p>
<p style="margin:0 0 6px;">9.3. Bei etwaigen Widersprüchen gehen Regelungen dieser Anlage zum Datenschutz den Regelungen des Vertrages vor. Sollten einzelne Teile dieser Anlage unwirksam sein, so berührt dies die Wirksamkeit der Anlage im Übrigen nicht.</p>
<p style="margin:0 0 6px;">9.4. Es gilt das auf dem Hauptvertrag anwendbare Recht sowie Gerichtsstand.</p>
</div>
</div>
<div class="signatures" style="display:flex;gap:30px;margin-top:38px;">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:4px;min-height:110px;">
<span style="display:block;max-width:100%;max-height:80px;">{{signatureImage}}</span>
<div style="font-size:0.75em;line-height:1.2;">{{fullName}}</div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</title>
<style>
@page { size: A4; margin:15mm 12mm 18mm 12mm; } /* was 20mm 17mm 22mm 17mm */
body { counter-reset:page; font-size:13px; }
h1 { font-size:18px; margin:0 0 6px; }
h2 { font-size:13px; }
.page { page-break-after:always; }
.page:last-child { page-break-after:auto; }
.page-header {
display:flex;
justify-content:flex-end;
font-size:0.65em; /* slightly smaller */
counter-increment:page;
}
.page-header:after { content:"Seite " counter(page); }
.heading-block { text-align:center; margin:0 0 10px; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:4px;}
.print-date{font-size:0.7em;}
/* ADDED signature sizing fix */
.sig-block { min-height:120px !important; }
/* UPDATED: show Profit Planet signature full size */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
.sig-date { margin-top:4px; display:block; }
@media print {
.page { padding:12px 20px 18px !important; } /* was 25px 35px 35px */
}
</style>
</head>
<body style="margin:0;font-family:Arial,Helvetica,sans-serif;line-height:1.35;-webkit-print-color-adjust:exact;print-color-adjust:exact;">
<div class="document" style="margin:0;">
<!-- PAGE 1 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<!-- CHANGED header/date grouping -->
<div class="page-header-block">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<div class="print-date" style="font-size:0.7em;text-align:right;margin-top:4px;margin-bottom:8px;">Erstellt am: {{currentDate}}</div>
</div>
<div class="heading-block">
<h1 style="margin:0 0 8px;text-align:center;">SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</h1>
<p style="margin:0 0 6px;text-align:center;">i.S.d. Art. 28 Abs. 3 Datenschutz-Grundverordnung (DS-GVO)</p>
</div>
<p style="margin:0 0 6px;">abgeschlossen zwischen</p>
<p style="margin:0 0 6px;">Profit Planet GmbH (kurz Auftraggeber)<br>
FN 649474i<br>
Liebenauer Hauptstraße 82c<br>
A-8041 Graz</p>
<p style="margin:0 0 6px;">und</p>
<p style="margin:0 0 6px;">Vertriebspartner (kurz Auftragnehmer)</p>
<div class="meta-info" style="border:1px solid #000;padding:8px 12px;margin:12px 0 25px;font-size:0.9em;">
<table class="meta-grid" style="width:100%;border-collapse:collapse;">
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;width:28%;white-space:nowrap;">Vertriebspartner</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullName}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{address}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">PLZ / Ort</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{zip_code}} {{city}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Vollständige Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullAddress}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;font-weight:bold;white-space:nowrap;border-bottom:0;">E-Mail / Telefon</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:0;">{{email}} / {{phone}}</td>
</tr>
</table>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">1. PRÄAMBEL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">1.1. Diese Anlage konkretisiert die Verpflichtungen der Vertragsparteien zum Datenschutz, die sich aus der im bestehenden Vertriebspartner-Vertrag („Hauptvertrag“) und seinen Anlagen in ihren Einzelheiten beschriebenen Auftragsverarbeitung ergeben. Sie findet Anwendung auf alle Tätigkeiten, die mit dem Vertrag in Zusammenhang stehen, und bei denen Beschäftigte des Auftragnehmers oder durch den Auftragnehmer Beauftragte personenbezogene Daten („Daten“) des Auftraggebers verarbeiten.</p>
<p style="margin:0 0 6px;">1.2. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber als Auftragsverarbeiter für Dritte („Verantwortliche“ im Sinne des Art. 4 Nr. 7 DS-GVO) tätig ist. Im Rahmen des vorbezeichneten Hauptvertrags nimmt der Auftraggeber die Dienste des Auftragnehmers als „weiteren Auftragsverarbeiter“ im Sinne von Art. 28 Nr. 4 DS-GVO in Anspruch, um bestimmte Verarbeitungstätigkeiten im Namen des Dritten („Verantwortlicher“ iSd Art. 4 Nr. 7 DS-GVO) auszuführen.</p>
<p style="margin:0 0 6px;">1.3. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber gegenüber Dritten für die Einhaltung der Pflichten des Auftragnehmers haftet, falls der Auftragnehmer seinen Datenschutzpflichten nach diesem Vertrag und nach dem Gesetz nicht nachkommt.</p>
<p style="margin:0 0 6px;">1.4. Die Laufzeit dieser Anlage richtet sich nach der Laufzeit des Vertriebspartner-Vertrages, sofern sich aus den Bestimmungen dieser Anlage nicht darüber hinausgehende Verpflichtungen ergeben.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">2. DAUER, GEGENSTAND UND SPEZIFIZIERUNG DER AUFTRAGSVERARBEITUNG</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">2.1. Alle Daten dürfen nur so lange verarbeitet werden, als das durch die Vertragserfüllung oder den Zweck der Datenverarbeitung erforderlich ist.</p>
<p style="margin:0 0 6px;">2.2. Aus dem Vertrag ergeben sich Gegenstand und Dauer des Auftrags sowie Art und Zweck der Verarbeitung.</p>
<p style="margin:0 0 6px;">2.3. Im Einzelnen sind insbesondere die folgenden Daten Bestandteil der Datenverarbeitung:</p>
</div>
</div>
</div>
<!-- PAGE 2 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<table class="data-table" style="width:100%;border-collapse:collapse;border:1px solid #000;table-layout:fixed;font-size:0.85em;margin:0 0 12px;">
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;width:180px;">Art der Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Interessenten- und Kundendaten; Kontaktdaten beim Auftraggeber; Kontaktdaten des jeweiligen Datenverantwortlichen</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Art und Zweck der Datenverarbeitung</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Datenerfassung beim Interessenten (potenziellen Kunden); Datenübermittlung (auch elektronisch via E-Mail bzw. falls vorhanden über elektronische Schnittstellen der Verantwortlichen) an Auftraggeber bzw. Datenverantwortliche zur Legung eines Angebots bzw. zur Verwirklichung der Kundenbestellung; ggf. telefonischer Nachkontakt zur Qualitätskontrolle</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Kategorien betroffener Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Name, Vorname, Adresse, Geburtsdatum, SV-Nr., E-Mail, Kontodaten Ausweiskopie; Daten zur Energieversorgung (z.B. Zählpunkt, Zählernummer, Kilowattprognose, Jahresverbrauch); Aufzeichnung etwaiger Qualitätskontrollen; Aufzeichnung etwaiger Interessensgebiete im Bereich Versicherung, Kreditwirtschaft, Telekommunikation, Energieeffizienz (PV, Speicher, LED, Infrarotheizung, Kalkschutz…).</td>
</tr>
</table>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">3. ANWENDUNGSBEREICH UND VERANTWORTLICHKEIT</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">3.1. Der Auftragnehmer verarbeitet personenbezogene Daten im Auftrag des Auftraggebers. Dies umfasst Tätigkeiten, die im Vertrag und in der Leistungsbeschreibung konkretisiert sind.</p>
<p style="margin:0 0 6px;">3.2. Der Auftraggeber ist gegenüber dem/den Dritten als („Verantwortliche Person“ iSd Art. 4 Nr. 7 DS-GVO) für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe an den Auftragnehmer sowie für die Rechtmäßigkeit der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.3. Der Auftragnehmer ist gegenüber dem Auftraggeber im Rahmen dieses Vertrages für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe sowie der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.4. Die Weisungen werden anfänglich durch diese Vertragsanlage festgelegt und können vom Auftraggeber danach in schriftlicher Form oder in einem elektronischen Format (Textform) an die vom Auftragnehmer bezeichnete Stelle durch einzelne Weisungen geändert, ergänzt oder ersetzt werden (Einzelweisung). Weisungen, die in der Vertragsanlage nicht vorgesehen sind, werden als Antrag auf Leistungsänderung behandelt. Mündliche Weisungen sind unverzüglich schriftlich oder in Textform zu bestätigen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">4. PFLICHTEN DES AUFTRAGNEHMERS</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.1. Der Auftragnehmer darf Daten von betroffenen Personen nur im Rahmen des Auftrages und der Weisungen des Auftraggebers verarbeiten, außer es liegt ein Ausnahmefall iSd Art 28 Abs. 3 a) DS-GVO vor. Der Auftragnehmer informiert den Auftraggeber unverzüglich, wenn er der Auffassung ist, dass eine Weisung gegen anwendbare Gesetze verstößt. Der Auftragnehmer darf die Umsetzung der Weisung solange aussetzen, bis sie vom Auftraggeber bestätigt oder abgeändert wurde.</p>
<p style="margin:0 0 6px;">4.2. Der Auftragnehmer wird in seinem Verantwortungsbereich die innerbetriebliche Organisation so gestalten, dass sie den besonderen Anforderungen des Datenschutzes gerecht wird. Er wird technische und organisatorische Maßnahmen zum angemessenen Schutz der Daten des Auftraggebers treffen, die den Anforderungen der Datenschutz- Grundverordnung (Art. 32 DS-GVO) genügen. Der Auftragnehmer hat technische und organisatorische Maßnahmen zu treffen, die die Vertraulichkeit, Integrität, Verfügbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherstellen. Der Auftraggeber ist berechtigt, diese technischen und organisatorischen Maßnahmen dahingehend zu überprüfen, ob sie für die Risiken der zu verarbeitenden Daten ein angemessenes Schutzniveau bieten. Eine Änderung der getroffenen Sicherheitsmaßnahmen bleibt dem Auftragnehmer vorbehalten, wobei jedoch sichergestellt sein muss, dass das vertraglich vereinbarte Schutzniveau nicht unterschritten wird.</p>
<p style="margin:0 0 6px;">4.3. Der Auftragnehmer gewährleistet, seinen Pflichten nach Art. 32 Abs. 1 lit. d) DS-GVO nachzukommen, ein Verfahren zur regelmäßigen Überprüfung der Wirksamkeit der technischen und organisatorischen Maßnahmen zur Gewährleistung der Sicherheit der Verarbeitung einzusetzen.</p>
<p style="margin:0 0 6px;">4.4. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der Anfragen und Ansprüche betroffener Personen gem. Kapitel III der DS-GVO sowie bei der Einhaltung der in Art. 33 bis 36 DS-GVO genannten Pflichten.</p>
<p style="margin:0 0 6px;">4.5. Der Auftragnehmer gewährleistet, dass es den mit der Verarbeitung der Daten des Auftraggebers befassten Mitarbeiter und andere für den Auftragnehmer tätigen Personen untersagt ist, die Daten außerhalb der Weisung zu verarbeiten. Ferner gewährleistet der Auftragnehmer, dass sich die zur Verarbeitung der personenbezogenen Daten befugten Personen zur Vertraulichkeit verpflichtet haben oder einer angemessenen gesetzlichen Verschwiegenheitspflicht unterliegen. Die Vertraulichkeits-/ Verschwiegenheitspflicht besteht auch nach Beendigung des Auftrages fort.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<!-- FIX: reopen subsection and place paragraphs inside -->
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.6. Der Auftragnehmer unterrichtet den Auftraggeber unverzüglich, wenn ihm Verletzungen des Schutzes personenbezogener Daten des Auftraggebers bekannt werden. Der Auftragnehmer trifft die erforderlichen Maßnahmen zur Sicherung der Daten und zur Minderung möglicher nachteiliger Folgen der betroffenen Personen und spricht sich hierzu unverzüglich mit dem Auftraggeber ab.</p>
<p style="margin:0 0 6px;">4.7. Der Auftragnehmer nennt dem Auftraggeber den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
<p style="margin:0 0 6px;">4.8. Der Auftragnehmer berichtigt oder löscht die vertragsgegenständlichen Daten, wenn der Auftraggeber dies anweist und dies vom Weisungsrahmen umfasst ist. Ist eine datenschutzkonforme Löschung oder eine entsprechende Einschränkung der Datenverarbeitung nicht möglich, übernimmt der Auftragnehmer die datenschutzkonforme Vernichtung von Datenträgern und sonstigen Materialien auf Grund einer Einzelbeauftragung durch den Auftraggeber oder gibt diese Datenträger an den Auftraggeber zurück, sofern nicht im Vertrag bereits vereinbart.</p>
<p style="margin:0 0 6px;">4.9. Daten, Datenträger sowie sämtliche sonstige Materialien sind nach Auftragsende auf Verlangen des Auftraggebers entweder herauszugeben oder zu löschen.</p>
<p style="margin:0 0 6px;">4.10. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
<p style="margin:0 0 6px;">4.11. Im Falle einer Inanspruchnahme des Auftraggebers durch den Dritten, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">5. PFLICHTEN DES AUFTRAGGEBERS</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">5.1. Der Auftraggeber hat den Auftragnehmer unverzüglich und vollständig zu informieren, wenn er in den Auftragsergebnissen Fehler oder Unregelmäßigkeiten bzgl. datenschutzrechtlicher Bestimmungen feststellt.</p>
<p style="margin:0 0 6px;">5.2. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, gilt §3 Abs. 10 entsprechend.</p>
<p style="margin:0 0 6px;">5.3. Der Auftraggeber nennt dem Auftragnehmer den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">6. ANFRAGEN BETROFFENER PERSONEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">6.1. Wendet sich eine betroffene Person mit Forderungen zur Berichtigung, Löschung oder Auskunft an den Auftragnehmer, wird der Auftragnehmer die betroffene Person an den Auftraggeber verweisen und ggf. den Antrag der betroffenen Person unverzüglich an den Auftraggeber weiterleiten. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der jeweiligen Forderung.</p>
<p style="margin:0 0 6px;">6.2. Der Auftragnehmer haftet nicht, wenn das Ersuchen der betroffenen Person vom Auftraggeber nicht, nicht richtig oder nicht fristgerecht beantwortet wird.</p>
<p style="margin:0 0 6px;">6.3. Der Auftraggeber haftet nicht für Forderungen betroffener Personen, die dadurch entstehen, dass der Auftragnehmer das entsprechende Anliegen nicht zeitgerecht an den Auftraggeber übermittelt hat.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">7. NACHWEISMÖGLICHKEITEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">7.1. Der Auftragnehmer weist dem Auftraggeber die Einhaltung der in diesem Vertrag niedergelegten Pflichten mit geeigneten Mitteln nach.</p>
<p style="margin:0 0 6px;">7.2. Sollten im Einzelfall Inspektionen durch den Auftraggeber oder einen von diesem beauftragten Prüfer erforderlich sein, werden diese zu den üblichen Geschäftszeiten ohne Störung des Betriebsablaufs nach Anmeldung unter Berücksichtigung einer angemessenen Vorlaufzeit durchgeführt. Der Auftragnehmer darf diese von der Unterzeichnung einer Verschwiegenheitserklärung hinsichtlich der Daten anderer Kunden und der eingerichteten technischen und organisatorischen Maßnahmen abhängig machen. Sollte der durch den Auftraggeber beauftragte Prüfer in einem Wettbewerbsverhältnis zu dem Auftragnehmer stehen, hat der Auftragnehmer gegen diesen ein Einspruchsrecht</p>
</div>
</div>
<!-- MOVED UP: Sections 8 & 9 and signatures (formerly PAGE 4) -->
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">8. SUBUNTERNEHMER (WEITERE AUFTRAGSVERARBEITER)</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">8.1. Der Einsatz von Subunternehmern als weitere Auftragsverarbeiter ist nur zulässig, wenn der Auftraggeber vorher zugestimmt hat.</p>
<p style="margin:0 0 6px;">8.2. Ein zustimmungspflichtiges Subunternehmerverhältnis liegt vor, wenn der Auftragnehmer weitere Auftragnehmer mit der ganzen oder einer Teilleistung der im Vertrag vereinbarten Leistung beauftragt. Der Auftragnehmer wird mit diesen Dritten im erforderlichen Umfang Vereinbarungen treffen, um angemessene Datenschutz- und Informationssicherheitsmaßnahmen zu gewährleisten.</p>
<p style="margin:0 0 6px;">8.3. Erteilt der Auftragnehmer Aufträge an Subunternehmer, so obliegt es dem Auftragnehmer, seine datenschutzrechtlichen Pflichten aus diesem Vertrag dem Subunternehmer zu überbinden.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">9. INFORMATIONSPFLICHTEN, SCHRIFTFORMKLAUSEL, RECHTSWAHL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">9.1. Sollten die Daten des Auftraggebers beim Auftragnehmer durch Pfändung oder Beschlagnahme, durch ein Insolvenz- oder Vergleichsverfahren oder durch sonstige Ereignisse oder Maßnahmen Dritter gefährdet werden, so hat der Auftragnehmer den Auftraggeber unverzüglich darüber zu informieren. Der Auftragnehmer wird alle in diesem Zusammenhang Verantwortlichen unverzüglich darüber informieren, dass die Hoheit und das Eigentum an den Daten ausschließlich beim Dritten als verantwortliche Person im Sinne der Datenschutz-Grundverordnung liegen.</p>
<p style="margin:0 0 6px;">9.2. Änderungen und Ergänzungen dieser Anlage und aller ihrer Bestandteile einschließlich etwaiger Zusicherungen des Auftragnehmers bedürfen einer schriftlichen Vereinbarung, die auch in einem elektronischen Format (Textform) erfolgen kann, und des ausdrücklichen Hinweises darauf, dass es sich um eine Änderung bzw. Ergänzung dieser Bedingungen handelt. Dies gilt auch für den Verzicht auf dieses Formerfordernis.</p>
<p style="margin:0 0 6px;">9.3. Bei etwaigen Widersprüchen gehen Regelungen dieser Anlage zum Datenschutz den Regelungen des Vertrages vor. Sollten einzelne Teile dieser Anlage unwirksam sein, so berührt dies die Wirksamkeit der Anlage im Übrigen nicht.</p>
<p style="margin:0 0 6px;">9.4. Es gilt das auf dem Hauptvertrag anwendbare Recht sowie Gerichtsstand.</p>
</div>
</div>
<div class="signatures" style="display:flex;gap:30px;margin-top:38px;">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:4px;min-height:110px;">
<span style="display:block;max-width:100%;max-height:80px;">{{signatureImage}}</span>
<div style="font-size:0.75em;line-height:1.2;">{{fullName}}</div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</title>
<style>
@page { size: A4; margin:15mm 12mm 18mm 12mm; } /* was 20mm 17mm 22mm 17mm */
body { counter-reset:page; font-size:13px; }
h1 { font-size:18px; margin:0 0 6px; }
h2 { font-size:13px; }
.page { page-break-after:always; }
.page:last-child { page-break-after:auto; }
.page-header {
display:flex;
justify-content:flex-end;
font-size:0.65em; /* slightly smaller */
counter-increment:page;
}
.page-header:after { content:"Seite " counter(page); }
.heading-block { text-align:center; margin:0 0 10px; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:4px;}
.print-date{font-size:0.7em;}
/* ADDED signature sizing fix */
.sig-block { min-height:120px !important; }
/* UPDATED: show Profit Planet signature full size */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
.sig-date { margin-top:4px; display:block; }
@media print {
.page { padding:12px 20px 18px !important; } /* was 25px 35px 35px */
}
</style>
</head>
<body style="margin:0;font-family:Arial,Helvetica,sans-serif;line-height:1.35;-webkit-print-color-adjust:exact;print-color-adjust:exact;">
<div class="document" style="margin:0;">
<!-- PAGE 1 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<!-- CHANGED header/date grouping -->
<div class="page-header-block">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<div class="print-date" style="font-size:0.7em;text-align:right;margin-top:4px;margin-bottom:8px;">Erstellt am: {{currentDate}}</div>
</div>
<div class="heading-block">
<h1 style="margin:0 0 8px;text-align:center;">SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</h1>
<p style="margin:0 0 6px;text-align:center;">i.S.d. Art. 28 Abs. 3 Datenschutz-Grundverordnung (DS-GVO)</p>
</div>
<p style="margin:0 0 6px;">abgeschlossen zwischen</p>
<p style="margin:0 0 6px;">Profit Planet GmbH (kurz Auftraggeber)<br>
FN 649474i<br>
Liebenauer Hauptstraße 82c<br>
A-8041 Graz</p>
<p style="margin:0 0 6px;">und</p>
<p style="margin:0 0 6px;">Vertriebspartner (kurz Auftragnehmer)</p>
<div class="meta-info" style="border:1px solid #000;padding:8px 12px;margin:12px 0 25px;font-size:0.9em;">
<table class="meta-grid" style="width:100%;border-collapse:collapse;">
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;width:28%;white-space:nowrap;">Vertriebspartner</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;"></td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;"></td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">PLZ / Ort</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;"> </td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Vollständige Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;"></td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;font-weight:bold;white-space:nowrap;border-bottom:0;">E-Mail / Telefon</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:0;"> / </td>
</tr>
</table>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">1. PRÄAMBEL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">1.1. Diese Anlage konkretisiert die Verpflichtungen der Vertragsparteien zum Datenschutz, die sich aus der im bestehenden Vertriebspartner-Vertrag („Hauptvertrag“) und seinen Anlagen in ihren Einzelheiten beschriebenen Auftragsverarbeitung ergeben. Sie findet Anwendung auf alle Tätigkeiten, die mit dem Vertrag in Zusammenhang stehen, und bei denen Beschäftigte des Auftragnehmers oder durch den Auftragnehmer Beauftragte personenbezogene Daten („Daten“) des Auftraggebers verarbeiten.</p>
<p style="margin:0 0 6px;">1.2. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber als Auftragsverarbeiter für Dritte („Verantwortliche“ im Sinne des Art. 4 Nr. 7 DS-GVO) tätig ist. Im Rahmen des vorbezeichneten Hauptvertrags nimmt der Auftraggeber die Dienste des Auftragnehmers als „weiteren Auftragsverarbeiter“ im Sinne von Art. 28 Nr. 4 DS-GVO in Anspruch, um bestimmte Verarbeitungstätigkeiten im Namen des Dritten („Verantwortlicher“ iSd Art. 4 Nr. 7 DS-GVO) auszuführen.</p>
<p style="margin:0 0 6px;">1.3. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber gegenüber Dritten für die Einhaltung der Pflichten des Auftragnehmers haftet, falls der Auftragnehmer seinen Datenschutzpflichten nach diesem Vertrag und nach dem Gesetz nicht nachkommt.</p>
<p style="margin:0 0 6px;">1.4. Die Laufzeit dieser Anlage richtet sich nach der Laufzeit des Vertriebspartner-Vertrages, sofern sich aus den Bestimmungen dieser Anlage nicht darüber hinausgehende Verpflichtungen ergeben.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">2. DAUER, GEGENSTAND UND SPEZIFIZIERUNG DER AUFTRAGSVERARBEITUNG</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">2.1. Alle Daten dürfen nur so lange verarbeitet werden, als das durch die Vertragserfüllung oder den Zweck der Datenverarbeitung erforderlich ist.</p>
<p style="margin:0 0 6px;">2.2. Aus dem Vertrag ergeben sich Gegenstand und Dauer des Auftrags sowie Art und Zweck der Verarbeitung.</p>
<p style="margin:0 0 6px;">2.3. Im Einzelnen sind insbesondere die folgenden Daten Bestandteil der Datenverarbeitung:</p>
</div>
</div>
</div>
<!-- PAGE 2 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<table class="data-table" style="width:100%;border-collapse:collapse;border:1px solid #000;table-layout:fixed;font-size:0.85em;margin:0 0 12px;">
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;width:180px;">Art der Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Interessenten- und Kundendaten; Kontaktdaten beim Auftraggeber; Kontaktdaten des jeweiligen Datenverantwortlichen</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Art und Zweck der Datenverarbeitung</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Datenerfassung beim Interessenten (potenziellen Kunden); Datenübermittlung (auch elektronisch via E-Mail bzw. falls vorhanden über elektronische Schnittstellen der Verantwortlichen) an Auftraggeber bzw. Datenverantwortliche zur Legung eines Angebots bzw. zur Verwirklichung der Kundenbestellung; ggf. telefonischer Nachkontakt zur Qualitätskontrolle</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Kategorien betroffener Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Name, Vorname, Adresse, Geburtsdatum, SV-Nr., E-Mail, Kontodaten Ausweiskopie; Daten zur Energieversorgung (z.B. Zählpunkt, Zählernummer, Kilowattprognose, Jahresverbrauch); Aufzeichnung etwaiger Qualitätskontrollen; Aufzeichnung etwaiger Interessensgebiete im Bereich Versicherung, Kreditwirtschaft, Telekommunikation, Energieeffizienz (PV, Speicher, LED, Infrarotheizung, Kalkschutz…).</td>
</tr>
</table>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">3. ANWENDUNGSBEREICH UND VERANTWORTLICHKEIT</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">3.1. Der Auftragnehmer verarbeitet personenbezogene Daten im Auftrag des Auftraggebers. Dies umfasst Tätigkeiten, die im Vertrag und in der Leistungsbeschreibung konkretisiert sind.</p>
<p style="margin:0 0 6px;">3.2. Der Auftraggeber ist gegenüber dem/den Dritten als („Verantwortliche Person“ iSd Art. 4 Nr. 7 DS-GVO) für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe an den Auftragnehmer sowie für die Rechtmäßigkeit der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.3. Der Auftragnehmer ist gegenüber dem Auftraggeber im Rahmen dieses Vertrages für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe sowie der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.4. Die Weisungen werden anfänglich durch diese Vertragsanlage festgelegt und können vom Auftraggeber danach in schriftlicher Form oder in einem elektronischen Format (Textform) an die vom Auftragnehmer bezeichnete Stelle durch einzelne Weisungen geändert, ergänzt oder ersetzt werden (Einzelweisung). Weisungen, die in der Vertragsanlage nicht vorgesehen sind, werden als Antrag auf Leistungsänderung behandelt. Mündliche Weisungen sind unverzüglich schriftlich oder in Textform zu bestätigen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">4. PFLICHTEN DES AUFTRAGNEHMERS</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.1. Der Auftragnehmer darf Daten von betroffenen Personen nur im Rahmen des Auftrages und der Weisungen des Auftraggebers verarbeiten, außer es liegt ein Ausnahmefall iSd Art 28 Abs. 3 a) DS-GVO vor. Der Auftragnehmer informiert den Auftraggeber unverzüglich, wenn er der Auffassung ist, dass eine Weisung gegen anwendbare Gesetze verstößt. Der Auftragnehmer darf die Umsetzung der Weisung solange aussetzen, bis sie vom Auftraggeber bestätigt oder abgeändert wurde.</p>
<p style="margin:0 0 6px;">4.2. Der Auftragnehmer wird in seinem Verantwortungsbereich die innerbetriebliche Organisation so gestalten, dass sie den besonderen Anforderungen des Datenschutzes gerecht wird. Er wird technische und organisatorische Maßnahmen zum angemessenen Schutz der Daten des Auftraggebers treffen, die den Anforderungen der Datenschutz- Grundverordnung (Art. 32 DS-GVO) genügen. Der Auftragnehmer hat technische und organisatorische Maßnahmen zu treffen, die die Vertraulichkeit, Integrität, Verfügbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherstellen. Der Auftraggeber ist berechtigt, diese technischen und organisatorischen Maßnahmen dahingehend zu überprüfen, ob sie für die Risiken der zu verarbeitenden Daten ein angemessenes Schutzniveau bieten. Eine Änderung der getroffenen Sicherheitsmaßnahmen bleibt dem Auftragnehmer vorbehalten, wobei jedoch sichergestellt sein muss, dass das vertraglich vereinbarte Schutzniveau nicht unterschritten wird.</p>
<p style="margin:0 0 6px;">4.3. Der Auftragnehmer gewährleistet, seinen Pflichten nach Art. 32 Abs. 1 lit. d) DS-GVO nachzukommen, ein Verfahren zur regelmäßigen Überprüfung der Wirksamkeit der technischen und organisatorischen Maßnahmen zur Gewährleistung der Sicherheit der Verarbeitung einzusetzen.</p>
<p style="margin:0 0 6px;">4.4. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der Anfragen und Ansprüche betroffener Personen gem. Kapitel III der DS-GVO sowie bei der Einhaltung der in Art. 33 bis 36 DS-GVO genannten Pflichten.</p>
<p style="margin:0 0 6px;">4.5. Der Auftragnehmer gewährleistet, dass es den mit der Verarbeitung der Daten des Auftraggebers befassten Mitarbeiter und andere für den Auftragnehmer tätigen Personen untersagt ist, die Daten außerhalb der Weisung zu verarbeiten. Ferner gewährleistet der Auftragnehmer, dass sich die zur Verarbeitung der personenbezogenen Daten befugten Personen zur Vertraulichkeit verpflichtet haben oder einer angemessenen gesetzlichen Verschwiegenheitspflicht unterliegen. Die Vertraulichkeits-/ Verschwiegenheitspflicht besteht auch nach Beendigung des Auftrages fort.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<!-- FIX: reopen subsection and place paragraphs inside -->
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.6. Der Auftragnehmer unterrichtet den Auftraggeber unverzüglich, wenn ihm Verletzungen des Schutzes personenbezogener Daten des Auftraggebers bekannt werden. Der Auftragnehmer trifft die erforderlichen Maßnahmen zur Sicherung der Daten und zur Minderung möglicher nachteiliger Folgen der betroffenen Personen und spricht sich hierzu unverzüglich mit dem Auftraggeber ab.</p>
<p style="margin:0 0 6px;">4.7. Der Auftragnehmer nennt dem Auftraggeber den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
<p style="margin:0 0 6px;">4.8. Der Auftragnehmer berichtigt oder löscht die vertragsgegenständlichen Daten, wenn der Auftraggeber dies anweist und dies vom Weisungsrahmen umfasst ist. Ist eine datenschutzkonforme Löschung oder eine entsprechende Einschränkung der Datenverarbeitung nicht möglich, übernimmt der Auftragnehmer die datenschutzkonforme Vernichtung von Datenträgern und sonstigen Materialien auf Grund einer Einzelbeauftragung durch den Auftraggeber oder gibt diese Datenträger an den Auftraggeber zurück, sofern nicht im Vertrag bereits vereinbart.</p>
<p style="margin:0 0 6px;">4.9. Daten, Datenträger sowie sämtliche sonstige Materialien sind nach Auftragsende auf Verlangen des Auftraggebers entweder herauszugeben oder zu löschen.</p>
<p style="margin:0 0 6px;">4.10. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
<p style="margin:0 0 6px;">4.11. Im Falle einer Inanspruchnahme des Auftraggebers durch den Dritten, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">5. PFLICHTEN DES AUFTRAGGEBERS</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">5.1. Der Auftraggeber hat den Auftragnehmer unverzüglich und vollständig zu informieren, wenn er in den Auftragsergebnissen Fehler oder Unregelmäßigkeiten bzgl. datenschutzrechtlicher Bestimmungen feststellt.</p>
<p style="margin:0 0 6px;">5.2. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, gilt §3 Abs. 10 entsprechend.</p>
<p style="margin:0 0 6px;">5.3. Der Auftraggeber nennt dem Auftragnehmer den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">6. ANFRAGEN BETROFFENER PERSONEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">6.1. Wendet sich eine betroffene Person mit Forderungen zur Berichtigung, Löschung oder Auskunft an den Auftragnehmer, wird der Auftragnehmer die betroffene Person an den Auftraggeber verweisen und ggf. den Antrag der betroffenen Person unverzüglich an den Auftraggeber weiterleiten. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der jeweiligen Forderung.</p>
<p style="margin:0 0 6px;">6.2. Der Auftragnehmer haftet nicht, wenn das Ersuchen der betroffenen Person vom Auftraggeber nicht, nicht richtig oder nicht fristgerecht beantwortet wird.</p>
<p style="margin:0 0 6px;">6.3. Der Auftraggeber haftet nicht für Forderungen betroffener Personen, die dadurch entstehen, dass der Auftragnehmer das entsprechende Anliegen nicht zeitgerecht an den Auftraggeber übermittelt hat.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">7. NACHWEISMÖGLICHKEITEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">7.1. Der Auftragnehmer weist dem Auftraggeber die Einhaltung der in diesem Vertrag niedergelegten Pflichten mit geeigneten Mitteln nach.</p>
<p style="margin:0 0 6px;">7.2. Sollten im Einzelfall Inspektionen durch den Auftraggeber oder einen von diesem beauftragten Prüfer erforderlich sein, werden diese zu den üblichen Geschäftszeiten ohne Störung des Betriebsablaufs nach Anmeldung unter Berücksichtigung einer angemessenen Vorlaufzeit durchgeführt. Der Auftragnehmer darf diese von der Unterzeichnung einer Verschwiegenheitserklärung hinsichtlich der Daten anderer Kunden und der eingerichteten technischen und organisatorischen Maßnahmen abhängig machen. Sollte der durch den Auftraggeber beauftragte Prüfer in einem Wettbewerbsverhältnis zu dem Auftragnehmer stehen, hat der Auftragnehmer gegen diesen ein Einspruchsrecht</p>
</div>
</div>
<!-- MOVED UP: Sections 8 & 9 and signatures (formerly PAGE 4) -->
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">8. SUBUNTERNEHMER (WEITERE AUFTRAGSVERARBEITER)</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">8.1. Der Einsatz von Subunternehmern als weitere Auftragsverarbeiter ist nur zulässig, wenn der Auftraggeber vorher zugestimmt hat.</p>
<p style="margin:0 0 6px;">8.2. Ein zustimmungspflichtiges Subunternehmerverhältnis liegt vor, wenn der Auftragnehmer weitere Auftragnehmer mit der ganzen oder einer Teilleistung der im Vertrag vereinbarten Leistung beauftragt. Der Auftragnehmer wird mit diesen Dritten im erforderlichen Umfang Vereinbarungen treffen, um angemessene Datenschutz- und Informationssicherheitsmaßnahmen zu gewährleisten.</p>
<p style="margin:0 0 6px;">8.3. Erteilt der Auftragnehmer Aufträge an Subunternehmer, so obliegt es dem Auftragnehmer, seine datenschutzrechtlichen Pflichten aus diesem Vertrag dem Subunternehmer zu überbinden.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">9. INFORMATIONSPFLICHTEN, SCHRIFTFORMKLAUSEL, RECHTSWAHL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">9.1. Sollten die Daten des Auftraggebers beim Auftragnehmer durch Pfändung oder Beschlagnahme, durch ein Insolvenz- oder Vergleichsverfahren oder durch sonstige Ereignisse oder Maßnahmen Dritter gefährdet werden, so hat der Auftragnehmer den Auftraggeber unverzüglich darüber zu informieren. Der Auftragnehmer wird alle in diesem Zusammenhang Verantwortlichen unverzüglich darüber informieren, dass die Hoheit und das Eigentum an den Daten ausschließlich beim Dritten als verantwortliche Person im Sinne der Datenschutz-Grundverordnung liegen.</p>
<p style="margin:0 0 6px;">9.2. Änderungen und Ergänzungen dieser Anlage und aller ihrer Bestandteile einschließlich etwaiger Zusicherungen des Auftragnehmers bedürfen einer schriftlichen Vereinbarung, die auch in einem elektronischen Format (Textform) erfolgen kann, und des ausdrücklichen Hinweises darauf, dass es sich um eine Änderung bzw. Ergänzung dieser Bedingungen handelt. Dies gilt auch für den Verzicht auf dieses Formerfordernis.</p>
<p style="margin:0 0 6px;">9.3. Bei etwaigen Widersprüchen gehen Regelungen dieser Anlage zum Datenschutz den Regelungen des Vertrages vor. Sollten einzelne Teile dieser Anlage unwirksam sein, so berührt dies die Wirksamkeit der Anlage im Übrigen nicht.</p>
<p style="margin:0 0 6px;">9.4. Es gilt das auf dem Hauptvertrag anwendbare Recht sowie Gerichtsstand.</p>
</div>
</div>
<div class="signatures" style="display:flex;gap:30px;margin-top:38px;">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:4px;min-height:110px;">
<span style="display:block;max-width:100%;max-height:80px;"></span>
<div style="font-size:0.75em;line-height:1.2;"></div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG</title>
<style>
/* PDF friendly page setup */
@page { size:A4; margin:15mm 12mm 18mm 12mm; }
body { font-family:Arial,Helvetica,sans-serif; line-height:1.4; font-size:13px; counter-reset: page; margin:0; }
h1 { text-align:center; font-size:18px; margin:0 0 8px; }
h2 { margin:16px 0 6px; font-size:13px; }
.page { page-break-after:always; padding:12px 20px 18px; }
.page:last-child { page-break-after:auto; }
.page-header { display:flex; justify-content:flex-end; font-size:0.65em; counter-increment:page; }
.page-header:after { content:"Seite " counter(page); }
.meta-info { border:1px solid #000; padding:8px 12px; margin:12px 0 25px; font-size:0.9em; }
.meta-info table { width:100%; border-collapse:collapse; }
.meta-info td { padding:3px 6px; vertical-align:top; border-bottom:1px solid #ccc; }
.meta-info td:first-child { font-weight:bold; width:28%; white-space:nowrap; }
.meta-info tr:last-child td { border-bottom:0; }
/* Unified signatures */
.signatures { display:flex; gap:30px; margin-top:38px; }
.signature { flex:1; text-align:center; }
/* Added: extra space below company stamp so date sits clearly underneath */
.signature-company .sig-image { margin-bottom:22px !important; }
.signature-company .sig-date { margin-top:0 !important; }
.sig-block { display:flex; flex-direction:column; align-items:center; gap:6px; min-height:120px; }
.sig-image { display:block; max-width:180px !important; max-height:70px !important; height:auto !important; margin:0 auto 6px; }
.sig-date { margin-top:4px; display:block; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:6px;}
.print-date{font-size:0.7em;}
@media print {
body { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}
/* ADDED: full size Profit Planet signature */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
</style>
</head>
<body>
<div class="page">
<div class="page-header-block">
<div class="page-header"></div>
<div class="print-date">Erstellt am: {{currentDate}}</div>
</div>
<h1>VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG</h1>
<p style="text-align:center;margin:0 0 6px;">idF 21.05.2025</p>
<p>abgeschlossen zwischen</p>
<p>Profit Planet GmbH (kurz PROFIT PLANET)<br>FN 649474i<br>Liebenauer Hauptstraße 82c<br>A-8041 Graz</p>
<p>und</p>
<p>Vertriebspartner / Businesspartner / Affiliate (kurz VP)</p>
<div class="meta-info">
<table class="meta-grid">
<tr>
<td>Vertriebspartner</td>
<td>{{fullName}}</td>
</tr>
<tr>
<td>Adresse</td>
<td>{{address}}</td>
</tr>
<tr>
<td>PLZ / Ort</td>
<td>{{zip_code}} {{city}}</td>
</tr>
<tr>
<td>Vollständige Adresse</td>
<td>{{fullAddress}}</td>
</tr>
<tr>
<td>E-Mail / Telefon</td>
<td>{{email}} / {{phone}}</td>
</tr>
</table>
</div>
<h2>1. Präambel und Vertragsgegenstand</h2>
<p>1.1. Dieser Vertrag regelt die Zusammenarbeit zwischen PROFIT PLANET und VP als Grundlage einer fairen, langfristigen und erfolgreichen Kooperation. Die VP unterstützen einander im Sinne der Ziele der Zusammenarbeit und unterrichten sich gegenseitig über alle Vorgänge, die für ihre Leistungen im Rahmen der Kooperation von Interesse sind.</p>
<p>1.2. PROFIT PLANET bietet über ein Vertriebspartner / Businesspartner / Affiliate-Netzwerk den Vertrieb verschiedener Dienstleistungen und Produkte, vornehmlich aus den Bereichen Nachhaltigkeit, Energie, Handel sowie Consulting und Coaching an.</p>
<p>1.3. Der VP vermittelt die jeweiligen Dienstleistungen, Produkte oder qualifizierten Leads, die zu einem Abschluss führen, und erhält dafür eine Provision. Für die Tätigkeit als VP ist es nicht erforderlich, weitere VP zu werben.</p>
<p>1.4. Der VP ist berechtigt, weitere Vertriebspartner / Businesspartner / Affiliate für den Vertrieb der Dienstleistungen und Produkte zu gewinnen. Für die Vermittlung und Betreuung der von ihm akquirierten Vertriebspartner / Businesspartner / Affiliate erhält der werbende VP eine Provision, die sich aus den erwirtschafteten Umsätzen der geworbenen VP ermittelt. Die Höhe der Provision ergibt sich aus der Provisionsübersicht.</p>
<p>1.5. Die Vertragsabschlüsse kommen nur zwischen dem Endkunden und dem jeweiligen Dienstleister und/oder Produktgeber (Energieversorgungs-, Handels-, Dienstleistungs- oder Coachingunternehmen) zustande, ohne dass dadurch eine Vertragsbeziehung zwischen dem VP und dem Endkunden entsteht. Ein Anspruch auf Abschluss des jeweiligen Vertrags seitens des Endkunden gegenüber PROFIT PLANET oder dem VP entsteht nicht; der Vertragsabschluss ist von der Annahme des entsprechenden Antrags durch den Dienstleister bzw. Produktgeber abhängig. PROFIT PLANET hat darauf keinen Einfluss.</p>
<p>1.6. PROFIT PLANET behält sich vor, die angebotenen Produkte zurückzuziehen, zu ändern, neue hinzuzufügen oder sonstige Anpassungen des Produktangebots vorzunehmen. PROFIT PLANET wird den VP über Änderungen von Produkten oder Tarifen nach Maßgabe der Möglichkeiten rechtzeitig vor Wirksamkeit der Änderungen informieren.</p>
<p>1.7. Die genauen Produktbestandteile und Konditionen ergeben sich aus dem jeweiligen Produktpartnerinformationsblatt, welches auf der Online-Plattform hinterlegt wird.</p>
<p>1.8. PROFIT PLANET ist berechtigt, nach eigenem Ermessen andere Personen und Unternehmen mit der Vermittlung von Produkten und Dienstleistungen von PROFIT PLANET bzw. Produktpartnern von PROFIT PLANET zu beauftragen. Es bestehen grundsätzlich keine Alleinvermittlungsaufträge und keine Exklusivität.</p>
<h2>2. Vertriebspartner / Businesspartner / Affiliate werden</h2>
<p>2.1. Kapitalgesellschaften, Personengesellschaften und volljährige natürliche Personen können Vertriebspartner / Businesspartner / Affiliate des PROFIT PLANET werden; pro Entität ist die Registrierung nur eines VP-Vertrags vorgesehen. Natürliche Personen, die bloß als Verbraucher handeln (wollen), können nicht Vertriebspartner / Businesspartner / Affiliate von PROFIT PLANET werden.</p>
<p>2.2. Kapitalgesellschaften müssen ihrem VP-Antrag die Firmenbuchnummer und gegebenenfalls die Umsatzsteuer-Identifikationsnummer (UID) beilegen. Der Antrag muss von allen Zeichnungsbereichten der Gesellschaft derart gezeichnet werden, dass eine rechtwirksame Vertretung sichergestellt ist. Die Gesellschafter haften gegenüber PROFIT PLANET jeweils persönlich für das Verhalten der Gesellschaft.</p>
<p>2.3. Absatz 2.2 gilt inhaltsgemäß auch für Personengesellschaften.</p>
<p>2.4. Der VP ist verpflichtet, Änderungen seiner unternehmens- oder personenbezogenen Daten unverzüglich an PROFIT PLANET zu melden.</p>
<p>2.5. Für die Verwendung des Online-Systems gelten die allgemeinen Geschäftsbedingungen.</p>
<p>2.6. PROFIT PLANET kann Vertriebspartner / Businesspartner / Affiliate ohne Angabe von Gründen ablehnen.</p>
<h2>3. Leistungen / Pflichten des VP</h2>
<p>3.1. Der VP handelt unabhängig als selbständiger Unternehmer, er ist weder Arbeitnehmer noch Handelsvertreter oder Makler von PROFIT PLANET. Er ist bei der Vermittlung von Produktverträgen eigenverantwortlich tätig, handelt abgesehen von den Pflichten aus diesem Vertrag frei von Weisungen und ist nicht mit der ständigen Vermittlung von Geschäften betraut. Es bestehen seitens PROFIT PLANET keine Umsatzvorgaben und keine Abnahme- oder Vertriebspflichten. Der VP trägt alle mit der Kundenakquisition verbundenen Kosten und Risiken selbst und verwendet eigene Betriebsmittel. Er stellt im geschäftlichen Verkehr klar, dass er nicht im Auftrag oder im Namen von PROFIT PLANET handelt, sondern als unabhängiger Vertriebspartner / Businesspartner / Affiliate.</p>
<p>3.2. Der VP betreibt sein Unternehmen mit der Sorgfalt eines ordentlichen Kaufmanns und ist für die Einhaltung aller gesetzlichen sowie der steuer- und sozialrechtlichen Vorgaben selbst verantwortlich.</p>
<p>3.3. Der VP hält sich insbesondere auch an das Wettbewerbsrecht und nimmt Abstand von ungenehmigter, irreführender oder sonst unlauterer Werbung. Der VP verpflichtet sich auch, falsche oder irreführende Aussagen über Dienstleistungen, Produkte und Vertriebssystem der PROFIT PLANET zu unterlassen.</p>
<p>3.4. Grundsätzlich steht es dem VP frei, Produkte / Dienstleistungen auch für andere Unternehmen zu vertreiben. Falls es allerdings in der Zusammenarbeit mit einem anderen Dienstleister oder Produktgeber in räumlicher oder zeitlicher Nähe zu Überschneidungen im Vertrieb, insbesondere bei Terminisierungen, Promotion-Auftritten (POS) oder anderen dienstleistungs- oder produktspezifischen Werbetätigkeiten kommen, so wäre dies nur nach ausdrücklicher Zustimmung durch PROFIT PLANET zulässig.</p>
<p>3.5. Beim Abschluss von Kundenverträgen ist der VP verpflichtet, die von PROFIT PLANET zur Verfügung gestellten Originalunterlagen (zB Antragsformulare, AGB, sonstige Unterlagen der Dienstleister oder Produktgeber) in der jeweils aktuellen Version zu verwenden und dem Kunden bei Vertragsabschluss vorzulegen bzw. auszuhändigen. Die Originalunterlagen sind durch den VP nicht zu verändern, missbräuchliche Verwendung ist zu verhindern.</p>
<p>3.6. Kundenverträge in Papierform sind vom VP unverzüglich, spätestens jedoch binnen 1 Woche nach Aufforderung durch PROFIT PLANET oder den Produktgeber an PROFIT PLANET auszuhändigen.</p>
<p>3.7. Sämtliche Präsentations-, Werbe- und Schulungsmaterialien sowie label von PROFIT PLANET sind urheberrechtlich geschützt und dürfen ohne ausdrückliches Einverständnis von PROFIT PLANET weder ganz noch teilweise vervielfältigt, verbreitet oder öffentlich zugänglich gemacht werden. Die Herstellung, Verwendung und Verbreitung eigener Werbemittel, Schulungsmaterialien oder Produktbroschüren ist nur nach schriftlicher Genehmigung und Freigabe durch PROFIT PLANET gestattet.</p>
<p>3.8. Der VP ist während der Dauer dieser Vereinbarung und für die Dauer von 36 Monaten nach Beendigung dieses Vertrags aus welchem Grund immer, nicht berechtigt, unmittelbar selbst bzw. mittelbar über Dritte Kunden von PROFIT PLANET und ihrer Produktpartner, einschließlich der vom VP vermittelten Endkunden, durch direkte Ansprache abzuwerben. Als Abwerben gilt jede Form des direkten Herantretens an den Kunden mit der Absicht, ihn zum Wechsel zu einem anderen Energieversorgungs-, Dienstleistungs-, Handels-, und/oder Coachingunternehmen zu bewegen (beispielsweise etwa durch Anrufe beim Kunden, Direktmailing mit Absicht der Abwerbung, Haustürgeschäfte etc.).</p>
<h2>4. Geheimhaltung</h2>
<p>4.1. Der VP verpflichtet sich, Geschäfts- und Betriebsgeheimnisse und sonstige vertrauliche Informationen von PROFIT PLANET und dessen Struktur, Geschäftspartner, Vertriebspartner / Businesspartner / Affiliate, Produktgeber, Provisionen und Endkunden unter äußerster Geheimhaltung zu behandeln und zu verwahren und diese Daten nur nach erfolgter schriftlicher Zustimmung durch den PROFIT PLANET an Dritte weiterzugeben.</p>
<p>4.2. Diese Verpflichtung gilt auch für Mitarbeiter und Unter-Vertriebspartner / Businesspartner / Affiliate des VP. Der VP hat für das Verhalten allfälliger Erfüllungsgehilfen und/oder Subpartner einzustehen.</p>
<p>4.3. Zu den Geschäftsgeheimnissen gehören insbesondere auch Informationen zu internen Betriebsabläufen, Provisionen und Provisionsstrukturen, Produkt- und Preiskalkulationen, Vertriebspartner / Businesspartner / Affiliate-strukturen und -aktivitäten.</p>
<p>4.4. Dem VP ist es nicht gestattet, auf Presseanfragen zu PROFIT PLANET, dessen Provisionspläne, Produkte oder andere Leistungen zu antworten. Presseanfragen sind immer an PROFIT PLANET weiterzuleiten.</p>
<h2>5. Datenschutz</h2>
<p>5.1. Die Vertragspartner sind verpflichtet, die gesetzlichen Datenschutzbestimmungen vollumfänglich einzuhalten. Für Verstöße gegen datenschutzrechtliche Schutzbestimmungen haftet ausschließlich der jeweils die Bestimmung verletzende Vertragspartner, dieser wird den schuldlos handelnden Vertragspartner von allen entsprechenden Ansprüchen freistellen und schad- und klaglos halten.</p>
<p>5.2. Im Regelfall ist der VP ist hinsichtlich der Daten der von ihm vermittelten Endkunden und Akquisitionskontakte Subauftragsverarbeiter im Sinne der Datenschutzgesetze (DSG, DSGVO); PROFIT PLANET ist Auftragsverarbeiter im Sinne der DSGVO. Soweit durch die gesetzlichen Bestimmungen vorgesehen, werden zu dieser Vereinbarung entsprechende datenschutzrechtliche Zusatzverträge abgeschlossen.</p>
<p>5.3. PROFIT PLANET ist bezüglich der Daten des VP auf Datenschutz verpflichtet. Die Datenschutzerklärung ist Online jederzeit abrufbar.</p>
<h2>6. VP-Schutz</h2>
<p>6.1. Ein neu geworbener VP wird in die Struktur desjenigen VP zugewiesen, der ihn geworben hat (VP-Schutz). Wenn mehrere VP denselben VP neu melden, wird seitens PROFIT PLANET nur die zuerst erfolgte Meldung berücksichtigt, wobei das Eingangsdatum des Registrierungsantrags bei PROFIT PLANET für die Zuteilung maßgeblich ist.</p>
<p>6.2. Der meldende VP ist verantwortlich dafür, die Daten des geworbenen VP vollständig und ordentlich zu übermitteln. PROFIT PLANET ist berechtigt, die Daten eines geworbenen VP aus ihrem System zu löschen, wenn von diesem innerhalb einer angemessenen Frist keine Umsätze oder Rückmeldungen kommen.</p>
<p>6.3. Ein Wechsel von der Struktur eines VP in die eines anderen ist grundsätzlich ausgeschlossen und nur ausnahmsweise möglich, wenn der wechselwillige VP nachweist, dass der in der Struktur über ihm stehende VP versucht hat, ihn zu einem gesetzes- oder vertragswidrigen Verhalten zu veranlassen oder sonst schwerwiegende Vorfälle die weitere Zusammenarbeit in der Struktur dieses VP untragbar machen. Über einen entsprechenden schriftlichen Antrag entscheidet PROFIT PLANET nach freiem Ermessen.</p>
<p>6.4. Ein VP, der innerhalb der letzten 12 Monate bereits einen VP-Vertrag mit PROFIT PLANET hatte, kann nicht geworben werden.</p>
<p>6.5. Eine Umgehung des VP-Schutzes etwa durch Verwendung der Namen von Strohnamen, -personen oder -firmen ist untersagt.</p>
<p>6.6. PROFIT PLANET räumt ihren VP ausdrücklich keinen Gebietsschutz ein. Alle VP können europaweit ohne Einschränkungen tätig sein.</p>
<h2>7. Provision</h2>
<p>7.1. Für jedes vom VP erfolgreich vermittelte Vertragsverhältnis zwischen Produktgeber und Endkunden erwirbt der VP Anspruch auf Provision als Bearbeitungs- und Aufwandspauschale</p>
<p>7.2. Die Höhe der Provision richtet sich nach der jeweils aktuell gültigen Provisionsübersicht laut Marketingkonzept. Die jeweils gültige Fassung dieser Provisionsübersicht ist jederzeit auf der Website von PROFIT PLANET (www.profit-planet.com) im internen Bereich abrufbar, einsehbar, downloadbar und kann dort auch auf Anfrage zur Verfügung gestellt werden. Änderungen der Provisionsübersicht werden dem VP rechtzeitig bekannt gegeben. Es gelten jeweils die zum Zeitpunkt der Vermittlung gültigen Provisionssätze.</p>
<p>7.3. Als erfolgreiche Vermittlung im Sinne dieses Vertrages gilt, wenn das Vertragsverhältnis zwischen Endkunden und Produktpartner tatsächlich zustande gekommen ist. Insbesondere entsteht kein Provisionsanspruch, wenn</p>
<ul>
<li>der Kunde von seinen Widerrufs- oder Rücktrittsrechten Gebrauch macht,</li>
<li>der Vertrag rechtswirksam angefochten wird,</li>
<li>der Kunde vom Dienstleister oder Produktpartner aus welchem Grund auch immer nicht angenommen wird,</li>
<li>fehlerhafte oder unvollständige Kundenanträge eingereicht werden,</li>
<li>der Vertrag widerrechtlich zustande gekommen ist oder</li>
<li>der Dienstleister oder Produktgeber die Auszahlung der Provision an PROFIT PLANET aus Gründen, die nicht von PROFIT PLANET zu verantworten sind, verweigert.</li>
</ul>
<p>7.4. Anspruch auf Auszahlung der Provision entsteht gegenüber PROFIT PLANET grundsätzlich erst dann, wenn die Zahlungen seitens des Geschäftspartners / Produktgebers bei PROFIT PLANET eingelangt sind und alle sonstigen Auszahlungsvoraussetzungen vorliegen. Der VP nimmt zur Kenntnis, dass die exakten Zahlungsmodalitäten bei den verschiedenen Dienstleistern oder Produktgebern voneinander abweichen können und PROFIT PLANET diese Unterschiede bei der Auszahlung berücksichtigt. Die unterschiedlichen Zeitspannen divergieren je nach Partnerunternehmen derzeit durchschnittlich zwischen 30 bis 100 Tage. Die genauen Anforderungen und Konditionen ergeben sich aus dem jeweiligen Produktpartnerinformationsblatt und dem Marketingkonzept.</p>
<p>7.5. Die Auszahlung durch PROFIT PLANET erfolgt einmal monatlich, ungefähr um den 20. des auf den Zahlungseingang bei PROFIT PLANET folgenden Monats. Die Auszahlung erfolgt bargeldlos per Überweisung auf das vom VP genannte Konto. PROFIT PLANET kann Zahlungen bis zu einer Höhe von EUR 100,00 von der Auszahlung ausschließen (Mindestauszahlungshöhe); die nicht ausbezahlten Provisionsansprüche werden auf dem Provisionskonto des VP rechnerisch fortgeführt und im Folgemonat nach Erreichen der Mindestauszahlungshöhe ausbezahlt. Beträge unterhalb der Mindestauszahlungshöhe werden einmal jährlich zur Auszahlung gebracht.</p>
<p>7.6. Der Provisionsanspruch entfällt rückwirkend, wenn PROFIT PLANET, Provisionen an einen Produktgeber zurückzahlen muss, etwa weil ein Kunde den Vertrag widerruft oder andere Ausschlusskriterien seitens des Produktgebers vorliegen (Stornohaftung etc.). PROFIT PLANET ist berechtigt, Forderungen, die dem PROFIT PLANET gegen den VP zustehen, mit dessen Provisionsansprüchen ganz oder teilweise aufzurechnen.</p>
<p>7.7. Mit dieser Provision sind sämtliche Tätigkeiten des VP einschließlich aller ihm in Zusammenhang mit dieser Vereinbarung entstandenen Kosten, Auslagen und Aufwendungen, wie beispielsweise Fahrt- und Reisekosten, Bürokosten, Porto und Telefongebühren, abgegolten. Dasselbe gilt für Leistungen des VP in Hinblick auf Pflege und Herstellung eines VP-Bestandes und/oder Kundenstocks, sodass im Fall der Beendigung des Vertrags unbeachtet des Grundes der Auflösung keinesfalls Ansprüche auf Abfindungen oder Ausgleiche jedweder Art gegen PROFIT PLANET bestehen.</p>
<p>7.8. Fehlerhafte Provisionszahlungen oder sonstige Zahlungen sind vom VP binnen 60 Tagen schriftlich einzumahnen. Danach gelten die Zahlungen als genehmigt.</p>
<p>7.9. Wenn vom VP keine UID-Nummer bekannt gegeben wird, erfolgen alle Auszahlungen netto.</p>
<h2>8. Vertragsstrafe, Schadenersatz</h2>
<p>8.1. Bei einem ersten Verstoß gegen die in diesem Vertrag geregelten Pflichten durch den VP erfolgt eine schriftliche Abmahnung durch PROFIT PLANET. Die Pflichtverletzung ist unmittelbar zu beenden bzw. gegebenenfalls zu beheben.</p>
<p>8.2. Kommt es erneut zu einem Verstoß gegen diesen Vertrag oder wird der zuerst gemahnte Zustand nicht beseitigt, so verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe für jeden jeweiligen Verstoß in Höhe von EUR 5.000,00.</p>
<p>8.3. Bei Verstößen gegen die Geheimhaltungs- und Datenschutzpflichten, sowie bei besonders schwerwiegenden Verstößen, insbesondere gegen Punkt 10.2 dieses Vertrags, ist PROFIT PLANET auch ohne vorhergehende Abmahnung zur Geltendmachung der jeweiligen Vertragsstrafe berechtigt.</p>
<p>8.4. Für jede Zuwiderhandlung gegen Punkt 3.8 verpflichtet sich der VP zur Zahlung einer verschuldens- und schadensunabhängigen Konventionalstrafe an den PROFIT PLANET von EUR 5.000,00 pro Verstoß (z.B. pro an ein anderes Unternehmen oder sonstigen Dritten vermittelten Vertrags oder pro abgeworbenen Kunden). Die Geltendmachung darüber hinausgehender sonstiger Schadenersatzansprüche, der Vertragsstrafe nach 8.2 oder etwa von Erfüllungsansprüchen bleibt dadurch unberührt.</p>
<p>8.5. Für jeden Verstoß gegen die in Punkt 4. dieses Vertrags (Geheimhaltungsverpflichtung) normierten Pflichten, verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe in Höhe von EUR 7.000,00 pro Verstoß. Die Geltendmachung weitergehender zivilrechtlicher Ansprüche insbesondere auf Unterlassung und Schadenersatz bleibt davon unberührt.</p>
<p>8.6. Bei Handlungen, die dem Katalog außerordentlicher Kündigungsgründe gemäß Punkt 10.2 entsprechen, insbesondere bei treuwidrigem Verhalten im Sinne der dort beschriebenen Fallgruppen (z. B. unautorisierte Kaltakquise, rufschädigendes Verhalten, unbefugtes Auftreten im Namen von PROFIT PLANET), verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe in Höhe von EUR 10.000,00 pro Verstoß. Auch in diesen Fällen bleiben darüber hinausgehende Ansprüche insbesondere Schadenersatz oder außerordentliche Kündigung ausdrücklich vorbehalten.</p>
<h2>9. Haftungsausschluss</h2>
<p>9.1. Der VP führt seine Tätigkeiten nach bestem Wissen und Gewissen und in eigener Verantwortung, insbesondere auch in Bezug auf die korrekte Beratung der Endkunden aus. Eine Haftungsübernahme von PROFIT PLANET für Falschberatungen oder sonstiges Fehlverhalten des VP ist explizit ausgeschlossen.</p>
<p>9.2. Für Schäden haftet PROFIT PLANET nur, soweit diese auf Vorsatz oder grober Fahrlässigkeit oder auf grob schuldhafter Verletzung einer wesentlichen Vertragspflicht durch PROFIT PLANET, ihrer Mitarbeiter oder Erfüllungsgehilfen beruhen.</p>
<p>9.3. Eine Haftung von PROFIT PLANET für mittelbare Schäden, Folgeschäden, entgangenen Gewinn oder erwartete Ersparnis ist jedenfalls ausgeschlossen.</p>
<p>9.4. PROFIT PLANET übernimmt keine Haftung für Schäden, die durch Datenverlust auf den Servern auftreten, außer der Schaden beruht auf Vorsatz oder grober Fahrlässigkeit seitens PROFIT PLANET, ihrer Mitarbeiter oder Erfüllungsgehilfen.</p>
<p>9.5. Der Eintritt eines Schadens ist PROFIT PLANET unverzüglich mitzuteilen.</p>
<h2>10. Vertragsdauer & Kündigung</h2>
<p>10.1. Der Vertrag tritt mit Unterzeichnung oder im Fall einer Online-Registrierung, Online mit der Annahme des Vertrags durch PROFIT PLANET in Kraft und wird auf unbestimmte Zeit geschlossen. Er kann von beiden Parteien unter Einhaltung einer Frist von drei Monaten zum Ende jedes Kalendermonats schriftlich gekündigt werden.</p>
<p>10.2. Dessen ungeachtet kann der Vertrag seitens PROFIT PLANET aus wichtigem Grund ohne Einhaltung einer Kündigungsfrist gekündigt werden. Das Recht zur außerordentlichen Kündigung besteht ungeachtet weiterer Ansprüche. Folgende Gründe berechtigen insbesondere zur außerordentlichen Kündigung, die Aufzählung ist nicht abschließend:</p>
<ul>
<li>Akte treuwidrigen Verhaltens, die eine weitere Zusammenarbeit zw. den Vertragspartnern unzumutbar machen;</li>
<li>ein solches treuwidriges Verhalten liegt insbesondere etwa dann vor, wenn ein VP ohne ausdrückliche Zustimmung eines vertretungs- und zeichnungsbefugten Organs von PROFIT PLANET Handlungen setzt, welche nach außen den Anschein erwecken, im Namen oder Auftrag von PROFIT PLANET zu erfolgen insbesondere etwa durch Kaltakquise, Verwendung von Geschäftsdrucksorten oder -signaturen, Auftritt unter Verwendung der Marke PROFIT PLANET oder vergleichbare ruf- bzw. imageschädigende Aktivitäten.</li>
<li>die Anwendung unlauterer Praktiken oder ein grober oder wiederholter Verstoß gegen diesen Vertrag sowie der Verstoß gegen zwingende Rechtsnormen;</li>
<li>wenn über das Vermögen des jeweils anderen Vertragspartners die Einleitung eines Insolvenzverfahrens beantragt oder wenn die Eröffnung eines Insolvenzverfahrens mangels Masse abgelehnt wird;</li>
<li>Verletzung der vereinbarten oder gesetzlichen Datenschutz- oder Geheimhaltungspflichten;</li>
<li>wenn die Kooperation durch das Verhalten eines Vertragspartners oder dessen Ruf in der Öffentlichkeit den anderen Vertragspartnern einen Imageschaden zufügen würde;</li>
<li>wenn die Kooperation aufgrund der Gesetzeslage oder von dritter Seite als unzulässig untersagt wird;</li>
<li>Unzulässige Nebenabsprachen mit am Vertrieb beteiligten Dritten;</li>
</ul>
<p>10.3. Abgesehen von 10.2 kann PROFIT PLANET den VP auch außerordentlich kündigen, wenn dieser in den letzten 6 Monaten keine neuen Umsätze erzielt hat oder bei den durch seine Vermittlung zustande gekommenen Verträgen zwischen Endkunden und Produktgebern über einen Zeitraum von 2 Monaten überdurchschnittliche Stornoquoten von mehr als 30% der vermittelten Verträge bestehen. PROFIT PLANET wird den VP vor einer außerordentlichen Kündigung nach diesem Passus einmalig schriftlich verwarnen, so dass der VP die Möglichkeit hat, innerhalb einer Frist von 30 Tagen die erforderlichen neuen Umsätze zu generieren oder seine Stornoquote zu verbessern.</p>
<p>10.4. Mit der Beendigung des Vertrags steht dem VP mit Ausnahme der Provision für zu diesem Zeitpunkt bereits erfolgreich vermittelte Verträge, kein Recht auf Provision mehr zu. Ein Anspruch auf Handelsvertreterausgleich ist ausdrücklich ausgeschlossen, da der VP nicht als Handelsvertreter für den PROFIT PLANET tätig wird. Etwaige Ansprüche auf Folgeprovisionen für vermittelte Produkte bestehen für 12 Monate nach Vertragsbeendigung fort; im Falle einer außerordentlichen Kündigung verfallen Ansprüche auf Folgeprovisionen unmittelbar mit der Vertragsbeendigung.</p>
<p>10.5. Nach Beendigung des Vertrags sind vom VP sämtliche überlassenen Unterlagen und Werbematerialien unaufgefordert binnen einem Monat an PROFIT PLANET zurückzugeben. Die Verwendung der Marke PROFIT PLANET und entsprechender Logos etwa auf Briefpapier oder in E-Mail-Signaturen ist nach Beendigung des Vertrags untersagt.</p>
<h2>11. Übertragung</h2>
<p>11.1. PROFIT PLANET ist jederzeit berechtigt, den Geschäftsbetrieb ganz oder teilweise auf Dritte zu übertragen.</p>
<p>11.2. Der VP ist nur mit ausdrücklicher Zustimmung von PROFIT PLANET berechtigt, seine Vertriebsstruktur an einen Dritten zu übertragen.</p>
<p>11.3. Wenn eine als VP registrierte Kapital- oder Personengesellschaft einen neuen Gesellschafter aufnimmt, hat dies auf diesen Vertrag keine Auswirkung, sofern der/die Gesellschafter, die den VP-Antrag ursprünglich unterzeichnet haben, als Gesellschafter in der Gesellschaft verbleiben. Wenn ein Gesellschafter aus einer registrierten Gesellschaft ausscheidet oder seine Anteile an einen Dritten überträgt, so ist dies in Bezug auf diesen Vertrag zulässig, sofern er dies PROFIT PLANET schriftlich unter Vorlage der entsprechenden rechtsgültigen Urkunden anzeigt, und der Vorgang keinen anderen Bestimmungen dieses Vertrags widerspricht; anderenfalls behält PROFIT PLANET sich das Recht vor, den VP-Vertrag der betreffenden Kapital- oder Personengesellschaft aufzukündigen.</p>
<p>11.4. Bei Auflösung einer als VP registrierten Gemeinschaft (Kapital- oder Personengesellschaft, aber auch z.B. Ehepartnerschaften oder ähnliches, die einen gemeinsamen VP-Vertrag haben), bleibt nur ein VP-Vertrag bestehen. Die Mitglieder der aufzulösenden Gemeinschaft haben sich intern zu einigen, durch welches Mitglied/Gesellschafter die Vertriebspartner / Businesspartner / Affiliateschaft fortgesetzt werden soll, und dies PROFIT PLANET schriftlich anzuzeigen. Falls sich die Mitglieder der Gemeinschaft in Bezug auf die Fortsetzung des VP-vertrags nicht gütlich einigen können, behält sich PROFIT PLANET das Recht einer außerordentlichen Kündigung vor, insbesondere, wenn es durch die Uneinigkeit über die Folgen zur Vernachlässigung der Pflichten des VP, einem Verstoß gegen diesen Vertrag oder geltendes Recht oder zu einer übermäßigen Belastung der Vertriebsstruktur des VP kommt.</p>
</div>
<div class="page">
<div class="page-header"></div>
<h2>12. Schlussbestimmungen</h2>
<p>12.1. Änderungen und Ergänzungen dieser Vereinbarung bedürfen der Schriftform. Dies gilt auch für das Abgehen der Schriftformerfordernis. Mündliche Nebenabreden bestehen nicht.</p>
<p>12.2. Sollte eine Bestimmung dieser Vereinbarung unwirksam sein oder werden, gilt anstelle der unwirksamen Bestimmung jene Bestimmung als vereinbart, die dem wirtschaftlichen Zweck der unwirksamen Bestimmung am nächsten kommt.</p>
<p>12.3. Vereinbarter Gerichtsstand für alle Streitigkeiten aus oder in Zusammenhang mit dieser Vereinbarung ist das für Graz sachlich zuständige Gericht. Diese Vereinbarung unterliegt österreichischem Recht, nicht jedoch den nichtzwingenden Verweisungsnormen des IPR. Weiter- bzw. Rückverweisungen sind ausgeschlossen. Darüber hinaus steht es PROFIT PLANET frei, den VP auch seinem allgemeinen Gerichtsstand zu klagen.</p>
<div class="signatures">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block">
<span class="sig-image">{{signatureImage}}</span>
<div style="font-size:0.75em;line-height:1.2;">{{fullName}}</div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG</title>
<style>
/* PDF friendly page setup */
@page { size:A4; margin:15mm 12mm 18mm 12mm; }
body { font-family:Arial,Helvetica,sans-serif; line-height:1.4; font-size:13px; counter-reset: page; margin:0; }
h1 { text-align:center; font-size:18px; margin:0 0 8px; }
h2 { margin:16px 0 6px; font-size:13px; }
.page { page-break-after:always; padding:12px 20px 18px; }
.page:last-child { page-break-after:auto; }
.page-header { display:flex; justify-content:flex-end; font-size:0.65em; counter-increment:page; }
.page-header:after { content:"Seite " counter(page); }
.meta-info { border:1px solid #000; padding:8px 12px; margin:12px 0 25px; font-size:0.9em; }
.meta-info table { width:100%; border-collapse:collapse; }
.meta-info td { padding:3px 6px; vertical-align:top; border-bottom:1px solid #ccc; }
.meta-info td:first-child { font-weight:bold; width:28%; white-space:nowrap; }
.meta-info tr:last-child td { border-bottom:0; }
/* Unified signatures */
.signatures { display:flex; gap:30px; margin-top:38px; }
.signature { flex:1; text-align:center; }
/* Added: extra space below company stamp so date sits clearly underneath */
.signature-company .sig-image { margin-bottom:22px !important; }
.signature-company .sig-date { margin-top:0 !important; }
.sig-block { display:flex; flex-direction:column; align-items:center; gap:6px; min-height:120px; }
.sig-image { display:block; max-width:180px !important; max-height:70px !important; height:auto !important; margin:0 auto 6px; }
.sig-date { margin-top:4px; display:block; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:6px;}
.print-date{font-size:0.7em;}
@media print {
body { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}
/* ADDED: full size Profit Planet signature */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
</style>
</head>
<body>
<div class="page">
<div class="page-header-block">
<div class="page-header"></div>
<div class="print-date">Erstellt am: {{currentDate}}</div>
</div>
<h1>VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG</h1>
<p style="text-align:center;margin:0 0 6px;">idF 21.05.2025</p>
<p>abgeschlossen zwischen</p>
<p>Profit Planet GmbH (kurz PROFIT PLANET)<br>FN 649474i<br>Liebenauer Hauptstraße 82c<br>A-8041 Graz</p>
<p>und</p>
<p>Vertriebspartner / Businesspartner / Affiliate (kurz VP)</p>
<div class="meta-info">
<table class="meta-grid">
<tr>
<td>Vertriebspartner</td>
<td>{{fullName}}</td>
</tr>
<tr>
<td>Adresse</td>
<td>{{address}}</td>
</tr>
<tr>
<td>PLZ / Ort</td>
<td>{{zip_code}} {{city}}</td>
</tr>
<tr>
<td>Vollständige Adresse</td>
<td>{{fullAddress}}</td>
</tr>
<tr>
<td>E-Mail / Telefon</td>
<td>{{email}} / {{phone}}</td>
</tr>
</table>
</div>
<h2>1. Präambel und Vertragsgegenstand</h2>
<p>1.1. Dieser Vertrag regelt die Zusammenarbeit zwischen PROFIT PLANET und VP als Grundlage einer fairen, langfristigen und erfolgreichen Kooperation. Die VP unterstützen einander im Sinne der Ziele der Zusammenarbeit und unterrichten sich gegenseitig über alle Vorgänge, die für ihre Leistungen im Rahmen der Kooperation von Interesse sind.</p>
<p>1.2. PROFIT PLANET bietet über ein Vertriebspartner / Businesspartner / Affiliate-Netzwerk den Vertrieb verschiedener Dienstleistungen und Produkte, vornehmlich aus den Bereichen Nachhaltigkeit, Energie, Handel sowie Consulting und Coaching an.</p>
<p>1.3. Der VP vermittelt die jeweiligen Dienstleistungen, Produkte oder qualifizierten Leads, die zu einem Abschluss führen, und erhält dafür eine Provision. Für die Tätigkeit als VP ist es nicht erforderlich, weitere VP zu werben.</p>
<p>1.4. Der VP ist berechtigt, weitere Vertriebspartner / Businesspartner / Affiliate für den Vertrieb der Dienstleistungen und Produkte zu gewinnen. Für die Vermittlung und Betreuung der von ihm akquirierten Vertriebspartner / Businesspartner / Affiliate erhält der werbende VP eine Provision, die sich aus den erwirtschafteten Umsätzen der geworbenen VP ermittelt. Die Höhe der Provision ergibt sich aus der Provisionsübersicht.</p>
<p>1.5. Die Vertragsabschlüsse kommen nur zwischen dem Endkunden und dem jeweiligen Dienstleister und/oder Produktgeber (Energieversorgungs-, Handels-, Dienstleistungs- oder Coachingunternehmen) zustande, ohne dass dadurch eine Vertragsbeziehung zwischen dem VP und dem Endkunden entsteht. Ein Anspruch auf Abschluss des jeweiligen Vertrags seitens des Endkunden gegenüber PROFIT PLANET oder dem VP entsteht nicht; der Vertragsabschluss ist von der Annahme des entsprechenden Antrags durch den Dienstleister bzw. Produktgeber abhängig. PROFIT PLANET hat darauf keinen Einfluss.</p>
<p>1.6. PROFIT PLANET behält sich vor, die angebotenen Produkte zurückzuziehen, zu ändern, neue hinzuzufügen oder sonstige Anpassungen des Produktangebots vorzunehmen. PROFIT PLANET wird den VP über Änderungen von Produkten oder Tarifen nach Maßgabe der Möglichkeiten rechtzeitig vor Wirksamkeit der Änderungen informieren.</p>
<p>1.7. Die genauen Produktbestandteile und Konditionen ergeben sich aus dem jeweiligen Produktpartnerinformationsblatt, welches auf der Online-Plattform hinterlegt wird.</p>
<p>1.8. PROFIT PLANET ist berechtigt, nach eigenem Ermessen andere Personen und Unternehmen mit der Vermittlung von Produkten und Dienstleistungen von PROFIT PLANET bzw. Produktpartnern von PROFIT PLANET zu beauftragen. Es bestehen grundsätzlich keine Alleinvermittlungsaufträge und keine Exklusivität.</p>
<h2>2. Vertriebspartner / Businesspartner / Affiliate werden</h2>
<p>2.1. Kapitalgesellschaften, Personengesellschaften und volljährige natürliche Personen können Vertriebspartner / Businesspartner / Affiliate des PROFIT PLANET werden; pro Entität ist die Registrierung nur eines VP-Vertrags vorgesehen. Natürliche Personen, die bloß als Verbraucher handeln (wollen), können nicht Vertriebspartner / Businesspartner / Affiliate von PROFIT PLANET werden.</p>
<p>2.2. Kapitalgesellschaften müssen ihrem VP-Antrag die Firmenbuchnummer und gegebenenfalls die Umsatzsteuer-Identifikationsnummer (UID) beilegen. Der Antrag muss von allen Zeichnungsbereichten der Gesellschaft derart gezeichnet werden, dass eine rechtwirksame Vertretung sichergestellt ist. Die Gesellschafter haften gegenüber PROFIT PLANET jeweils persönlich für das Verhalten der Gesellschaft.</p>
<p>2.3. Absatz 2.2 gilt inhaltsgemäß auch für Personengesellschaften.</p>
<p>2.4. Der VP ist verpflichtet, Änderungen seiner unternehmens- oder personenbezogenen Daten unverzüglich an PROFIT PLANET zu melden.</p>
<p>2.5. Für die Verwendung des Online-Systems gelten die allgemeinen Geschäftsbedingungen.</p>
<p>2.6. PROFIT PLANET kann Vertriebspartner / Businesspartner / Affiliate ohne Angabe von Gründen ablehnen.</p>
<h2>3. Leistungen / Pflichten des VP</h2>
<p>3.1. Der VP handelt unabhängig als selbständiger Unternehmer, er ist weder Arbeitnehmer noch Handelsvertreter oder Makler von PROFIT PLANET. Er ist bei der Vermittlung von Produktverträgen eigenverantwortlich tätig, handelt abgesehen von den Pflichten aus diesem Vertrag frei von Weisungen und ist nicht mit der ständigen Vermittlung von Geschäften betraut. Es bestehen seitens PROFIT PLANET keine Umsatzvorgaben und keine Abnahme- oder Vertriebspflichten. Der VP trägt alle mit der Kundenakquisition verbundenen Kosten und Risiken selbst und verwendet eigene Betriebsmittel. Er stellt im geschäftlichen Verkehr klar, dass er nicht im Auftrag oder im Namen von PROFIT PLANET handelt, sondern als unabhängiger Vertriebspartner / Businesspartner / Affiliate.</p>
<p>3.2. Der VP betreibt sein Unternehmen mit der Sorgfalt eines ordentlichen Kaufmanns und ist für die Einhaltung aller gesetzlichen sowie der steuer- und sozialrechtlichen Vorgaben selbst verantwortlich.</p>
<p>3.3. Der VP hält sich insbesondere auch an das Wettbewerbsrecht und nimmt Abstand von ungenehmigter, irreführender oder sonst unlauterer Werbung. Der VP verpflichtet sich auch, falsche oder irreführende Aussagen über Dienstleistungen, Produkte und Vertriebssystem der PROFIT PLANET zu unterlassen.</p>
<p>3.4. Grundsätzlich steht es dem VP frei, Produkte / Dienstleistungen auch für andere Unternehmen zu vertreiben. Falls es allerdings in der Zusammenarbeit mit einem anderen Dienstleister oder Produktgeber in räumlicher oder zeitlicher Nähe zu Überschneidungen im Vertrieb, insbesondere bei Terminisierungen, Promotion-Auftritten (POS) oder anderen dienstleistungs- oder produktspezifischen Werbetätigkeiten kommen, so wäre dies nur nach ausdrücklicher Zustimmung durch PROFIT PLANET zulässig.</p>
<p>3.5. Beim Abschluss von Kundenverträgen ist der VP verpflichtet, die von PROFIT PLANET zur Verfügung gestellten Originalunterlagen (zB Antragsformulare, AGB, sonstige Unterlagen der Dienstleister oder Produktgeber) in der jeweils aktuellen Version zu verwenden und dem Kunden bei Vertragsabschluss vorzulegen bzw. auszuhändigen. Die Originalunterlagen sind durch den VP nicht zu verändern, missbräuchliche Verwendung ist zu verhindern.</p>
<p>3.6. Kundenverträge in Papierform sind vom VP unverzüglich, spätestens jedoch binnen 1 Woche nach Aufforderung durch PROFIT PLANET oder den Produktgeber an PROFIT PLANET auszuhändigen.</p>
<p>3.7. Sämtliche Präsentations-, Werbe- und Schulungsmaterialien sowie label von PROFIT PLANET sind urheberrechtlich geschützt und dürfen ohne ausdrückliches Einverständnis von PROFIT PLANET weder ganz noch teilweise vervielfältigt, verbreitet oder öffentlich zugänglich gemacht werden. Die Herstellung, Verwendung und Verbreitung eigener Werbemittel, Schulungsmaterialien oder Produktbroschüren ist nur nach schriftlicher Genehmigung und Freigabe durch PROFIT PLANET gestattet.</p>
<p>3.8. Der VP ist während der Dauer dieser Vereinbarung und für die Dauer von 36 Monaten nach Beendigung dieses Vertrags aus welchem Grund immer, nicht berechtigt, unmittelbar selbst bzw. mittelbar über Dritte Kunden von PROFIT PLANET und ihrer Produktpartner, einschließlich der vom VP vermittelten Endkunden, durch direkte Ansprache abzuwerben. Als Abwerben gilt jede Form des direkten Herantretens an den Kunden mit der Absicht, ihn zum Wechsel zu einem anderen Energieversorgungs-, Dienstleistungs-, Handels-, und/oder Coachingunternehmen zu bewegen (beispielsweise etwa durch Anrufe beim Kunden, Direktmailing mit Absicht der Abwerbung, Haustürgeschäfte etc.).</p>
<h2>4. Geheimhaltung</h2>
<p>4.1. Der VP verpflichtet sich, Geschäfts- und Betriebsgeheimnisse und sonstige vertrauliche Informationen von PROFIT PLANET und dessen Struktur, Geschäftspartner, Vertriebspartner / Businesspartner / Affiliate, Produktgeber, Provisionen und Endkunden unter äußerster Geheimhaltung zu behandeln und zu verwahren und diese Daten nur nach erfolgter schriftlicher Zustimmung durch den PROFIT PLANET an Dritte weiterzugeben.</p>
<p>4.2. Diese Verpflichtung gilt auch für Mitarbeiter und Unter-Vertriebspartner / Businesspartner / Affiliate des VP. Der VP hat für das Verhalten allfälliger Erfüllungsgehilfen und/oder Subpartner einzustehen.</p>
<p>4.3. Zu den Geschäftsgeheimnissen gehören insbesondere auch Informationen zu internen Betriebsabläufen, Provisionen und Provisionsstrukturen, Produkt- und Preiskalkulationen, Vertriebspartner / Businesspartner / Affiliate-strukturen und -aktivitäten.</p>
<p>4.4. Dem VP ist es nicht gestattet, auf Presseanfragen zu PROFIT PLANET, dessen Provisionspläne, Produkte oder andere Leistungen zu antworten. Presseanfragen sind immer an PROFIT PLANET weiterzuleiten.</p>
<h2>5. Datenschutz</h2>
<p>5.1. Die Vertragspartner sind verpflichtet, die gesetzlichen Datenschutzbestimmungen vollumfänglich einzuhalten. Für Verstöße gegen datenschutzrechtliche Schutzbestimmungen haftet ausschließlich der jeweils die Bestimmung verletzende Vertragspartner, dieser wird den schuldlos handelnden Vertragspartner von allen entsprechenden Ansprüchen freistellen und schad- und klaglos halten.</p>
<p>5.2. Im Regelfall ist der VP ist hinsichtlich der Daten der von ihm vermittelten Endkunden und Akquisitionskontakte Subauftragsverarbeiter im Sinne der Datenschutzgesetze (DSG, DSGVO); PROFIT PLANET ist Auftragsverarbeiter im Sinne der DSGVO. Soweit durch die gesetzlichen Bestimmungen vorgesehen, werden zu dieser Vereinbarung entsprechende datenschutzrechtliche Zusatzverträge abgeschlossen.</p>
<p>5.3. PROFIT PLANET ist bezüglich der Daten des VP auf Datenschutz verpflichtet. Die Datenschutzerklärung ist Online jederzeit abrufbar.</p>
<h2>6. VP-Schutz</h2>
<p>6.1. Ein neu geworbener VP wird in die Struktur desjenigen VP zugewiesen, der ihn geworben hat (VP-Schutz). Wenn mehrere VP denselben VP neu melden, wird seitens PROFIT PLANET nur die zuerst erfolgte Meldung berücksichtigt, wobei das Eingangsdatum des Registrierungsantrags bei PROFIT PLANET für die Zuteilung maßgeblich ist.</p>
<p>6.2. Der meldende VP ist verantwortlich dafür, die Daten des geworbenen VP vollständig und ordentlich zu übermitteln. PROFIT PLANET ist berechtigt, die Daten eines geworbenen VP aus ihrem System zu löschen, wenn von diesem innerhalb einer angemessenen Frist keine Umsätze oder Rückmeldungen kommen.</p>
<p>6.3. Ein Wechsel von der Struktur eines VP in die eines anderen ist grundsätzlich ausgeschlossen und nur ausnahmsweise möglich, wenn der wechselwillige VP nachweist, dass der in der Struktur über ihm stehende VP versucht hat, ihn zu einem gesetzes- oder vertragswidrigen Verhalten zu veranlassen oder sonst schwerwiegende Vorfälle die weitere Zusammenarbeit in der Struktur dieses VP untragbar machen. Über einen entsprechenden schriftlichen Antrag entscheidet PROFIT PLANET nach freiem Ermessen.</p>
<p>6.4. Ein VP, der innerhalb der letzten 12 Monate bereits einen VP-Vertrag mit PROFIT PLANET hatte, kann nicht geworben werden.</p>
<p>6.5. Eine Umgehung des VP-Schutzes etwa durch Verwendung der Namen von Strohnamen, -personen oder -firmen ist untersagt.</p>
<p>6.6. PROFIT PLANET räumt ihren VP ausdrücklich keinen Gebietsschutz ein. Alle VP können europaweit ohne Einschränkungen tätig sein.</p>
<h2>7. Provision</h2>
<p>7.1. Für jedes vom VP erfolgreich vermittelte Vertragsverhältnis zwischen Produktgeber und Endkunden erwirbt der VP Anspruch auf Provision als Bearbeitungs- und Aufwandspauschale</p>
<p>7.2. Die Höhe der Provision richtet sich nach der jeweils aktuell gültigen Provisionsübersicht laut Marketingkonzept. Die jeweils gültige Fassung dieser Provisionsübersicht ist jederzeit auf der Website von PROFIT PLANET (www.profit-planet.com) im internen Bereich abrufbar, einsehbar, downloadbar und kann dort auch auf Anfrage zur Verfügung gestellt werden. Änderungen der Provisionsübersicht werden dem VP rechtzeitig bekannt gegeben. Es gelten jeweils die zum Zeitpunkt der Vermittlung gültigen Provisionssätze.</p>
<p>7.3. Als erfolgreiche Vermittlung im Sinne dieses Vertrages gilt, wenn das Vertragsverhältnis zwischen Endkunden und Produktpartner tatsächlich zustande gekommen ist. Insbesondere entsteht kein Provisionsanspruch, wenn</p>
<ul>
<li>der Kunde von seinen Widerrufs- oder Rücktrittsrechten Gebrauch macht,</li>
<li>der Vertrag rechtswirksam angefochten wird,</li>
<li>der Kunde vom Dienstleister oder Produktpartner aus welchem Grund auch immer nicht angenommen wird,</li>
<li>fehlerhafte oder unvollständige Kundenanträge eingereicht werden,</li>
<li>der Vertrag widerrechtlich zustande gekommen ist oder</li>
<li>der Dienstleister oder Produktgeber die Auszahlung der Provision an PROFIT PLANET aus Gründen, die nicht von PROFIT PLANET zu verantworten sind, verweigert.</li>
</ul>
<p>7.4. Anspruch auf Auszahlung der Provision entsteht gegenüber PROFIT PLANET grundsätzlich erst dann, wenn die Zahlungen seitens des Geschäftspartners / Produktgebers bei PROFIT PLANET eingelangt sind und alle sonstigen Auszahlungsvoraussetzungen vorliegen. Der VP nimmt zur Kenntnis, dass die exakten Zahlungsmodalitäten bei den verschiedenen Dienstleistern oder Produktgebern voneinander abweichen können und PROFIT PLANET diese Unterschiede bei der Auszahlung berücksichtigt. Die unterschiedlichen Zeitspannen divergieren je nach Partnerunternehmen derzeit durchschnittlich zwischen 30 bis 100 Tage. Die genauen Anforderungen und Konditionen ergeben sich aus dem jeweiligen Produktpartnerinformationsblatt und dem Marketingkonzept.</p>
<p>7.5. Die Auszahlung durch PROFIT PLANET erfolgt einmal monatlich, ungefähr um den 20. des auf den Zahlungseingang bei PROFIT PLANET folgenden Monats. Die Auszahlung erfolgt bargeldlos per Überweisung auf das vom VP genannte Konto. PROFIT PLANET kann Zahlungen bis zu einer Höhe von EUR 100,00 von der Auszahlung ausschließen (Mindestauszahlungshöhe); die nicht ausbezahlten Provisionsansprüche werden auf dem Provisionskonto des VP rechnerisch fortgeführt und im Folgemonat nach Erreichen der Mindestauszahlungshöhe ausbezahlt. Beträge unterhalb der Mindestauszahlungshöhe werden einmal jährlich zur Auszahlung gebracht.</p>
<p>7.6. Der Provisionsanspruch entfällt rückwirkend, wenn PROFIT PLANET, Provisionen an einen Produktgeber zurückzahlen muss, etwa weil ein Kunde den Vertrag widerruft oder andere Ausschlusskriterien seitens des Produktgebers vorliegen (Stornohaftung etc.). PROFIT PLANET ist berechtigt, Forderungen, die dem PROFIT PLANET gegen den VP zustehen, mit dessen Provisionsansprüchen ganz oder teilweise aufzurechnen.</p>
<p>7.7. Mit dieser Provision sind sämtliche Tätigkeiten des VP einschließlich aller ihm in Zusammenhang mit dieser Vereinbarung entstandenen Kosten, Auslagen und Aufwendungen, wie beispielsweise Fahrt- und Reisekosten, Bürokosten, Porto und Telefongebühren, abgegolten. Dasselbe gilt für Leistungen des VP in Hinblick auf Pflege und Herstellung eines VP-Bestandes und/oder Kundenstocks, sodass im Fall der Beendigung des Vertrags unbeachtet des Grundes der Auflösung keinesfalls Ansprüche auf Abfindungen oder Ausgleiche jedweder Art gegen PROFIT PLANET bestehen.</p>
<p>7.8. Fehlerhafte Provisionszahlungen oder sonstige Zahlungen sind vom VP binnen 60 Tagen schriftlich einzumahnen. Danach gelten die Zahlungen als genehmigt.</p>
<p>7.9. Wenn vom VP keine UID-Nummer bekannt gegeben wird, erfolgen alle Auszahlungen netto.</p>
<h2>8. Vertragsstrafe, Schadenersatz</h2>
<p>8.1. Bei einem ersten Verstoß gegen die in diesem Vertrag geregelten Pflichten durch den VP erfolgt eine schriftliche Abmahnung durch PROFIT PLANET. Die Pflichtverletzung ist unmittelbar zu beenden bzw. gegebenenfalls zu beheben.</p>
<p>8.2. Kommt es erneut zu einem Verstoß gegen diesen Vertrag oder wird der zuerst gemahnte Zustand nicht beseitigt, so verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe für jeden jeweiligen Verstoß in Höhe von EUR 5.000,00.</p>
<p>8.3. Bei Verstößen gegen die Geheimhaltungs- und Datenschutzpflichten, sowie bei besonders schwerwiegenden Verstößen, insbesondere gegen Punkt 10.2 dieses Vertrags, ist PROFIT PLANET auch ohne vorhergehende Abmahnung zur Geltendmachung der jeweiligen Vertragsstrafe berechtigt.</p>
<p>8.4. Für jede Zuwiderhandlung gegen Punkt 3.8 verpflichtet sich der VP zur Zahlung einer verschuldens- und schadensunabhängigen Konventionalstrafe an den PROFIT PLANET von EUR 5.000,00 pro Verstoß (z.B. pro an ein anderes Unternehmen oder sonstigen Dritten vermittelten Vertrags oder pro abgeworbenen Kunden). Die Geltendmachung darüber hinausgehender sonstiger Schadenersatzansprüche, der Vertragsstrafe nach 8.2 oder etwa von Erfüllungsansprüchen bleibt dadurch unberührt.</p>
<p>8.5. Für jeden Verstoß gegen die in Punkt 4. dieses Vertrags (Geheimhaltungsverpflichtung) normierten Pflichten, verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe in Höhe von EUR 7.000,00 pro Verstoß. Die Geltendmachung weitergehender zivilrechtlicher Ansprüche insbesondere auf Unterlassung und Schadenersatz bleibt davon unberührt.</p>
<p>8.6. Bei Handlungen, die dem Katalog außerordentlicher Kündigungsgründe gemäß Punkt 10.2 entsprechen, insbesondere bei treuwidrigem Verhalten im Sinne der dort beschriebenen Fallgruppen (z. B. unautorisierte Kaltakquise, rufschädigendes Verhalten, unbefugtes Auftreten im Namen von PROFIT PLANET), verpflichtet sich der VP zur Zahlung einer verschuldensunabhängigen Vertragsstrafe in Höhe von EUR 10.000,00 pro Verstoß. Auch in diesen Fällen bleiben darüber hinausgehende Ansprüche insbesondere Schadenersatz oder außerordentliche Kündigung ausdrücklich vorbehalten.</p>
<h2>9. Haftungsausschluss</h2>
<p>9.1. Der VP führt seine Tätigkeiten nach bestem Wissen und Gewissen und in eigener Verantwortung, insbesondere auch in Bezug auf die korrekte Beratung der Endkunden aus. Eine Haftungsübernahme von PROFIT PLANET für Falschberatungen oder sonstiges Fehlverhalten des VP ist explizit ausgeschlossen.</p>
<p>9.2. Für Schäden haftet PROFIT PLANET nur, soweit diese auf Vorsatz oder grober Fahrlässigkeit oder auf grob schuldhafter Verletzung einer wesentlichen Vertragspflicht durch PROFIT PLANET, ihrer Mitarbeiter oder Erfüllungsgehilfen beruhen.</p>
<p>9.3. Eine Haftung von PROFIT PLANET für mittelbare Schäden, Folgeschäden, entgangenen Gewinn oder erwartete Ersparnis ist jedenfalls ausgeschlossen.</p>
<p>9.4. PROFIT PLANET übernimmt keine Haftung für Schäden, die durch Datenverlust auf den Servern auftreten, außer der Schaden beruht auf Vorsatz oder grober Fahrlässigkeit seitens PROFIT PLANET, ihrer Mitarbeiter oder Erfüllungsgehilfen.</p>
<p>9.5. Der Eintritt eines Schadens ist PROFIT PLANET unverzüglich mitzuteilen.</p>
<h2>10. Vertragsdauer & Kündigung</h2>
<p>10.1. Der Vertrag tritt mit Unterzeichnung oder im Fall einer Online-Registrierung, Online mit der Annahme des Vertrags durch PROFIT PLANET in Kraft und wird auf unbestimmte Zeit geschlossen. Er kann von beiden Parteien unter Einhaltung einer Frist von drei Monaten zum Ende jedes Kalendermonats schriftlich gekündigt werden.</p>
<p>10.2. Dessen ungeachtet kann der Vertrag seitens PROFIT PLANET aus wichtigem Grund ohne Einhaltung einer Kündigungsfrist gekündigt werden. Das Recht zur außerordentlichen Kündigung besteht ungeachtet weiterer Ansprüche. Folgende Gründe berechtigen insbesondere zur außerordentlichen Kündigung, die Aufzählung ist nicht abschließend:</p>
<ul>
<li>Akte treuwidrigen Verhaltens, die eine weitere Zusammenarbeit zw. den Vertragspartnern unzumutbar machen;</li>
<li>ein solches treuwidriges Verhalten liegt insbesondere etwa dann vor, wenn ein VP ohne ausdrückliche Zustimmung eines vertretungs- und zeichnungsbefugten Organs von PROFIT PLANET Handlungen setzt, welche nach außen den Anschein erwecken, im Namen oder Auftrag von PROFIT PLANET zu erfolgen insbesondere etwa durch Kaltakquise, Verwendung von Geschäftsdrucksorten oder -signaturen, Auftritt unter Verwendung der Marke PROFIT PLANET oder vergleichbare ruf- bzw. imageschädigende Aktivitäten.</li>
<li>die Anwendung unlauterer Praktiken oder ein grober oder wiederholter Verstoß gegen diesen Vertrag sowie der Verstoß gegen zwingende Rechtsnormen;</li>
<li>wenn über das Vermögen des jeweils anderen Vertragspartners die Einleitung eines Insolvenzverfahrens beantragt oder wenn die Eröffnung eines Insolvenzverfahrens mangels Masse abgelehnt wird;</li>
<li>Verletzung der vereinbarten oder gesetzlichen Datenschutz- oder Geheimhaltungspflichten;</li>
<li>wenn die Kooperation durch das Verhalten eines Vertragspartners oder dessen Ruf in der Öffentlichkeit den anderen Vertragspartnern einen Imageschaden zufügen würde;</li>
<li>wenn die Kooperation aufgrund der Gesetzeslage oder von dritter Seite als unzulässig untersagt wird;</li>
<li>Unzulässige Nebenabsprachen mit am Vertrieb beteiligten Dritten;</li>
</ul>
<p>10.3. Abgesehen von 10.2 kann PROFIT PLANET den VP auch außerordentlich kündigen, wenn dieser in den letzten 6 Monaten keine neuen Umsätze erzielt hat oder bei den durch seine Vermittlung zustande gekommenen Verträgen zwischen Endkunden und Produktgebern über einen Zeitraum von 2 Monaten überdurchschnittliche Stornoquoten von mehr als 30% der vermittelten Verträge bestehen. PROFIT PLANET wird den VP vor einer außerordentlichen Kündigung nach diesem Passus einmalig schriftlich verwarnen, so dass der VP die Möglichkeit hat, innerhalb einer Frist von 30 Tagen die erforderlichen neuen Umsätze zu generieren oder seine Stornoquote zu verbessern.</p>
<p>10.4. Mit der Beendigung des Vertrags steht dem VP mit Ausnahme der Provision für zu diesem Zeitpunkt bereits erfolgreich vermittelte Verträge, kein Recht auf Provision mehr zu. Ein Anspruch auf Handelsvertreterausgleich ist ausdrücklich ausgeschlossen, da der VP nicht als Handelsvertreter für den PROFIT PLANET tätig wird. Etwaige Ansprüche auf Folgeprovisionen für vermittelte Produkte bestehen für 12 Monate nach Vertragsbeendigung fort; im Falle einer außerordentlichen Kündigung verfallen Ansprüche auf Folgeprovisionen unmittelbar mit der Vertragsbeendigung.</p>
<p>10.5. Nach Beendigung des Vertrags sind vom VP sämtliche überlassenen Unterlagen und Werbematerialien unaufgefordert binnen einem Monat an PROFIT PLANET zurückzugeben. Die Verwendung der Marke PROFIT PLANET und entsprechender Logos etwa auf Briefpapier oder in E-Mail-Signaturen ist nach Beendigung des Vertrags untersagt.</p>
<h2>11. Übertragung</h2>
<p>11.1. PROFIT PLANET ist jederzeit berechtigt, den Geschäftsbetrieb ganz oder teilweise auf Dritte zu übertragen.</p>
<p>11.2. Der VP ist nur mit ausdrücklicher Zustimmung von PROFIT PLANET berechtigt, seine Vertriebsstruktur an einen Dritten zu übertragen.</p>
<p>11.3. Wenn eine als VP registrierte Kapital- oder Personengesellschaft einen neuen Gesellschafter aufnimmt, hat dies auf diesen Vertrag keine Auswirkung, sofern der/die Gesellschafter, die den VP-Antrag ursprünglich unterzeichnet haben, als Gesellschafter in der Gesellschaft verbleiben. Wenn ein Gesellschafter aus einer registrierten Gesellschaft ausscheidet oder seine Anteile an einen Dritten überträgt, so ist dies in Bezug auf diesen Vertrag zulässig, sofern er dies PROFIT PLANET schriftlich unter Vorlage der entsprechenden rechtsgültigen Urkunden anzeigt, und der Vorgang keinen anderen Bestimmungen dieses Vertrags widerspricht; anderenfalls behält PROFIT PLANET sich das Recht vor, den VP-Vertrag der betreffenden Kapital- oder Personengesellschaft aufzukündigen.</p>
<p>11.4. Bei Auflösung einer als VP registrierten Gemeinschaft (Kapital- oder Personengesellschaft, aber auch z.B. Ehepartnerschaften oder ähnliches, die einen gemeinsamen VP-Vertrag haben), bleibt nur ein VP-Vertrag bestehen. Die Mitglieder der aufzulösenden Gemeinschaft haben sich intern zu einigen, durch welches Mitglied/Gesellschafter die Vertriebspartner / Businesspartner / Affiliateschaft fortgesetzt werden soll, und dies PROFIT PLANET schriftlich anzuzeigen. Falls sich die Mitglieder der Gemeinschaft in Bezug auf die Fortsetzung des VP-vertrags nicht gütlich einigen können, behält sich PROFIT PLANET das Recht einer außerordentlichen Kündigung vor, insbesondere, wenn es durch die Uneinigkeit über die Folgen zur Vernachlässigung der Pflichten des VP, einem Verstoß gegen diesen Vertrag oder geltendes Recht oder zu einer übermäßigen Belastung der Vertriebsstruktur des VP kommt.</p>
</div>
<div class="page">
<div class="page-header"></div>
<h2>12. Schlussbestimmungen</h2>
<p>12.1. Änderungen und Ergänzungen dieser Vereinbarung bedürfen der Schriftform. Dies gilt auch für das Abgehen der Schriftformerfordernis. Mündliche Nebenabreden bestehen nicht.</p>
<p>12.2. Sollte eine Bestimmung dieser Vereinbarung unwirksam sein oder werden, gilt anstelle der unwirksamen Bestimmung jene Bestimmung als vereinbart, die dem wirtschaftlichen Zweck der unwirksamen Bestimmung am nächsten kommt.</p>
<p>12.3. Vereinbarter Gerichtsstand für alle Streitigkeiten aus oder in Zusammenhang mit dieser Vereinbarung ist das für Graz sachlich zuständige Gericht. Diese Vereinbarung unterliegt österreichischem Recht, nicht jedoch den nichtzwingenden Verweisungsnormen des IPR. Weiter- bzw. Rückverweisungen sind ausgeschlossen. Darüber hinaus steht es PROFIT PLANET frei, den VP auch seinem allgemeinen Gerichtsstand zu klagen.</p>
<div class="signatures">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block">
<span class="sig-image">{{signatureImage}}</span>
<div style="font-size:0.75em;line-height:1.2;">{{fullName}}</div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</title>
<style>
@page { size: A4; margin:15mm 12mm 18mm 12mm; } /* was 20mm 17mm 22mm 17mm */
body { counter-reset:page; font-size:13px; }
h1 { font-size:18px; margin:0 0 6px; }
h2 { font-size:13px; }
.page { page-break-after:always; }
.page:last-child { page-break-after:auto; }
.page-header {
display:flex;
justify-content:flex-end;
font-size:0.65em; /* slightly smaller */
counter-increment:page;
}
.page-header:after { content:"Seite " counter(page); }
.heading-block { text-align:center; margin:0 0 10px; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:4px;}
.print-date{font-size:0.7em;}
/* ADDED signature sizing fix */
.sig-block { min-height:120px !important; }
/* UPDATED: show Profit Planet signature full size */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
.sig-date { margin-top:4px; display:block; }
@media print {
.page { padding:12px 20px 18px !important; } /* was 25px 35px 35px */
}
</style>
</head>
<body style="margin:0;font-family:Arial,Helvetica,sans-serif;line-height:1.35;-webkit-print-color-adjust:exact;print-color-adjust:exact;">
<div class="document" style="margin:0;">
<!-- PAGE 1 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<!-- CHANGED header/date grouping -->
<div class="page-header-block">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<div class="print-date" style="font-size:0.7em;text-align:right;margin-top:4px;margin-bottom:8px;">Erstellt am: {{currentDate}}</div>
</div>
<div class="heading-block">
<h1 style="margin:0 0 8px;text-align:center;">SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</h1>
<p style="margin:0 0 6px;text-align:center;">i.S.d. Art. 28 Abs. 3 Datenschutz-Grundverordnung (DS-GVO)</p>
</div>
<p style="margin:0 0 6px;">abgeschlossen zwischen</p>
<p style="margin:0 0 6px;">Profit Planet GmbH (kurz Auftraggeber)<br>
FN 649474i<br>
Liebenauer Hauptstraße 82c<br>
A-8041 Graz</p>
<p style="margin:0 0 6px;">und</p>
<p style="margin:0 0 6px;">Vertriebspartner (kurz Auftragnehmer)</p>
<div class="meta-info" style="border:1px solid #000;padding:8px 12px;margin:12px 0 25px;font-size:0.9em;">
<table class="meta-grid" style="width:100%;border-collapse:collapse;">
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;width:28%;white-space:nowrap;">Vertriebspartner</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullName}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{address}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">PLZ / Ort</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{zip_code}} {{city}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Vollständige Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullAddress}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;font-weight:bold;white-space:nowrap;border-bottom:0;">E-Mail / Telefon</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:0;">{{email}} / {{phone}}</td>
</tr>
</table>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">1. PRÄAMBEL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">1.1. Diese Anlage konkretisiert die Verpflichtungen der Vertragsparteien zum Datenschutz, die sich aus der im bestehenden Vertriebspartner-Vertrag („Hauptvertrag“) und seinen Anlagen in ihren Einzelheiten beschriebenen Auftragsverarbeitung ergeben. Sie findet Anwendung auf alle Tätigkeiten, die mit dem Vertrag in Zusammenhang stehen, und bei denen Beschäftigte des Auftragnehmers oder durch den Auftragnehmer Beauftragte personenbezogene Daten („Daten“) des Auftraggebers verarbeiten.</p>
<p style="margin:0 0 6px;">1.2. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber als Auftragsverarbeiter für Dritte („Verantwortliche“ im Sinne des Art. 4 Nr. 7 DS-GVO) tätig ist. Im Rahmen des vorbezeichneten Hauptvertrags nimmt der Auftraggeber die Dienste des Auftragnehmers als „weiteren Auftragsverarbeiter“ im Sinne von Art. 28 Nr. 4 DS-GVO in Anspruch, um bestimmte Verarbeitungstätigkeiten im Namen des Dritten („Verantwortlicher“ iSd Art. 4 Nr. 7 DS-GVO) auszuführen.</p>
<p style="margin:0 0 6px;">1.3. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber gegenüber Dritten für die Einhaltung der Pflichten des Auftragnehmers haftet, falls der Auftragnehmer seinen Datenschutzpflichten nach diesem Vertrag und nach dem Gesetz nicht nachkommt.</p>
<p style="margin:0 0 6px;">1.4. Die Laufzeit dieser Anlage richtet sich nach der Laufzeit des Vertriebspartner-Vertrages, sofern sich aus den Bestimmungen dieser Anlage nicht darüber hinausgehende Verpflichtungen ergeben.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">2. DAUER, GEGENSTAND UND SPEZIFIZIERUNG DER AUFTRAGSVERARBEITUNG</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">2.1. Alle Daten dürfen nur so lange verarbeitet werden, als das durch die Vertragserfüllung oder den Zweck der Datenverarbeitung erforderlich ist.</p>
<p style="margin:0 0 6px;">2.2. Aus dem Vertrag ergeben sich Gegenstand und Dauer des Auftrags sowie Art und Zweck der Verarbeitung.</p>
<p style="margin:0 0 6px;">2.3. Im Einzelnen sind insbesondere die folgenden Daten Bestandteil der Datenverarbeitung:</p>
</div>
</div>
</div>
<!-- PAGE 2 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<table class="data-table" style="width:100%;border-collapse:collapse;border:1px solid #000;table-layout:fixed;font-size:0.85em;margin:0 0 12px;">
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;width:180px;">Art der Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Interessenten- und Kundendaten; Kontaktdaten beim Auftraggeber; Kontaktdaten des jeweiligen Datenverantwortlichen</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Art und Zweck der Datenverarbeitung</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Datenerfassung beim Interessenten (potenziellen Kunden); Datenübermittlung (auch elektronisch via E-Mail bzw. falls vorhanden über elektronische Schnittstellen der Verantwortlichen) an Auftraggeber bzw. Datenverantwortliche zur Legung eines Angebots bzw. zur Verwirklichung der Kundenbestellung; ggf. telefonischer Nachkontakt zur Qualitätskontrolle</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Kategorien betroffener Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Name, Vorname, Adresse, Geburtsdatum, SV-Nr., E-Mail, Kontodaten Ausweiskopie; Daten zur Energieversorgung (z.B. Zählpunkt, Zählernummer, Kilowattprognose, Jahresverbrauch); Aufzeichnung etwaiger Qualitätskontrollen; Aufzeichnung etwaiger Interessensgebiete im Bereich Versicherung, Kreditwirtschaft, Telekommunikation, Energieeffizienz (PV, Speicher, LED, Infrarotheizung, Kalkschutz…).</td>
</tr>
</table>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">3. ANWENDUNGSBEREICH UND VERANTWORTLICHKEIT</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">3.1. Der Auftragnehmer verarbeitet personenbezogene Daten im Auftrag des Auftraggebers. Dies umfasst Tätigkeiten, die im Vertrag und in der Leistungsbeschreibung konkretisiert sind.</p>
<p style="margin:0 0 6px;">3.2. Der Auftraggeber ist gegenüber dem/den Dritten als („Verantwortliche Person“ iSd Art. 4 Nr. 7 DS-GVO) für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe an den Auftragnehmer sowie für die Rechtmäßigkeit der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.3. Der Auftragnehmer ist gegenüber dem Auftraggeber im Rahmen dieses Vertrages für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe sowie der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.4. Die Weisungen werden anfänglich durch diese Vertragsanlage festgelegt und können vom Auftraggeber danach in schriftlicher Form oder in einem elektronischen Format (Textform) an die vom Auftragnehmer bezeichnete Stelle durch einzelne Weisungen geändert, ergänzt oder ersetzt werden (Einzelweisung). Weisungen, die in der Vertragsanlage nicht vorgesehen sind, werden als Antrag auf Leistungsänderung behandelt. Mündliche Weisungen sind unverzüglich schriftlich oder in Textform zu bestätigen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">4. PFLICHTEN DES AUFTRAGNEHMERS</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.1. Der Auftragnehmer darf Daten von betroffenen Personen nur im Rahmen des Auftrages und der Weisungen des Auftraggebers verarbeiten, außer es liegt ein Ausnahmefall iSd Art 28 Abs. 3 a) DS-GVO vor. Der Auftragnehmer informiert den Auftraggeber unverzüglich, wenn er der Auffassung ist, dass eine Weisung gegen anwendbare Gesetze verstößt. Der Auftragnehmer darf die Umsetzung der Weisung solange aussetzen, bis sie vom Auftraggeber bestätigt oder abgeändert wurde.</p>
<p style="margin:0 0 6px;">4.2. Der Auftragnehmer wird in seinem Verantwortungsbereich die innerbetriebliche Organisation so gestalten, dass sie den besonderen Anforderungen des Datenschutzes gerecht wird. Er wird technische und organisatorische Maßnahmen zum angemessenen Schutz der Daten des Auftraggebers treffen, die den Anforderungen der Datenschutz- Grundverordnung (Art. 32 DS-GVO) genügen. Der Auftragnehmer hat technische und organisatorische Maßnahmen zu treffen, die die Vertraulichkeit, Integrität, Verfügbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherstellen. Der Auftraggeber ist berechtigt, diese technischen und organisatorischen Maßnahmen dahingehend zu überprüfen, ob sie für die Risiken der zu verarbeitenden Daten ein angemessenes Schutzniveau bieten. Eine Änderung der getroffenen Sicherheitsmaßnahmen bleibt dem Auftragnehmer vorbehalten, wobei jedoch sichergestellt sein muss, dass das vertraglich vereinbarte Schutzniveau nicht unterschritten wird.</p>
<p style="margin:0 0 6px;">4.3. Der Auftragnehmer gewährleistet, seinen Pflichten nach Art. 32 Abs. 1 lit. d) DS-GVO nachzukommen, ein Verfahren zur regelmäßigen Überprüfung der Wirksamkeit der technischen und organisatorischen Maßnahmen zur Gewährleistung der Sicherheit der Verarbeitung einzusetzen.</p>
<p style="margin:0 0 6px;">4.4. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der Anfragen und Ansprüche betroffener Personen gem. Kapitel III der DS-GVO sowie bei der Einhaltung der in Art. 33 bis 36 DS-GVO genannten Pflichten.</p>
<p style="margin:0 0 6px;">4.5. Der Auftragnehmer gewährleistet, dass es den mit der Verarbeitung der Daten des Auftraggebers befassten Mitarbeiter und andere für den Auftragnehmer tätigen Personen untersagt ist, die Daten außerhalb der Weisung zu verarbeiten. Ferner gewährleistet der Auftragnehmer, dass sich die zur Verarbeitung der personenbezogenen Daten befugten Personen zur Vertraulichkeit verpflichtet haben oder einer angemessenen gesetzlichen Verschwiegenheitspflicht unterliegen. Die Vertraulichkeits-/ Verschwiegenheitspflicht besteht auch nach Beendigung des Auftrages fort.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<!-- FIX: reopen subsection and place paragraphs inside -->
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.6. Der Auftragnehmer unterrichtet den Auftraggeber unverzüglich, wenn ihm Verletzungen des Schutzes personenbezogener Daten des Auftraggebers bekannt werden. Der Auftragnehmer trifft die erforderlichen Maßnahmen zur Sicherung der Daten und zur Minderung möglicher nachteiliger Folgen der betroffenen Personen und spricht sich hierzu unverzüglich mit dem Auftraggeber ab.</p>
<p style="margin:0 0 6px;">4.7. Der Auftragnehmer nennt dem Auftraggeber den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
<p style="margin:0 0 6px;">4.8. Der Auftragnehmer berichtigt oder löscht die vertragsgegenständlichen Daten, wenn der Auftraggeber dies anweist und dies vom Weisungsrahmen umfasst ist. Ist eine datenschutzkonforme Löschung oder eine entsprechende Einschränkung der Datenverarbeitung nicht möglich, übernimmt der Auftragnehmer die datenschutzkonforme Vernichtung von Datenträgern und sonstigen Materialien auf Grund einer Einzelbeauftragung durch den Auftraggeber oder gibt diese Datenträger an den Auftraggeber zurück, sofern nicht im Vertrag bereits vereinbart.</p>
<p style="margin:0 0 6px;">4.9. Daten, Datenträger sowie sämtliche sonstige Materialien sind nach Auftragsende auf Verlangen des Auftraggebers entweder herauszugeben oder zu löschen.</p>
<p style="margin:0 0 6px;">4.10. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
<p style="margin:0 0 6px;">4.11. Im Falle einer Inanspruchnahme des Auftraggebers durch den Dritten, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">5. PFLICHTEN DES AUFTRAGGEBERS</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">5.1. Der Auftraggeber hat den Auftragnehmer unverzüglich und vollständig zu informieren, wenn er in den Auftragsergebnissen Fehler oder Unregelmäßigkeiten bzgl. datenschutzrechtlicher Bestimmungen feststellt.</p>
<p style="margin:0 0 6px;">5.2. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, gilt §3 Abs. 10 entsprechend.</p>
<p style="margin:0 0 6px;">5.3. Der Auftraggeber nennt dem Auftragnehmer den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">6. ANFRAGEN BETROFFENER PERSONEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">6.1. Wendet sich eine betroffene Person mit Forderungen zur Berichtigung, Löschung oder Auskunft an den Auftragnehmer, wird der Auftragnehmer die betroffene Person an den Auftraggeber verweisen und ggf. den Antrag der betroffenen Person unverzüglich an den Auftraggeber weiterleiten. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der jeweiligen Forderung.</p>
<p style="margin:0 0 6px;">6.2. Der Auftragnehmer haftet nicht, wenn das Ersuchen der betroffenen Person vom Auftraggeber nicht, nicht richtig oder nicht fristgerecht beantwortet wird.</p>
<p style="margin:0 0 6px;">6.3. Der Auftraggeber haftet nicht für Forderungen betroffener Personen, die dadurch entstehen, dass der Auftragnehmer das entsprechende Anliegen nicht zeitgerecht an den Auftraggeber übermittelt hat.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">7. NACHWEISMÖGLICHKEITEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">7.1. Der Auftragnehmer weist dem Auftraggeber die Einhaltung der in diesem Vertrag niedergelegten Pflichten mit geeigneten Mitteln nach.</p>
<p style="margin:0 0 6px;">7.2. Sollten im Einzelfall Inspektionen durch den Auftraggeber oder einen von diesem beauftragten Prüfer erforderlich sein, werden diese zu den üblichen Geschäftszeiten ohne Störung des Betriebsablaufs nach Anmeldung unter Berücksichtigung einer angemessenen Vorlaufzeit durchgeführt. Der Auftragnehmer darf diese von der Unterzeichnung einer Verschwiegenheitserklärung hinsichtlich der Daten anderer Kunden und der eingerichteten technischen und organisatorischen Maßnahmen abhängig machen. Sollte der durch den Auftraggeber beauftragte Prüfer in einem Wettbewerbsverhältnis zu dem Auftragnehmer stehen, hat der Auftragnehmer gegen diesen ein Einspruchsrecht</p>
</div>
</div>
<!-- MOVED UP: Sections 8 & 9 and signatures (formerly PAGE 4) -->
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">8. SUBUNTERNEHMER (WEITERE AUFTRAGSVERARBEITER)</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">8.1. Der Einsatz von Subunternehmern als weitere Auftragsverarbeiter ist nur zulässig, wenn der Auftraggeber vorher zugestimmt hat.</p>
<p style="margin:0 0 6px;">8.2. Ein zustimmungspflichtiges Subunternehmerverhältnis liegt vor, wenn der Auftragnehmer weitere Auftragnehmer mit der ganzen oder einer Teilleistung der im Vertrag vereinbarten Leistung beauftragt. Der Auftragnehmer wird mit diesen Dritten im erforderlichen Umfang Vereinbarungen treffen, um angemessene Datenschutz- und Informationssicherheitsmaßnahmen zu gewährleisten.</p>
<p style="margin:0 0 6px;">8.3. Erteilt der Auftragnehmer Aufträge an Subunternehmer, so obliegt es dem Auftragnehmer, seine datenschutzrechtlichen Pflichten aus diesem Vertrag dem Subunternehmer zu überbinden.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">9. INFORMATIONSPFLICHTEN, SCHRIFTFORMKLAUSEL, RECHTSWAHL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">9.1. Sollten die Daten des Auftraggebers beim Auftragnehmer durch Pfändung oder Beschlagnahme, durch ein Insolvenz- oder Vergleichsverfahren oder durch sonstige Ereignisse oder Maßnahmen Dritter gefährdet werden, so hat der Auftragnehmer den Auftraggeber unverzüglich darüber zu informieren. Der Auftragnehmer wird alle in diesem Zusammenhang Verantwortlichen unverzüglich darüber informieren, dass die Hoheit und das Eigentum an den Daten ausschließlich beim Dritten als verantwortliche Person im Sinne der Datenschutz-Grundverordnung liegen.</p>
<p style="margin:0 0 6px;">9.2. Änderungen und Ergänzungen dieser Anlage und aller ihrer Bestandteile einschließlich etwaiger Zusicherungen des Auftragnehmers bedürfen einer schriftlichen Vereinbarung, die auch in einem elektronischen Format (Textform) erfolgen kann, und des ausdrücklichen Hinweises darauf, dass es sich um eine Änderung bzw. Ergänzung dieser Bedingungen handelt. Dies gilt auch für den Verzicht auf dieses Formerfordernis.</p>
<p style="margin:0 0 6px;">9.3. Bei etwaigen Widersprüchen gehen Regelungen dieser Anlage zum Datenschutz den Regelungen des Vertrages vor. Sollten einzelne Teile dieser Anlage unwirksam sein, so berührt dies die Wirksamkeit der Anlage im Übrigen nicht.</p>
<p style="margin:0 0 6px;">9.4. Es gilt das auf dem Hauptvertrag anwendbare Recht sowie Gerichtsstand.</p>
</div>
</div>
<div class="signatures" style="display:flex;gap:30px;margin-top:38px;">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:4px;min-height:110px;">
<span style="display:block;max-width:100%;max-height:80px;">{{signatureImage}}</span>
<div style="font-size:0.75em;line-height:1.2;">{{fullName}}</div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</title>
<style>
@page { size: A4; margin:15mm 12mm 18mm 12mm; } /* was 20mm 17mm 22mm 17mm */
body { counter-reset:page; font-size:13px; }
h1 { font-size:18px; margin:0 0 6px; }
h2 { font-size:13px; }
.page { page-break-after:always; }
.page:last-child { page-break-after:auto; }
.page-header {
display:flex;
justify-content:flex-end;
font-size:0.65em; /* slightly smaller */
counter-increment:page;
}
.page-header:after { content:"Seite " counter(page); }
.heading-block { text-align:center; margin:0 0 10px; }
.page-header-block{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-bottom:4px;}
.print-date{font-size:0.7em;}
/* ADDED signature sizing fix */
.sig-block { min-height:120px !important; }
/* UPDATED: show Profit Planet signature full size */
.pp-stamp,
.pp-stamp img {
max-width:none !important;
max-height:none !important;
width:auto !important;
height:auto !important;
display:block;
}
.sig-date { margin-top:4px; display:block; }
@media print {
.page { padding:12px 20px 18px !important; } /* was 25px 35px 35px */
}
</style>
</head>
<body style="margin:0;font-family:Arial,Helvetica,sans-serif;line-height:1.35;-webkit-print-color-adjust:exact;print-color-adjust:exact;">
<div class="document" style="margin:0;">
<!-- PAGE 1 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<!-- CHANGED header/date grouping -->
<div class="page-header-block">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<div class="print-date" style="font-size:0.7em;text-align:right;margin-top:4px;margin-bottom:8px;">Erstellt am: {{currentDate}}</div>
</div>
<div class="heading-block">
<h1 style="margin:0 0 8px;text-align:center;">SUB-AUFTRAGSVERARBEITUNGS-VERTRAG</h1>
<p style="margin:0 0 6px;text-align:center;">i.S.d. Art. 28 Abs. 3 Datenschutz-Grundverordnung (DS-GVO)</p>
</div>
<p style="margin:0 0 6px;">abgeschlossen zwischen</p>
<p style="margin:0 0 6px;">Profit Planet GmbH (kurz Auftraggeber)<br>
FN 649474i<br>
Liebenauer Hauptstraße 82c<br>
A-8041 Graz</p>
<p style="margin:0 0 6px;">und</p>
<p style="margin:0 0 6px;">Vertriebspartner (kurz Auftragnehmer)</p>
<div class="meta-info" style="border:1px solid #000;padding:8px 12px;margin:12px 0 25px;font-size:0.9em;">
<table class="meta-grid" style="width:100%;border-collapse:collapse;">
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;width:28%;white-space:nowrap;">Vertriebspartner</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullName}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{address}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">PLZ / Ort</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{zip_code}} {{city}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;font-weight:bold;white-space:nowrap;">Vollständige Adresse</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:1px solid #ccc;">{{fullAddress}}</td>
</tr>
<tr>
<td style="padding:3px 6px;vertical-align:top;font-weight:bold;white-space:nowrap;border-bottom:0;">E-Mail / Telefon</td>
<td style="padding:3px 6px;vertical-align:top;border-bottom:0;">{{email}} / {{phone}}</td>
</tr>
</table>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">1. PRÄAMBEL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">1.1. Diese Anlage konkretisiert die Verpflichtungen der Vertragsparteien zum Datenschutz, die sich aus der im bestehenden Vertriebspartner-Vertrag („Hauptvertrag“) und seinen Anlagen in ihren Einzelheiten beschriebenen Auftragsverarbeitung ergeben. Sie findet Anwendung auf alle Tätigkeiten, die mit dem Vertrag in Zusammenhang stehen, und bei denen Beschäftigte des Auftragnehmers oder durch den Auftragnehmer Beauftragte personenbezogene Daten („Daten“) des Auftraggebers verarbeiten.</p>
<p style="margin:0 0 6px;">1.2. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber als Auftragsverarbeiter für Dritte („Verantwortliche“ im Sinne des Art. 4 Nr. 7 DS-GVO) tätig ist. Im Rahmen des vorbezeichneten Hauptvertrags nimmt der Auftraggeber die Dienste des Auftragnehmers als „weiteren Auftragsverarbeiter“ im Sinne von Art. 28 Nr. 4 DS-GVO in Anspruch, um bestimmte Verarbeitungstätigkeiten im Namen des Dritten („Verantwortlicher“ iSd Art. 4 Nr. 7 DS-GVO) auszuführen.</p>
<p style="margin:0 0 6px;">1.3. Der Auftragnehmer ist sich bewusst, dass der Auftraggeber gegenüber Dritten für die Einhaltung der Pflichten des Auftragnehmers haftet, falls der Auftragnehmer seinen Datenschutzpflichten nach diesem Vertrag und nach dem Gesetz nicht nachkommt.</p>
<p style="margin:0 0 6px;">1.4. Die Laufzeit dieser Anlage richtet sich nach der Laufzeit des Vertriebspartner-Vertrages, sofern sich aus den Bestimmungen dieser Anlage nicht darüber hinausgehende Verpflichtungen ergeben.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">2. DAUER, GEGENSTAND UND SPEZIFIZIERUNG DER AUFTRAGSVERARBEITUNG</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">2.1. Alle Daten dürfen nur so lange verarbeitet werden, als das durch die Vertragserfüllung oder den Zweck der Datenverarbeitung erforderlich ist.</p>
<p style="margin:0 0 6px;">2.2. Aus dem Vertrag ergeben sich Gegenstand und Dauer des Auftrags sowie Art und Zweck der Verarbeitung.</p>
<p style="margin:0 0 6px;">2.3. Im Einzelnen sind insbesondere die folgenden Daten Bestandteil der Datenverarbeitung:</p>
</div>
</div>
</div>
<!-- PAGE 2 -->
<div class="page" style="page-break-after:always;padding:12px 20px 18px;">
<div class="page-header" style="display:flex;justify-content:flex-end;font-size:0.75em;"></div>
<table class="data-table" style="width:100%;border-collapse:collapse;border:1px solid #000;table-layout:fixed;font-size:0.85em;margin:0 0 12px;">
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;width:180px;">Art der Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Interessenten- und Kundendaten; Kontaktdaten beim Auftraggeber; Kontaktdaten des jeweiligen Datenverantwortlichen</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Art und Zweck der Datenverarbeitung</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Datenerfassung beim Interessenten (potenziellen Kunden); Datenübermittlung (auch elektronisch via E-Mail bzw. falls vorhanden über elektronische Schnittstellen der Verantwortlichen) an Auftraggeber bzw. Datenverantwortliche zur Legung eines Angebots bzw. zur Verwirklichung der Kundenbestellung; ggf. telefonischer Nachkontakt zur Qualitätskontrolle</td>
</tr>
<tr>
<th style="border:1px solid #000;padding:6px 8px;vertical-align:top;background:#f5f5f5;">Kategorien betroffener Daten</th>
<td style="border:1px solid #000;padding:6px 8px;vertical-align:top;">Name, Vorname, Adresse, Geburtsdatum, SV-Nr., E-Mail, Kontodaten Ausweiskopie; Daten zur Energieversorgung (z.B. Zählpunkt, Zählernummer, Kilowattprognose, Jahresverbrauch); Aufzeichnung etwaiger Qualitätskontrollen; Aufzeichnung etwaiger Interessensgebiete im Bereich Versicherung, Kreditwirtschaft, Telekommunikation, Energieeffizienz (PV, Speicher, LED, Infrarotheizung, Kalkschutz…).</td>
</tr>
</table>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">3. ANWENDUNGSBEREICH UND VERANTWORTLICHKEIT</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">3.1. Der Auftragnehmer verarbeitet personenbezogene Daten im Auftrag des Auftraggebers. Dies umfasst Tätigkeiten, die im Vertrag und in der Leistungsbeschreibung konkretisiert sind.</p>
<p style="margin:0 0 6px;">3.2. Der Auftraggeber ist gegenüber dem/den Dritten als („Verantwortliche Person“ iSd Art. 4 Nr. 7 DS-GVO) für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe an den Auftragnehmer sowie für die Rechtmäßigkeit der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.3. Der Auftragnehmer ist gegenüber dem Auftraggeber im Rahmen dieses Vertrages für die Einhaltung der gesetzlichen Bestimmungen der Datenschutzgesetze, insbesondere für die Rechtmäßigkeit der Datenweitergabe sowie der Datenverarbeitung verantwortlich.</p>
<p style="margin:0 0 6px;">3.4. Die Weisungen werden anfänglich durch diese Vertragsanlage festgelegt und können vom Auftraggeber danach in schriftlicher Form oder in einem elektronischen Format (Textform) an die vom Auftragnehmer bezeichnete Stelle durch einzelne Weisungen geändert, ergänzt oder ersetzt werden (Einzelweisung). Weisungen, die in der Vertragsanlage nicht vorgesehen sind, werden als Antrag auf Leistungsänderung behandelt. Mündliche Weisungen sind unverzüglich schriftlich oder in Textform zu bestätigen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">4. PFLICHTEN DES AUFTRAGNEHMERS</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.1. Der Auftragnehmer darf Daten von betroffenen Personen nur im Rahmen des Auftrages und der Weisungen des Auftraggebers verarbeiten, außer es liegt ein Ausnahmefall iSd Art 28 Abs. 3 a) DS-GVO vor. Der Auftragnehmer informiert den Auftraggeber unverzüglich, wenn er der Auffassung ist, dass eine Weisung gegen anwendbare Gesetze verstößt. Der Auftragnehmer darf die Umsetzung der Weisung solange aussetzen, bis sie vom Auftraggeber bestätigt oder abgeändert wurde.</p>
<p style="margin:0 0 6px;">4.2. Der Auftragnehmer wird in seinem Verantwortungsbereich die innerbetriebliche Organisation so gestalten, dass sie den besonderen Anforderungen des Datenschutzes gerecht wird. Er wird technische und organisatorische Maßnahmen zum angemessenen Schutz der Daten des Auftraggebers treffen, die den Anforderungen der Datenschutz- Grundverordnung (Art. 32 DS-GVO) genügen. Der Auftragnehmer hat technische und organisatorische Maßnahmen zu treffen, die die Vertraulichkeit, Integrität, Verfügbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherstellen. Der Auftraggeber ist berechtigt, diese technischen und organisatorischen Maßnahmen dahingehend zu überprüfen, ob sie für die Risiken der zu verarbeitenden Daten ein angemessenes Schutzniveau bieten. Eine Änderung der getroffenen Sicherheitsmaßnahmen bleibt dem Auftragnehmer vorbehalten, wobei jedoch sichergestellt sein muss, dass das vertraglich vereinbarte Schutzniveau nicht unterschritten wird.</p>
<p style="margin:0 0 6px;">4.3. Der Auftragnehmer gewährleistet, seinen Pflichten nach Art. 32 Abs. 1 lit. d) DS-GVO nachzukommen, ein Verfahren zur regelmäßigen Überprüfung der Wirksamkeit der technischen und organisatorischen Maßnahmen zur Gewährleistung der Sicherheit der Verarbeitung einzusetzen.</p>
<p style="margin:0 0 6px;">4.4. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der Anfragen und Ansprüche betroffener Personen gem. Kapitel III der DS-GVO sowie bei der Einhaltung der in Art. 33 bis 36 DS-GVO genannten Pflichten.</p>
<p style="margin:0 0 6px;">4.5. Der Auftragnehmer gewährleistet, dass es den mit der Verarbeitung der Daten des Auftraggebers befassten Mitarbeiter und andere für den Auftragnehmer tätigen Personen untersagt ist, die Daten außerhalb der Weisung zu verarbeiten. Ferner gewährleistet der Auftragnehmer, dass sich die zur Verarbeitung der personenbezogenen Daten befugten Personen zur Vertraulichkeit verpflichtet haben oder einer angemessenen gesetzlichen Verschwiegenheitspflicht unterliegen. Die Vertraulichkeits-/ Verschwiegenheitspflicht besteht auch nach Beendigung des Auftrages fort.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<!-- FIX: reopen subsection and place paragraphs inside -->
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">4.6. Der Auftragnehmer unterrichtet den Auftraggeber unverzüglich, wenn ihm Verletzungen des Schutzes personenbezogener Daten des Auftraggebers bekannt werden. Der Auftragnehmer trifft die erforderlichen Maßnahmen zur Sicherung der Daten und zur Minderung möglicher nachteiliger Folgen der betroffenen Personen und spricht sich hierzu unverzüglich mit dem Auftraggeber ab.</p>
<p style="margin:0 0 6px;">4.7. Der Auftragnehmer nennt dem Auftraggeber den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
<p style="margin:0 0 6px;">4.8. Der Auftragnehmer berichtigt oder löscht die vertragsgegenständlichen Daten, wenn der Auftraggeber dies anweist und dies vom Weisungsrahmen umfasst ist. Ist eine datenschutzkonforme Löschung oder eine entsprechende Einschränkung der Datenverarbeitung nicht möglich, übernimmt der Auftragnehmer die datenschutzkonforme Vernichtung von Datenträgern und sonstigen Materialien auf Grund einer Einzelbeauftragung durch den Auftraggeber oder gibt diese Datenträger an den Auftraggeber zurück, sofern nicht im Vertrag bereits vereinbart.</p>
<p style="margin:0 0 6px;">4.9. Daten, Datenträger sowie sämtliche sonstige Materialien sind nach Auftragsende auf Verlangen des Auftraggebers entweder herauszugeben oder zu löschen.</p>
<p style="margin:0 0 6px;">4.10. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
<p style="margin:0 0 6px;">4.11. Im Falle einer Inanspruchnahme des Auftraggebers durch den Dritten, verpflichtet sich der Auftragnehmer den Auftraggeber bei der Abwehr des Anspruches im Rahmen seiner Möglichkeiten zu unterstützen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">5. PFLICHTEN DES AUFTRAGGEBERS</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">5.1. Der Auftraggeber hat den Auftragnehmer unverzüglich und vollständig zu informieren, wenn er in den Auftragsergebnissen Fehler oder Unregelmäßigkeiten bzgl. datenschutzrechtlicher Bestimmungen feststellt.</p>
<p style="margin:0 0 6px;">5.2. Im Falle einer Inanspruchnahme des Auftraggebers oder des Dritten durch eine betroffene Person hinsichtlich etwaiger Ansprüche nach Art. 82 DS-GVO, gilt §3 Abs. 10 entsprechend.</p>
<p style="margin:0 0 6px;">5.3. Der Auftraggeber nennt dem Auftragnehmer den Ansprechpartner für im Rahmen des Vertrages anfallende Datenschutzfragen.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">6. ANFRAGEN BETROFFENER PERSONEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">6.1. Wendet sich eine betroffene Person mit Forderungen zur Berichtigung, Löschung oder Auskunft an den Auftragnehmer, wird der Auftragnehmer die betroffene Person an den Auftraggeber verweisen und ggf. den Antrag der betroffenen Person unverzüglich an den Auftraggeber weiterleiten. Der Auftragnehmer unterstützt den Auftraggeber im Rahmen seiner Möglichkeiten bei der Erfüllung der jeweiligen Forderung.</p>
<p style="margin:0 0 6px;">6.2. Der Auftragnehmer haftet nicht, wenn das Ersuchen der betroffenen Person vom Auftraggeber nicht, nicht richtig oder nicht fristgerecht beantwortet wird.</p>
<p style="margin:0 0 6px;">6.3. Der Auftraggeber haftet nicht für Forderungen betroffener Personen, die dadurch entstehen, dass der Auftragnehmer das entsprechende Anliegen nicht zeitgerecht an den Auftraggeber übermittelt hat.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">7. NACHWEISMÖGLICHKEITEN</h2>
<div class="subsection" style="margin-left:20px;">
<p style="margin:0 0 6px;">7.1. Der Auftragnehmer weist dem Auftraggeber die Einhaltung der in diesem Vertrag niedergelegten Pflichten mit geeigneten Mitteln nach.</p>
<p style="margin:0 0 6px;">7.2. Sollten im Einzelfall Inspektionen durch den Auftraggeber oder einen von diesem beauftragten Prüfer erforderlich sein, werden diese zu den üblichen Geschäftszeiten ohne Störung des Betriebsablaufs nach Anmeldung unter Berücksichtigung einer angemessenen Vorlaufzeit durchgeführt. Der Auftragnehmer darf diese von der Unterzeichnung einer Verschwiegenheitserklärung hinsichtlich der Daten anderer Kunden und der eingerichteten technischen und organisatorischen Maßnahmen abhängig machen. Sollte der durch den Auftraggeber beauftragte Prüfer in einem Wettbewerbsverhältnis zu dem Auftragnehmer stehen, hat der Auftragnehmer gegen diesen ein Einspruchsrecht</p>
</div>
</div>
<!-- MOVED UP: Sections 8 & 9 and signatures (formerly PAGE 4) -->
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">8. SUBUNTERNEHMER (WEITERE AUFTRAGSVERARBEITER)</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">8.1. Der Einsatz von Subunternehmern als weitere Auftragsverarbeiter ist nur zulässig, wenn der Auftraggeber vorher zugestimmt hat.</p>
<p style="margin:0 0 6px;">8.2. Ein zustimmungspflichtiges Subunternehmerverhältnis liegt vor, wenn der Auftragnehmer weitere Auftragnehmer mit der ganzen oder einer Teilleistung der im Vertrag vereinbarten Leistung beauftragt. Der Auftragnehmer wird mit diesen Dritten im erforderlichen Umfang Vereinbarungen treffen, um angemessene Datenschutz- und Informationssicherheitsmaßnahmen zu gewährleisten.</p>
<p style="margin:0 0 6px;">8.3. Erteilt der Auftragnehmer Aufträge an Subunternehmer, so obliegt es dem Auftragnehmer, seine datenschutzrechtlichen Pflichten aus diesem Vertrag dem Subunternehmer zu überbinden.</p>
</div>
</div>
<div class="section" style="margin-bottom:10px;">
<h2 style="margin:14px 0 6px;">9. INFORMATIONSPFLICHTEN, SCHRIFTFORMKLAUSEL, RECHTSWAHL</h2>
<div class="subsection" style="margin-left:16px;">
<p style="margin:0 0 6px;">9.1. Sollten die Daten des Auftraggebers beim Auftragnehmer durch Pfändung oder Beschlagnahme, durch ein Insolvenz- oder Vergleichsverfahren oder durch sonstige Ereignisse oder Maßnahmen Dritter gefährdet werden, so hat der Auftragnehmer den Auftraggeber unverzüglich darüber zu informieren. Der Auftragnehmer wird alle in diesem Zusammenhang Verantwortlichen unverzüglich darüber informieren, dass die Hoheit und das Eigentum an den Daten ausschließlich beim Dritten als verantwortliche Person im Sinne der Datenschutz-Grundverordnung liegen.</p>
<p style="margin:0 0 6px;">9.2. Änderungen und Ergänzungen dieser Anlage und aller ihrer Bestandteile einschließlich etwaiger Zusicherungen des Auftragnehmers bedürfen einer schriftlichen Vereinbarung, die auch in einem elektronischen Format (Textform) erfolgen kann, und des ausdrücklichen Hinweises darauf, dass es sich um eine Änderung bzw. Ergänzung dieser Bedingungen handelt. Dies gilt auch für den Verzicht auf dieses Formerfordernis.</p>
<p style="margin:0 0 6px;">9.3. Bei etwaigen Widersprüchen gehen Regelungen dieser Anlage zum Datenschutz den Regelungen des Vertrages vor. Sollten einzelne Teile dieser Anlage unwirksam sein, so berührt dies die Wirksamkeit der Anlage im Übrigen nicht.</p>
<p style="margin:0 0 6px;">9.4. Es gilt das auf dem Hauptvertrag anwendbare Recht sowie Gerichtsstand.</p>
</div>
</div>
<div class="signatures" style="display:flex;gap:30px;margin-top:38px;">
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für PROFIT PLANET (Auftraggeber)</p>
<!-- CHANGED: wrap stamp + date in a flex column with spacing to avoid overlap -->
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:6px;min-height:140px;">
<div class="pp-stamp" style="display:block;max-width:220px;margin:0 auto 6px;">{{profitplanetSignature}}</div>
<div class="sig-date" style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Datum, Unterschrift</p>
</div>
<div class="signature" style="flex:1;text-align:center;">
<p style="margin:0 0 6px;">Für den VP (Auftragnehmer)</p>
<div class="sig-block" style="display:flex;flex-direction:column;align-items:center;gap:4px;min-height:110px;">
<span style="display:block;max-width:100%;max-height:80px;">{{signatureImage}}</span>
<div style="font-size:0.75em;line-height:1.2;">{{fullName}}</div>
<div style="font-size:0.75em;line-height:1.2;">{{currentDate}}</div>
</div>
<p style="margin:0 0 6px;">Name, Datum, Unterschrift</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,9 +1,13 @@
class Pool { class Pool {
constructor({ id = null, pool_name, description = null, price = 0.00, pool_type = 'other', is_active = true, created_by = null, updated_by = null, created_at = null, updated_at = null, members_count = 0 }) { constructor({ id = null, pool_name, description = null, price = 0.00, subscription_coffee_id = null, subscription_title = null, pool_type = 'other', is_active = true, created_by = null, updated_by = null, created_at = null, updated_at = null, members_count = 0 }) {
this.id = id; this.id = id;
this.pool_name = pool_name; this.pool_name = pool_name;
this.description = description; this.description = description;
this.price = price; this.price = price;
this.price_net = Number(price || 0);
this.price_per_capsule_net = Number(price || 0);
this.subscription_coffee_id = subscription_coffee_id == null ? null : Number(subscription_coffee_id);
this.subscription_title = subscription_title || null;
this.pool_type = pool_type; this.pool_type = pool_type;
this.is_active = is_active; this.is_active = is_active;
this.created_by = created_by; this.created_by = created_by;

2609
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,30 +17,36 @@
"dev": "nodemon server.js", "dev": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"overrides": {
"@aws-sdk/xml-builder": {
"fast-xml-parser": "^5.3.4",
"ajv": "8.18.0"
}
},
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.894.0", "@aws-sdk/client-s3": "^3.992.0",
"@aws-sdk/s3-request-presigner": "^3.894.0", "@aws-sdk/s3-request-presigner": "^3.992.0",
"@getbrevo/brevo": "^3.0.1", "@getbrevo/brevo": "^3.0.1",
"argon2": "^0.44.0", "argon2": "^0.44.0",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cors": "^2.8.5", "cors": "^2.8.6",
"csv-parse": "^6.1.0", "csv-parse": "^6.1.0",
"dotenv": "^17.2.2", "dotenv": "^17.3.1",
"express": "^5.1.0", "express": "^5.2.1",
"get-stream": "^9.0.1", "get-stream": "^9.0.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.3",
"multer": "^2.0.2", "multer": "^2.0.2",
"mysql2": "^3.15.0", "mysql2": "^3.17.2",
"nodemailer": "^7.0.6", "nodemailer": "^8.0.1",
"pdfkit": "^0.17.2", "pdfkit": "^0.17.2",
"pidusage": "^4.0.1", "pidusage": "^4.0.1",
"puppeteer": "^24.22.0", "puppeteer": "^24.37.3",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"winston": "^3.17.0", "winston": "^3.19.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.1.10" "nodemon": "^3.1.11"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"

View File

@ -129,14 +129,39 @@ class AbonemmentRepository {
} }
async listByUser(userId, { status, limit = 50, offset = 0 } = {}) { async listByUser(userId, { status, limit = 50, offset = 0 } = {}) {
const params = [userId]; const safeLimit = Number(limit);
let sql = `SELECT * FROM coffee_abonements WHERE user_id = ?`; const safeOffset = Number(offset);
const hasUserId = await this.hasColumn('user_id');
const hasPurchaserUserId = await this.hasColumn('purchaser_user_id');
const hasReferredBy = await this.hasColumn('referred_by');
let sql = `SELECT * FROM coffee_abonements WHERE `;
const params = [];
if (hasUserId && hasPurchaserUserId) {
sql += `(user_id = ? OR purchaser_user_id = ?)`;
params.push(userId, userId);
} else if (hasUserId) {
sql += `user_id = ?`;
params.push(userId);
} else if (hasPurchaserUserId) {
sql += `purchaser_user_id = ?`;
params.push(userId);
} else if (hasReferredBy) {
// Legacy fallback for older schema where ownership was not persisted in user_id.
sql += `referred_by = ?`;
params.push(userId);
} else {
// Legacy schema fallback: no owner columns available
return [];
}
if (status) { if (status) {
sql += ` AND status = ?`; sql += ` AND status = ?`;
params.push(status); params.push(status);
} }
sql += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`; sql += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`;
params.push(Number(limit), Number(offset)); params.push(safeLimit, safeOffset);
const [rows] = await pool.query(sql, params); const [rows] = await pool.query(sql, params);
return rows.map((r) => new Abonemment(r)); return rows.map((r) => new Abonemment(r));
} }
@ -231,6 +256,44 @@ class AbonemmentRepository {
return this.getAbonementById(id); return this.getAbonementById(id);
} }
async transitionContent(id, contentPayload = {}) {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.query(
`UPDATE coffee_abonements
SET pack_breakdown = ?, price = ?, currency = ?, updated_at = ?
WHERE id = ?`,
[
JSON.stringify(contentPayload.pack_breakdown || []),
contentPayload.price ?? null,
contentPayload.currency ?? 'EUR',
contentPayload.updated_at || new Date(),
id,
],
);
await conn.query(
`INSERT INTO coffee_abonement_history
(abonement_id, event_type, event_at, actor_user_id, details, created_at)
VALUES (?, ?, ?, ?, ?, NOW())`,
[
id,
contentPayload.event_type || 'content_updated',
contentPayload.event_at || new Date(),
contentPayload.actor_user_id || null,
JSON.stringify(contentPayload.details || {}),
],
);
await conn.commit();
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
return this.getAbonementById(id);
}
async listDueForBilling(now) { async listDueForBilling(now) {
const [rows] = await pool.query( const [rows] = await pool.query(
`SELECT * FROM coffee_abonements `SELECT * FROM coffee_abonements

View File

@ -146,6 +146,22 @@ class InvoiceRepository {
return rows[0] ? new Invoice(rows[0]) : null; return rows[0] ? new Invoice(rows[0]) : null;
} }
async getItemsByInvoiceId(invoiceId) {
const [rows] = await pool.query(
`SELECT * FROM invoice_items WHERE invoice_id = ? ORDER BY id ASC`,
[invoiceId],
);
return rows || [];
}
async updateStorageKey(invoiceId, storageKey) {
await pool.query(
`UPDATE invoices SET pdf_storage_key = ?, updated_at = NOW() WHERE id = ?`,
[storageKey, invoiceId],
);
return this.getById(invoiceId);
}
async listByUser(userId, { status, limit = 50, offset = 0 } = {}) { async listByUser(userId, { status, limit = 50, offset = 0 } = {}) {
const params = [userId]; const params = [userId];
let sql = `SELECT * FROM invoices WHERE user_id = ?`; let sql = `SELECT * FROM invoices WHERE user_id = ?`;

View File

@ -5,16 +5,16 @@ class PoolRepository {
this.uow = uow; this.uow = uow;
} }
async create({ pool_name, description = null, price = 0.00, pool_type = 'other', is_active = true, created_by = null }) { async create({ pool_name, description = null, price = 0.00, subscription_coffee_id = null, pool_type = 'other', is_active = true, created_by = null }) {
const conn = this.uow.connection; const conn = this.uow.connection;
try { try {
console.info('PoolRepository.create:start', { pool_name, pool_type, is_active, price, created_by }); console.info('PoolRepository.create:start', { pool_name, pool_type, is_active, price, subscription_coffee_id, created_by });
const sql = `INSERT INTO pools (pool_name, description, price, pool_type, is_active, created_by) const sql = `INSERT INTO pools (pool_name, description, price, subscription_coffee_id, pool_type, is_active, created_by)
VALUES (?, ?, ?, ?, ?, ?)`; VALUES (?, ?, ?, ?, ?, ?, ?)`;
const params = [pool_name, description, price, pool_type, is_active, created_by]; const params = [pool_name, description, price, subscription_coffee_id, pool_type, is_active, created_by];
const [res] = await conn.execute(sql, params); const [res] = await conn.execute(sql, params);
console.info('PoolRepository.create:success', { insertId: res?.insertId }); console.info('PoolRepository.create:success', { insertId: res?.insertId });
return new Pool({ id: res.insertId, pool_name, description, price, pool_type, is_active, created_by }); return new Pool({ id: res.insertId, pool_name, description, price, subscription_coffee_id, pool_type, is_active, created_by });
} catch (err) { } catch (err) {
console.error('PoolRepository.create:error', { code: err?.code, errno: err?.errno, sqlMessage: err?.sqlMessage, message: err?.message }); console.error('PoolRepository.create:error', { code: err?.code, errno: err?.errno, sqlMessage: err?.sqlMessage, message: err?.message });
const e = new Error('Failed to create pool'); const e = new Error('Failed to create pool');
@ -29,10 +29,11 @@ class PoolRepository {
try { try {
console.info('PoolRepository.findAll:start'); console.info('PoolRepository.findAll:start');
const sql = `SELECT const sql = `SELECT
p.id, p.pool_name, p.description, p.price, p.pool_type, p.is_active, p.id, p.pool_name, p.description, p.price, p.subscription_coffee_id, c.title AS subscription_title, p.pool_type, p.is_active,
p.created_by, p.updated_by, p.created_at, p.updated_at, p.created_by, p.updated_by, p.created_at, p.updated_at,
COUNT(pm.user_id) AS members_count COUNT(pm.user_id) AS members_count
FROM pools p FROM pools p
LEFT JOIN coffee_table c ON c.id = p.subscription_coffee_id
LEFT JOIN pool_members pm ON pm.pool_id = p.id LEFT JOIN pool_members pm ON pm.pool_id = p.id
GROUP BY p.id GROUP BY p.id
ORDER BY p.created_at DESC`; ORDER BY p.created_at DESC`;
@ -66,7 +67,10 @@ class PoolRepository {
[is_active, updated_by, id] [is_active, updated_by, id]
); );
const [updated] = await conn.execute( const [updated] = await conn.execute(
`SELECT id, pool_name, description, price, pool_type, is_active, created_by, updated_by, created_at, updated_at FROM pools WHERE id = ?`, `SELECT p.id, p.pool_name, p.description, p.price, p.subscription_coffee_id, c.title AS subscription_title, p.pool_type, p.is_active, p.created_by, p.updated_by, p.created_at, p.updated_at
FROM pools p
LEFT JOIN coffee_table c ON c.id = p.subscription_coffee_id
WHERE p.id = ?`,
[id] [id]
); );
console.info('PoolRepository.updateActive:success', { id, is_active }); console.info('PoolRepository.updateActive:success', { id, is_active });
@ -82,6 +86,43 @@ class PoolRepository {
throw err; throw err;
} }
} }
async updateSubscriptionLink(id, subscription_coffee_id = null, updated_by = null) {
const conn = this.uow.connection;
try {
console.info('PoolRepository.updateSubscriptionLink:start', { id, subscription_coffee_id, updated_by });
const [rows] = await conn.execute(`SELECT id FROM pools WHERE id = ?`, [id]);
if (!rows || rows.length === 0) {
const err = new Error('Pool not found');
err.status = 404;
throw err;
}
await conn.execute(
`UPDATE pools SET subscription_coffee_id = ?, updated_by = ?, updated_at = NOW() WHERE id = ?`,
[subscription_coffee_id, updated_by, id]
);
const [updated] = await conn.execute(
`SELECT p.id, p.pool_name, p.description, p.price, p.subscription_coffee_id, c.title AS subscription_title, p.pool_type, p.is_active, p.created_by, p.updated_by, p.created_at, p.updated_at
FROM pools p
LEFT JOIN coffee_table c ON c.id = p.subscription_coffee_id
WHERE p.id = ?`,
[id]
);
return new Pool(updated[0]);
} catch (err) {
console.error('PoolRepository.updateSubscriptionLink:error', { id, subscription_coffee_id, code: err?.code, message: err?.message });
if (!err.status) {
const e = new Error('Failed to update pool subscription link');
e.status = 500;
e.cause = err;
throw e;
}
throw err;
}
}
} }
module.exports = PoolRepository; module.exports = PoolRepository;

View File

@ -116,6 +116,7 @@ router.get('/company-stamps/all', authMiddleware, adminOnly, forceCompanyForAdmi
// Admin: coffee products // Admin: coffee products
router.get('/admin/coffee', authMiddleware, adminOnly, CoffeeController.list); router.get('/admin/coffee', authMiddleware, adminOnly, CoffeeController.list);
router.get('/admin/coffee/active', authMiddleware, adminOnly, CoffeeController.listActive); router.get('/admin/coffee/active', authMiddleware, adminOnly, CoffeeController.listActive);
router.get('/coffee/active', authMiddleware, CoffeeController.listActive);
// Matrix GETs // Matrix GETs
@ -134,6 +135,8 @@ router.post('/admin/matrix/add-user', authMiddleware, adminOnly, MatrixControlle
router.get('/admin/pools', authMiddleware, adminOnly, PoolController.list); router.get('/admin/pools', authMiddleware, adminOnly, PoolController.list);
// NEW: Admin list pool members // NEW: Admin list pool members
router.get('/admin/pools/:id/members', authMiddleware, adminOnly, PoolController.listMembers); router.get('/admin/pools/:id/members', authMiddleware, adminOnly, PoolController.listMembers);
// NEW: Admin diagnose pool inflow for invoice
router.get('/admin/pools/inflow-diagnostics', authMiddleware, adminOnly, PoolController.inflowDiagnostics);
// NEW: User matrices list and per-instance overview // NEW: User matrices list and per-instance overview
router.get('/matrix/me/list', authMiddleware, MatrixController.listMyMatrices); router.get('/matrix/me/list', authMiddleware, MatrixController.listMyMatrices);
@ -156,6 +159,8 @@ router.get('/affiliates/active', AffiliateController.listActive);
// Abonement GETs // Abonement GETs
router.get('/abonements/mine', authMiddleware, AbonemmentController.getMine); router.get('/abonements/mine', authMiddleware, AbonemmentController.getMine);
router.get('/abonements/mine/status', authMiddleware, AbonemmentController.getMineStatus);
router.get('/abonements/:id/invoices', authMiddleware, AbonemmentController.getInvoices);
router.get('/abonements/:id/history', authMiddleware, AbonemmentController.getHistory); router.get('/abonements/:id/history', authMiddleware, AbonemmentController.getHistory);
router.get('/admin/abonements', authMiddleware, adminOnly, AbonemmentController.adminList); router.get('/admin/abonements', authMiddleware, adminOnly, AbonemmentController.adminList);

View File

@ -12,6 +12,7 @@ const PoolController = require('../controller/pool/PoolController'); // <-- new
const MatrixController = require('../controller/matrix/MatrixController'); // <-- new const MatrixController = require('../controller/matrix/MatrixController'); // <-- new
const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new
const NewsController = require('../controller/news/NewsController'); const NewsController = require('../controller/news/NewsController');
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
const multer = require('multer'); const multer = require('multer');
const upload = multer({ storage: multer.memoryStorage() }); const upload = multer({ storage: multer.memoryStorage() });
@ -40,6 +41,8 @@ router.patch('/admin/update-user-status/:id', authMiddleware, adminOnly, AdminUs
router.patch('/admin/coffee/:id/state', authMiddleware, adminOnly, CoffeeController.setState); router.patch('/admin/coffee/:id/state', authMiddleware, adminOnly, CoffeeController.setState);
// NEW: Admin pool active status update // NEW: Admin pool active status update
router.patch('/admin/pools/:id/active', authMiddleware, adminOnly, PoolController.updateActive); router.patch('/admin/pools/:id/active', authMiddleware, adminOnly, PoolController.updateActive);
// NEW: Admin update pool linked subscription
router.patch('/admin/pools/:id/subscription', authMiddleware, adminOnly, PoolController.updateSubscription);
// NEW: deactivate a matrix instance (admin-only) // NEW: deactivate a matrix instance (admin-only)
router.patch('/admin/matrix/:id/deactivate', authMiddleware, adminOnly, MatrixController.deactivate); router.patch('/admin/matrix/:id/deactivate', authMiddleware, adminOnly, MatrixController.deactivate);
// NEW: activate a matrix instance (admin-only) // NEW: activate a matrix instance (admin-only)
@ -56,6 +59,7 @@ router.patch('/admin/news/:id/status', authMiddleware, adminOnly, NewsController
// Personal profile (self-service) - no admin guard // Personal profile (self-service) - no admin guard
router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic); router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic);
router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank); router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank);
router.patch('/abonements/:id/content', authMiddleware, AbonemmentController.updateContent);
// Add other PATCH routes here as needed // Add other PATCH routes here as needed

View File

@ -4,8 +4,8 @@ const argon2 = require('argon2');
async function createAdminUser() { async function createAdminUser() {
// const adminEmail = process.env.ADMIN_EMAIL || 'office@profit-planet.com'; // const adminEmail = process.env.ADMIN_EMAIL || 'office@profit-planet.com';
// const adminEmail = process.env.ADMIN_EMAIL || 'alexander.ibrahim.ai@gmail.com'; const adminEmail = process.env.ADMIN_EMAIL || 'alexander.ibrahim.ai@gmail.com';
const adminEmail = process.env.ADMIN_EMAIL || 'loki.aahi@gmail.com'; // const adminEmail = process.env.ADMIN_EMAIL || 'loki.aahi@gmail.com';
const adminPassword = process.env.ADMIN_PASSWORD || 'Chalanger75$%'; const adminPassword = process.env.ADMIN_PASSWORD || 'Chalanger75$%';
// const adminPassword = process.env.ADMIN_PASSWORD || 'W.profit-planet.com.2025'; // const adminPassword = process.env.ADMIN_PASSWORD || 'W.profit-planet.com.2025';
const firstName = process.env.ADMIN_FIRST_NAME || 'Admin'; const firstName = process.env.ADMIN_FIRST_NAME || 'Admin';

View File

@ -1,6 +1,10 @@
const pool = require('../../database/database'); const pool = require('../../database/database');
const AbonemmentRepository = require('../../repositories/abonemments/AbonemmentRepository'); const AbonemmentRepository = require('../../repositories/abonemments/AbonemmentRepository');
const InvoiceService = require('../invoice/InvoiceService'); // NEW const InvoiceService = require('../invoice/InvoiceService'); // NEW
const UnitOfWork = require('../../database/UnitOfWork');
const ReferralService = require('../referral/ReferralService');
const ReferralTokenRepository = require('../../repositories/referral/ReferralTokenRepository');
const MailService = require('../email/MailService');
class AbonemmentService { class AbonemmentService {
constructor() { constructor() {
@ -23,7 +27,7 @@ class AbonemmentService {
async getCoffeeProduct(id) { async getCoffeeProduct(id) {
const [rows] = await pool.query( const [rows] = await pool.query(
`SELECT id, price, currency, billing_interval, interval_count, state AS is_active, pack_group FROM coffee_table WHERE id = ?`, `SELECT id, title, price, currency, billing_interval, interval_count, state AS is_active, pack_group FROM coffee_table WHERE id = ?`,
[id], [id],
); );
return rows[0] || null; return rows[0] || null;
@ -40,6 +44,10 @@ class AbonemmentService {
billingInterval, billingInterval,
intervalCount, intervalCount,
isAutoRenew, isAutoRenew,
isForSelf,
recipientName,
recipientEmail,
recipientNotes,
firstName, firstName,
lastName, lastName,
email, email,
@ -67,6 +75,12 @@ class AbonemmentService {
}); });
const normalizedEmail = this.normalizeEmail(email); const normalizedEmail = this.normalizeEmail(email);
const normalizedRecipientEmail = this.normalizeEmail(recipientEmail);
const forSelf = isForSelf !== false && !normalizedRecipientEmail;
if (!forSelf && !normalizedRecipientEmail) {
throw new Error('recipient_email is required when subscription is for another person');
}
if (!Array.isArray(items) || items.length === 0) throw new Error('items must be a non-empty array'); if (!Array.isArray(items) || items.length === 0) throw new Error('items must be a non-empty array');
@ -88,6 +102,7 @@ class AbonemmentService {
breakdown.push({ breakdown.push({
coffee_table_id: coffeeId, coffee_table_id: coffeeId,
coffee_title: product.title || null,
packs, packs,
price_per_pack: Number(product.price), price_per_pack: Number(product.price),
currency: product.currency, currency: product.currency,
@ -98,6 +113,9 @@ class AbonemmentService {
const startDateObj = startDate ? new Date(startDate) : now; const startDateObj = startDate ? new Date(startDate) : now;
const nextBilling = this.addInterval(startDateObj, billingInterval || 'month', intervalCount || 1); const nextBilling = this.addInterval(startDateObj, billingInterval || 'month', intervalCount || 1);
const effectiveRecipientName = recipientName || `${firstName || ''} ${lastName || ''}`.trim() || null;
const effectiveEmail = forSelf ? normalizedEmail : normalizedRecipientEmail;
const snapshot = { const snapshot = {
status: 'active', status: 'active',
started_at: startDateObj, started_at: startDateObj,
@ -108,18 +126,24 @@ class AbonemmentService {
currency: breakdown[0]?.currency || 'EUR', currency: breakdown[0]?.currency || 'EUR',
is_auto_renew: isAutoRenew !== false, is_auto_renew: isAutoRenew !== false,
actor_user_id: actorUser?.id, actor_user_id: actorUser?.id,
details: { origin: 'subscribe_order', total_packs: totalPacks }, details: {
origin: 'subscribe_order',
total_packs: totalPacks,
is_for_self: forSelf,
recipient_name: forSelf ? null : effectiveRecipientName,
recipient_notes: forSelf ? null : (recipientNotes || null)
},
pack_breakdown: breakdown, pack_breakdown: breakdown,
first_name: firstName, first_name: forSelf ? firstName : (effectiveRecipientName || firstName),
last_name: lastName, last_name: forSelf ? lastName : null,
email: normalizedEmail, email: effectiveEmail,
street, street,
postal_code: postalCode, postal_code: postalCode,
city, city,
country, country,
frequency, frequency,
referred_by: referredBy || null, // Pass referred_by to snapshot referred_by: referredBy || (forSelf ? (actorUser?.id ?? null) : null),
user_id: actorUser?.id ?? null, // NEW: set owner (purchaser acts as owner here) user_id: forSelf ? (actorUser?.id ?? null) : null,
purchaser_user_id: actorUser?.id ?? null, // NEW: also store purchaser purchaser_user_id: actorUser?.id ?? null, // NEW: also store purchaser
}; };
@ -145,7 +169,11 @@ class AbonemmentService {
abonement, abonement,
snapshot.started_at, snapshot.started_at,
snapshot.next_billing_at, snapshot.next_billing_at,
{ actorUserId: actorUser?.id || null } {
actorUserId: actorUser?.id || null,
userType: actorUser?.user_type || actorUser?.userType || 'personal',
lang: actorUser?.lang || actorUser?.language || 'en'
}
); );
console.log('[SUBSCRIBE ORDER] Issued invoice:', { console.log('[SUBSCRIBE ORDER] Issued invoice:', {
id: invoice?.id, id: invoice?.id,
@ -165,9 +193,82 @@ class AbonemmentService {
// intentionally not throwing to avoid blocking subscription; adjust if you want transactional consistency // intentionally not throwing to avoid blocking subscription; adjust if you want transactional consistency
} }
if (!forSelf && effectiveEmail) {
const existingUser = await this.findUserByEmail(effectiveEmail);
if (!existingUser) {
await this.repo.upsertNoUserAboMail(effectiveEmail, abonement.id, actorUser?.id || null);
const referralLink = await this.getOrCreateReferralLink(actorUser?.id || null);
if (referralLink) {
try {
await MailService.sendSubscriptionInvitationEmail({
email: effectiveEmail,
inviterName: actorUser?.email || 'ProfitPlanet user',
referralLink,
lang: actorUser?.lang || actorUser?.language || 'en'
});
} catch (mailError) {
console.error('[SUBSCRIBE ORDER] Invitation email failed:', mailError);
}
}
}
}
return abonement; return abonement;
} }
async findUserByEmail(email) {
const normalized = this.normalizeEmail(email);
if (!normalized) return null;
const [rows] = await pool.query(
`SELECT id, email FROM users WHERE email = ? LIMIT 1`,
[normalized]
);
return rows && rows[0] ? rows[0] : null;
}
async getOrCreateReferralLink(userId) {
if (!userId) return null;
const unitOfWork = new UnitOfWork();
await unitOfWork.start();
try {
const repo = new ReferralTokenRepository(unitOfWork);
const tokens = await repo.getTokensByUser(userId);
const now = Date.now();
const active = (tokens || []).find((t) => {
const statusOk = t.status === 'active';
const remaining = Number(t.uses_remaining);
const unlimited = Number(t.max_uses) === -1 || remaining === -1;
const remainingOk = unlimited || remaining > 0;
const exp = t.expires_at ? new Date(t.expires_at).getTime() : NaN;
const expiryOk = Number.isNaN(exp) ? true : exp > now;
return statusOk && remainingOk && expiryOk;
});
let tokenValue;
if (active?.token) {
tokenValue = active.token;
} else {
const created = await ReferralService.createReferralToken({
userId,
expiresInDays: 7,
maxUses: 1,
unitOfWork,
});
tokenValue = created?.token;
}
await unitOfWork.commit();
if (!tokenValue) return null;
const base = (process.env.FRONTEND_URL || 'https://profit-planet.partners').replace(/\/$/, '');
return `${base}/register?ref=${tokenValue}`;
} catch (error) {
await unitOfWork.rollback(error);
console.error('[ABONEMENT] getOrCreateReferralLink failed:', error);
return null;
}
}
async subscribe({ async subscribe({
userId, userId,
coffeeId, coffeeId,
@ -235,6 +336,13 @@ class AbonemmentService {
recipient_email: normalizedRecipientEmail || null, // CHANGED recipient_email: normalizedRecipientEmail || null, // CHANGED
details, details,
recipient: recipientMeta || undefined, recipient: recipientMeta || undefined,
pack_breakdown: [{
coffee_table_id: product.id,
coffee_title: product.title || null,
packs: 1,
price_per_pack: Number(product.price),
currency: product.currency,
}],
purchaser_user_id, // NEW purchaser_user_id, // NEW
user_id: ownerUserId ?? null, // NEW: persist owner user_id: ownerUserId ?? null, // NEW: persist owner
referred_by: referredBy || null, // Pass referred_by to snapshot referred_by: referredBy || null, // Pass referred_by to snapshot
@ -267,7 +375,11 @@ class AbonemmentService {
abonement, abonement,
snapshot.started_at, snapshot.started_at,
snapshot.next_billing_at, snapshot.next_billing_at,
{ actorUserId: actorUser?.id || null } {
actorUserId: actorUser?.id || null,
userType: actorUser?.user_type || actorUser?.userType || 'personal',
lang: actorUser?.lang || actorUser?.language || 'en'
}
); );
console.log('[SUBSCRIBE] Issued invoice:', { console.log('[SUBSCRIBE] Issued invoice:', {
id: invoice?.id, id: invoice?.id,
@ -337,6 +449,67 @@ class AbonemmentService {
}); });
} }
async updateContent({ abonementId, actorUser, items }) {
const abon = await this.repo.getAbonementById(abonementId);
if (!abon) throw new Error('Not found');
if (!this.canManageAbonement(abon, actorUser)) throw new Error('Forbidden');
if (!['active', 'paused'].includes(abon.status)) {
throw new Error('Only active or paused abonements can be updated');
}
if (!Array.isArray(items) || items.length === 0) {
throw new Error('items must be a non-empty array');
}
let totalPacks = 0;
let totalPrice = 0;
const breakdown = [];
for (const item of items) {
const coffeeId = item?.coffeeId;
const packs = Number(item?.quantity ?? 0);
if (!coffeeId) throw new Error('coffeeId is required for each item');
if (!Number.isFinite(packs) || packs <= 0) {
throw new Error('quantity must be a positive integer per item');
}
const product = await this.getCoffeeProduct(coffeeId);
if (!product || !product.is_active) throw new Error(`Product ${coffeeId} not available`);
totalPacks += packs;
totalPrice += packs * Number(product.price);
breakdown.push({
coffee_table_id: coffeeId,
coffee_title: product.title || null,
packs,
price_per_pack: Number(product.price),
currency: product.currency,
});
}
if (totalPacks !== 6 && totalPacks !== 12) {
throw new Error('Order must contain exactly 6 packs (60 capsules) or 12 packs (120 capsules).');
}
const previousPacks = Array.isArray(abon.pack_breakdown)
? abon.pack_breakdown.reduce((sum, item) => sum + Number(item?.packs || item?.quantity || 0), 0)
: null;
return this.repo.transitionContent(abonementId, {
pack_breakdown: breakdown,
price: Number(totalPrice.toFixed(2)),
currency: breakdown[0]?.currency || abon.currency || 'EUR',
actor_user_id: actorUser?.id || null,
event_type: 'content_updated',
details: {
pack_group: abon.pack_group,
previous_total_packs: previousPacks,
total_packs: totalPacks,
effective_from: 'next_billing_cycle',
},
});
}
async renew({ abonementId, actorUser, invoiceId }) { async renew({ abonementId, actorUser, invoiceId }) {
const abon = await this.repo.getAbonementById(abonementId); const abon = await this.repo.getAbonementById(abonementId);
if (!abon) throw new Error('Not found'); if (!abon) throw new Error('Not found');
@ -354,7 +527,11 @@ class AbonemmentService {
renewed, renewed,
new Date(abon.next_billing_at || new Date()), new Date(abon.next_billing_at || new Date()),
next, next,
{ actorUserId: actorUser?.id || null } {
actorUserId: actorUser?.id || null,
userType: actorUser?.user_type || actorUser?.userType || 'personal',
lang: actorUser?.lang || actorUser?.language || 'en'
}
); );
await this.repo.appendHistory( await this.repo.appendHistory(
abonementId, abonementId,
@ -374,6 +551,26 @@ class AbonemmentService {
return this.repo.listByUser(userId); return this.repo.listByUser(userId);
} }
async getMyAbonementStatus({ userId }) {
const list = await this.repo.listByUser(userId);
const current = list.find((a) => ['active', 'paused'].includes(a.status)) || list[0] || null;
return {
hasAbo: Boolean(current),
abonement: current,
};
}
async getInvoicesForAbonement({ abonementId, actorUser }) {
const abon = await this.repo.getAbonementById(abonementId);
if (!abon) {
throw new Error('Not found');
}
if (!this.canManageAbonement(abon, actorUser)) {
throw new Error('Forbidden');
}
return this.invoiceService.listByAbonement(abonementId);
}
async getHistory({ abonementId }) { async getHistory({ abonementId }) {
return this.repo.listHistory(abonementId); return this.repo.listHistory(abonementId);
} }

View File

@ -1,19 +1,14 @@
const brevo = require('@getbrevo/brevo'); const { BrevoClient } = require('@getbrevo/brevo');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { logger } = require('../../middleware/logger'); const { logger } = require('../../middleware/logger');
class MailService { class MailService {
constructor() { constructor() {
this.brevo = new brevo.TransactionalEmailsApi();
const rawApiKey = process.env.BREVO_API_KEY; const rawApiKey = process.env.BREVO_API_KEY;
const apiKey = (rawApiKey || '').trim(); // helps catch whitespace/newline issues const apiKey = (rawApiKey || '').trim(); // helps catch whitespace/newline issues
this.brevo.setApiKey( this.brevo = new BrevoClient({ apiKey });
brevo.TransactionalEmailsApiApiKeys.apiKey,
apiKey
);
logger.info('MailService:brevo_api_key_loaded', { logger.info('MailService:brevo_api_key_loaded', {
present: Boolean(apiKey), present: Boolean(apiKey),
@ -59,8 +54,8 @@ class MailService {
} }
_extractBrevoErrorDetails(error) { _extractBrevoErrorDetails(error) {
const status = error?.response?.status; const status = error?.statusCode ?? error?.response?.status;
const data = error?.response?.data; const data = error?.body ?? error?.response?.data;
let dataSafe; let dataSafe;
try { try {
dataSafe = typeof data === 'string' ? data.slice(0, 2000) : JSON.parse(JSON.stringify(data)); dataSafe = typeof data === 'string' ? data.slice(0, 2000) : JSON.parse(JSON.stringify(data));
@ -101,24 +96,25 @@ class MailService {
} }
try { try {
const payload = new brevo.SendSmtpEmail(); const payload = {
payload.sender = this.sender; sender: this.sender,
payload.to = [{ email }]; to: [{ email }],
payload.subject = subject; subject,
payload.textContent = text; textContent: text
};
const data = await this.brevo.sendTransacEmail(payload); const data = await this.brevo.transactionalEmails.sendTransacEmail(payload);
logger.info('MailService.sendRegistrationEmail:email_sent', { email, userType, lang: chosenLang }); logger.info('MailService.sendRegistrationEmail:email_sent', { email, userType, lang: chosenLang });
return data; return data;
} catch (error) { } catch (error) {
const brevo = this._extractBrevoErrorDetails(error); const brevoError = this._extractBrevoErrorDetails(error);
logger.error('MailService.sendRegistrationEmail:error', { logger.error('MailService.sendRegistrationEmail:error', {
email, email,
userType, userType,
lang: chosenLang, lang: chosenLang,
message: error?.message, message: error?.message,
brevoStatus: brevo.status, brevoStatus: brevoError.status,
brevoData: brevo.data brevoData: brevoError.data
}); });
throw error; throw error;
} }
@ -136,23 +132,24 @@ class MailService {
}, chosenLang); }, chosenLang);
try { try {
const payload = new brevo.SendSmtpEmail(); const payload = {
payload.sender = this.sender; sender: this.sender,
payload.to = [{ email }]; to: [{ email }],
payload.subject = subject; subject,
payload.textContent = text; textContent: text
};
const data = await this.brevo.sendTransacEmail(payload); const data = await this.brevo.transactionalEmails.sendTransacEmail(payload);
logger.info('MailService.sendVerificationCodeEmail:email_sent', { email, lang: chosenLang }); // don't log code logger.info('MailService.sendVerificationCodeEmail:email_sent', { email, lang: chosenLang }); // don't log code
return data; return data;
} catch (error) { } catch (error) {
const brevo = this._extractBrevoErrorDetails(error); const brevoError = this._extractBrevoErrorDetails(error);
logger.error('MailService.sendVerificationCodeEmail:error', { logger.error('MailService.sendVerificationCodeEmail:error', {
email, email,
lang: chosenLang, lang: chosenLang,
message: error?.message, message: error?.message,
brevoStatus: brevo.status, brevoStatus: brevoError.status,
brevoData: brevo.data brevoData: brevoError.data
}); });
throw error; throw error;
} }
@ -172,24 +169,25 @@ class MailService {
}, chosenLang); }, chosenLang);
try { try {
const payload = new brevo.SendSmtpEmail(); const payload = {
payload.sender = this.sender; sender: this.sender,
payload.to = [{ email }]; to: [{ email }],
payload.subject = subject; subject,
payload.textContent = text; textContent: text
};
const data = await this.brevo.sendTransacEmail(payload); const data = await this.brevo.transactionalEmails.sendTransacEmail(payload);
logger.info('MailService.sendLoginNotificationEmail:email_sent', { email, ip, lang: chosenLang }); logger.info('MailService.sendLoginNotificationEmail:email_sent', { email, ip, lang: chosenLang });
return data; return data;
} catch (error) { } catch (error) {
const brevo = this._extractBrevoErrorDetails(error); const brevoError = this._extractBrevoErrorDetails(error);
logger.error('MailService.sendLoginNotificationEmail:error', { logger.error('MailService.sendLoginNotificationEmail:error', {
email, email,
ip, ip,
lang: chosenLang, lang: chosenLang,
message: error?.message, message: error?.message,
brevoStatus: brevo.status, brevoStatus: brevoError.status,
brevoData: brevo.data brevoData: brevoError.data
}); });
throw error; throw error;
} }
@ -209,28 +207,139 @@ class MailService {
}, chosenLang); }, chosenLang);
try { try {
const payload = new brevo.SendSmtpEmail(); const payload = {
payload.sender = this.sender; sender: this.sender,
payload.to = [{ email }]; to: [{ email }],
payload.subject = subject; subject,
payload.textContent = text; textContent: text
};
const data = await this.brevo.sendTransacEmail(payload); const data = await this.brevo.transactionalEmails.sendTransacEmail(payload);
logger.info('MailService.sendPasswordResetEmail:email_sent', { email, token, lang: chosenLang }); logger.info('MailService.sendPasswordResetEmail:email_sent', { email, token, lang: chosenLang });
return data; return data;
} catch (error) { } catch (error) {
const brevo = this._extractBrevoErrorDetails(error); const brevoError = this._extractBrevoErrorDetails(error);
logger.error('MailService.sendPasswordResetEmail:error', { logger.error('MailService.sendPasswordResetEmail:error', {
email, email,
token, token,
lang: chosenLang, lang: chosenLang,
message: error?.message, message: error?.message,
brevoStatus: brevo.status, brevoStatus: brevoError.status,
brevoData: brevo.data brevoData: brevoError.data
}); });
throw error; throw error;
} }
} }
async sendInvoiceEmail({ email, subject, text, html, lang, attachments = [] }) {
logger.info('MailService.sendInvoiceEmail:start', { email, lang, hasHtml: Boolean(html), attachments: attachments.length });
try {
const payload = {
sender: this.sender,
to: [{ email }],
subject,
textContent: text
};
if (html) {
payload.htmlContent = html;
}
if (Array.isArray(attachments) && attachments.length) {
payload.attachment = attachments;
}
const data = await this.brevo.transactionalEmails.sendTransacEmail(payload);
logger.info('MailService.sendInvoiceEmail:email_sent', { email, lang, hasHtml: Boolean(html), attachments: attachments.length });
return data;
} catch (error) {
const brevoError = this._extractBrevoErrorDetails(error);
logger.error('MailService.sendInvoiceEmail:error', {
email,
lang,
message: error?.message,
brevoStatus: brevoError.status,
brevoData: brevoError.data
});
throw error;
}
}
async sendSubscriptionInvitationEmail({ email, inviterName, referralLink, lang = 'en' }) {
logger.info('MailService.sendSubscriptionInvitationEmail:start', { email, lang });
const isDe = lang === 'de';
const subject = isDe
? 'ProfitPlanet: Einladung zur Registrierung'
: 'ProfitPlanet: Invitation to register';
const text = isDe
? `Hallo,\n\n${inviterName || 'Ein Benutzer'} hat ein Kaffee-Abonnement für Sie erstellt.\nBitte registrieren Sie sich hier: ${referralLink}\n\nViele Grüße\nProfitPlanet Team`
: `Hi,\n\n${inviterName || 'A user'} created a coffee subscription for you.\nPlease register here: ${referralLink}\n\nBest regards\nProfitPlanet Team`;
const html = `<!doctype html>
<html>
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:24px;background:#f5f7fb;font-family:Arial,sans-serif;color:#1f2937;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<table role="presentation" width="620" cellspacing="0" cellpadding="0" style="max-width:620px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
<tr>
<td style="padding:20px 24px;background:#111827;color:#ffffff;">
<h2 style="margin:0;font-size:22px;">${isDe ? 'Einladung zur Registrierung' : 'Invitation to register'}</h2>
</td>
</tr>
<tr>
<td style="padding:22px 24px;">
<p style="margin:0 0 12px 0;line-height:1.6;">${isDe
? `${this._escapeForHtml(inviterName || 'Ein Benutzer')} hat ein Kaffee-Abonnement für Sie erstellt.`
: `${this._escapeForHtml(inviterName || 'A user')} created a coffee subscription for you.`}</p>
<p style="margin:0 0 18px 0;line-height:1.6;">${isDe ? 'Bitte schließen Sie Ihre Registrierung über den folgenden Link ab:' : 'Please complete your registration using the link below:'}</p>
<p style="margin:0;">
<a href="${this._escapeForHtml(referralLink)}" style="display:inline-block;background:#2563eb;color:#ffffff;text-decoration:none;padding:10px 16px;border-radius:8px;font-weight:700;">${isDe ? 'Jetzt registrieren' : 'Register now'}</a>
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`;
try {
const payload = {
sender: this.sender,
to: [{ email }],
subject,
textContent: text,
htmlContent: html,
};
const data = await this.brevo.transactionalEmails.sendTransacEmail(payload);
logger.info('MailService.sendSubscriptionInvitationEmail:email_sent', { email, lang });
return data;
} catch (error) {
const brevoError = this._extractBrevoErrorDetails(error);
logger.error('MailService.sendSubscriptionInvitationEmail:error', {
email,
lang,
message: error?.message,
brevoStatus: brevoError.status,
brevoData: brevoError.data
});
throw error;
}
}
_escapeForHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
} }
module.exports = new MailService(); module.exports = new MailService();

View File

@ -1,12 +1,282 @@
const InvoiceRepository = require('../../repositories/invoice/InvoiceRepository'); const InvoiceRepository = require('../../repositories/invoice/InvoiceRepository');
const UnitOfWork = require('../../database/UnitOfWork'); // NEW const UnitOfWork = require('../../database/UnitOfWork'); // NEW
const TaxRepository = require('../../repositories/tax/taxRepository'); // NEW const TaxRepository = require('../../repositories/tax/taxRepository'); // NEW
const PoolInflowService = require('../pool/PoolInflowService');
const DocumentTemplateService = require('../template/DocumentTemplateService');
const MailService = require('../email/MailService');
const { GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
const { s3: sharedExoscaleClient } = require('../../utils/exoscaleUploader');
const { logger } = require('../../middleware/logger');
const puppeteer = require('puppeteer');
class InvoiceService { class InvoiceService {
constructor() { constructor() {
this.repo = new InvoiceRepository(); this.repo = new InvoiceRepository();
} }
_escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
_formatAmount(amount, currency = 'EUR') {
const numeric = Number(amount || 0);
return `${numeric.toFixed(2)} ${currency}`;
}
async _s3BodyToString(body) {
if (!body) return '';
if (typeof body.transformToString === 'function') {
return body.transformToString();
}
const chunks = [];
for await (const chunk of body) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks).toString('utf8');
}
_getEmailSubject(lang, invoiceNumber) {
return lang === 'de'
? `ProfitPlanet Rechnung ${invoiceNumber}`
: `ProfitPlanet Invoice ${invoiceNumber}`;
}
_buildItemsText(items, currency) {
if (!Array.isArray(items) || !items.length) return '- Subscription item';
return items.map((item, index) => {
const qty = Number(item.quantity || 0);
const lineGross = Number(item.line_gross || 0);
return `${index + 1}. ${item.description || 'Coffee'} | qty: ${qty} | total: ${this._formatAmount(lineGross, currency)}`;
}).join('\n');
}
_buildItemsHtml(items, currency) {
if (!Array.isArray(items) || !items.length) {
return '<li>Subscription item</li>';
}
return items.map((item) => {
const desc = this._escapeHtml(item.description || 'Coffee');
const qty = Number(item.quantity || 0);
const unit = this._formatAmount(item.unit_price || 0, currency);
const total = this._formatAmount(item.line_gross || 0, currency);
return `<li><strong>${desc}</strong> — ${qty} x ${this._escapeHtml(unit)} = ${this._escapeHtml(total)}</li>`;
}).join('');
}
_buildInvoiceText({ invoice, items, abonement, lang }) {
const isDe = lang === 'de';
const customerName = [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || invoice.buyer_name || '-';
const issuedAt = invoice.issued_at ? new Date(invoice.issued_at).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);
return [
isDe ? 'Ihre Rechnung wurde erstellt.' : 'Your invoice has been created.',
`${isDe ? 'Rechnungsnummer' : 'Invoice number'}: ${invoice.invoice_number}`,
`${isDe ? 'Kunde' : 'Customer'}: ${customerName}`,
`${isDe ? 'Datum' : 'Date'}: ${issuedAt}`,
`${isDe ? 'Positionen' : 'Items'}:`,
this._buildItemsText(items, invoice.currency),
`${isDe ? 'Gesamtbetrag' : 'Total'}: ${this._formatAmount(invoice.total_gross, invoice.currency)}`,
].join('\n');
}
_buildInvoiceMailText({ invoice, items, abonement, lang }) {
const isDe = lang === 'de';
const customerName = [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || invoice.buyer_name || '-';
return [
isDe ? `Vielen Dank für Ihr Abonnement, ${customerName}.` : `Thank you for your subscription, ${customerName}.`,
isDe ? 'Ihre Rechnung ist als PDF im Anhang enthalten.' : 'Your invoice is attached as a PDF.',
`${isDe ? 'Rechnungsnummer' : 'Invoice number'}: ${invoice.invoice_number}`,
`${isDe ? 'Gesamtbetrag' : 'Total'}: ${this._formatAmount(invoice.total_gross, invoice.currency)}`,
'',
`${isDe ? 'Positionen' : 'Items'}:`,
this._buildItemsText(items, invoice.currency),
].join('\n');
}
_buildInvoiceMailHtml({ invoice, abonement, lang }) {
const isDe = lang === 'de';
const customerName = [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || invoice.buyer_name || 'Customer';
const logoUrl = process.env.MAIL_LOGO_URL || process.env.BREVO_LOGO_URL || process.env.APP_LOGO_URL || '';
return `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${this._escapeHtml(invoice.invoice_number)}</title>
</head>
<body style="margin:0;padding:0;background:#f5f7fb;font-family:Arial,sans-serif;color:#1f2937;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f5f7fb;padding:24px 0;">
<tr>
<td align="center">
<table role="presentation" width="640" cellspacing="0" cellpadding="0" style="max-width:640px;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 8px 24px rgba(0,0,0,0.08);">
<tr>
<td style="padding:24px 28px;background:#111827;color:#ffffff;">
${logoUrl ? `<img src="${this._escapeHtml(logoUrl)}" alt="ProfitPlanet" style="max-height:44px;display:block;margin-bottom:12px;">` : ''}
<h1 style="margin:0;font-size:22px;line-height:1.3;">${isDe ? 'Danke für Ihr Abonnement!' : 'Thank you for your subscription!'}</h1>
</td>
</tr>
<tr>
<td style="padding:24px 28px;">
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.6;">${isDe ? 'Hallo' : 'Hi'} ${this._escapeHtml(customerName)},</p>
<p style="margin:0 0 18px 0;font-size:15px;line-height:1.6;">${isDe ? 'vielen Dank für Ihr Abonnement. Ihre Rechnung haben wir als PDF angehängt.' : 'thank you for your subscription. We attached your invoice as a PDF.'}</p>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;margin-bottom:18px;">
<tr>
<td style="padding:12px 14px;background:#f9fafb;font-size:13px;color:#6b7280;">${isDe ? 'Rechnungsnummer' : 'Invoice number'}</td>
<td style="padding:12px 14px;background:#f9fafb;font-size:13px;text-align:right;font-weight:700;">${this._escapeHtml(invoice.invoice_number || '')}</td>
</tr>
<tr>
<td style="padding:12px 14px;font-size:13px;color:#6b7280;">${isDe ? 'Gesamtbetrag' : 'Total'}</td>
<td style="padding:12px 14px;font-size:13px;text-align:right;font-weight:700;">${this._escapeHtml(this._formatAmount(invoice.total_gross, invoice.currency))}</td>
</tr>
</table>
<p style="margin:0;font-size:13px;color:#6b7280;">${isDe ? 'Falls diese E-Mail nicht korrekt angezeigt wird, nutzen Sie bitte den Textinhalt oder kontaktieren Sie unseren Support.' : 'If this email is not displayed correctly, please use the text version or contact support.'}</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`;
}
_buildFallbackInvoiceHtml({ invoice, items, abonement, lang }) {
const isDe = lang === 'de';
const customerName = [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || invoice.buyer_name || '-';
const issuedAt = invoice.issued_at ? new Date(invoice.issued_at).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);
return `<!doctype html>
<html>
<head><meta charset="utf-8"><title>${this._escapeHtml(invoice.invoice_number)}</title></head>
<body>
<h2>${isDe ? 'Rechnung' : 'Invoice'} ${this._escapeHtml(invoice.invoice_number)}</h2>
<p>${isDe ? 'Kunde' : 'Customer'}: ${this._escapeHtml(customerName)}</p>
<p>${isDe ? 'Datum' : 'Date'}: ${this._escapeHtml(issuedAt)}</p>
<h3>${isDe ? 'Positionen' : 'Items'}</h3>
<ul>${this._buildItemsHtml(items, invoice.currency)}</ul>
<p><strong>${isDe ? 'Gesamtbetrag' : 'Total'}: ${this._escapeHtml(this._formatAmount(invoice.total_gross, invoice.currency))}</strong></p>
</body>
</html>`;
}
async _loadInvoiceTemplateHtml({ userType = 'personal', lang = 'en' } = {}) {
try {
const templates = await DocumentTemplateService.getActiveTemplatesForUserType(userType, 'invoice');
if (!Array.isArray(templates) || !templates.length) return null;
const selected = templates.find((t) => t.lang === lang) || templates.find((t) => t.lang === 'en') || templates[0];
if (!selected?.storageKey) return null;
const command = new GetObjectCommand({
Bucket: process.env.EXOSCALE_BUCKET,
Key: selected.storageKey,
});
const obj = await sharedExoscaleClient.send(command);
const html = await this._s3BodyToString(obj.Body);
return html || null;
} catch (error) {
logger.warn('InvoiceService._loadInvoiceTemplateHtml:error', { message: error?.message });
return null;
}
}
_renderTemplate(template, variables) {
if (!template) return null;
return template.replace(/{{\s*([\w]+)\s*}}/g, (_, key) => variables[key] ?? '');
}
async _renderPdfFromHtml(html) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
try {
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
});
return Buffer.isBuffer(pdfBuffer) ? pdfBuffer : Buffer.from(pdfBuffer);
} finally {
await browser.close();
}
}
async _storeInvoicePdf(invoice, pdfBuffer) {
if (!pdfBuffer) return null;
const safeUser = invoice.user_id || 'unknown';
const key = `invoices/${safeUser}/${invoice.invoice_number || `invoice-${invoice.id}`}.pdf`;
await sharedExoscaleClient.send(new PutObjectCommand({
Bucket: process.env.EXOSCALE_BUCKET,
Key: key,
Body: pdfBuffer,
ContentType: 'application/pdf',
}));
await this.repo.updateStorageKey(invoice.id, key);
return key;
}
async _sendInvoiceEmail({ invoice, abonement, userType = 'personal', lang = 'en' }) {
const recipientEmail = invoice.buyer_email || abonement?.email;
if (!recipientEmail) {
logger.warn('InvoiceService._sendInvoiceEmail:missing_recipient', { invoiceId: invoice.id });
return;
}
const items = await this.repo.getItemsByInvoiceId(invoice.id);
const text = this._buildInvoiceMailText({ invoice, items, abonement, lang });
const subject = this._getEmailSubject(lang, invoice.invoice_number);
const templateHtml = await this._loadInvoiceTemplateHtml({ userType, lang });
let html = null;
if (templateHtml) {
const customerName = [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || invoice.buyer_name || '';
const issuedAt = invoice.issued_at ? new Date(invoice.issued_at).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);
const variables = {
invoiceNumber: this._escapeHtml(invoice.invoice_number || ''),
customerName: this._escapeHtml(customerName),
issuedAt: this._escapeHtml(issuedAt),
totalNet: this._escapeHtml(this._formatAmount(invoice.total_net, invoice.currency)),
totalTax: this._escapeHtml(this._formatAmount(invoice.total_tax, invoice.currency)),
totalGross: this._escapeHtml(this._formatAmount(invoice.total_gross, invoice.currency)),
itemsHtml: this._buildItemsHtml(items, invoice.currency),
};
html = this._renderTemplate(templateHtml, variables);
if (html && !html.includes('<li>')) {
html += `<hr><h3>${lang === 'de' ? 'Positionen' : 'Items'}</h3><ul>${variables.itemsHtml}</ul>`;
}
}
const htmlForPdf = html || this._buildFallbackInvoiceHtml({ invoice, items, abonement, lang });
const pdfBuffer = await this._renderPdfFromHtml(htmlForPdf);
await this._storeInvoicePdf(invoice, pdfBuffer);
const mailHtml = this._buildInvoiceMailHtml({ invoice, abonement, lang });
await MailService.sendInvoiceEmail({
email: recipientEmail,
subject,
text,
html: mailHtml,
attachments: [{
name: `${invoice.invoice_number || `invoice-${invoice.id}`}.pdf`,
content: pdfBuffer.toString('base64')
}],
lang,
});
}
// NEW: resolve current standard VAT rate for a buyer country code // NEW: resolve current standard VAT rate for a buyer country code
async resolveVatRateForCountry(countryCode) { async resolveVatRateForCountry(countryCode) {
if (!countryCode) return null; if (!countryCode) return null;
@ -34,7 +304,7 @@ class InvoiceService {
} }
// Issue invoice for a subscription period, with items from pack_breakdown // Issue invoice for a subscription period, with items from pack_breakdown
async issueForAbonement(abonement, periodStart, periodEnd, { actorUserId } = {}) { async issueForAbonement(abonement, periodStart, periodEnd, { actorUserId, userType = 'personal', lang = 'en' } = {}) {
console.log('[INVOICE ISSUE] Inputs:', { console.log('[INVOICE ISSUE] Inputs:', {
abonement_id: abonement?.id, abonement_id: abonement?.id,
abonement_user_id: abonement?.user_id, abonement_user_id: abonement?.user_id,
@ -62,7 +332,7 @@ class InvoiceService {
? breakdown.map((b) => ({ ? breakdown.map((b) => ({
product_id: Number(b.coffee_table_id) || null, product_id: Number(b.coffee_table_id) || null,
sku: `COFFEE-${b.coffee_table_id || 'N/A'}`, sku: `COFFEE-${b.coffee_table_id || 'N/A'}`,
description: `Coffee subscription: ${b.coffee_table_id}`, description: b.coffee_title || `Coffee subscription: ${b.coffee_table_id}`,
quantity: Number(b.packs || 1), quantity: Number(b.packs || 1),
unit_price: Number(b.price_per_pack || 0), unit_price: Number(b.price_per_pack || 0),
tax_rate: b.tax_rate != null ? Number(b.tax_rate) : vat_rate, // CHANGED: default to invoice vat_rate tax_rate: b.tax_rate != null ? Number(b.tax_rate) : vat_rate, // CHANGED: default to invoice vat_rate
@ -121,17 +391,56 @@ class InvoiceService {
total_gross: invoice?.total_gross, total_gross: invoice?.total_gross,
}); });
try {
await this._sendInvoiceEmail({
invoice,
abonement,
userType,
lang,
});
logger.info('InvoiceService.issueForAbonement:invoice_email_sent', {
invoiceId: invoice?.id,
userId: invoice?.user_id,
hasEmail: Boolean(invoice?.buyer_email || abonement?.email),
});
} catch (mailError) {
logger.error('InvoiceService.issueForAbonement:invoice_email_error', {
invoiceId: invoice?.id,
message: mailError?.message,
});
}
return invoice; return invoice;
} }
async markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at = new Date(), details } = {}) { async markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at = new Date(), details } = {}) {
return this.repo.markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at, details }); const paidInvoice = await this.repo.markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at, details });
try {
const inflowResult = await PoolInflowService.bookForPaidInvoice({
invoiceId: paidInvoice?.id,
paidAt: paid_at,
actorUserId: null,
});
console.log('[INVOICE PAID] Pool inflow booking result:', {
invoiceId: paidInvoice?.id,
...inflowResult,
});
} catch (e) {
console.error('[INVOICE PAID] Pool inflow booking failed:', e);
}
return paidInvoice;
} }
async listMine(userId, { status, limit = 50, offset = 0 } = {}) { async listMine(userId, { status, limit = 50, offset = 0 } = {}) {
return this.repo.listByUser(userId, { status, limit, offset }); return this.repo.listByUser(userId, { status, limit, offset });
} }
async listByAbonement(abonementId) {
return this.repo.findByAbonement(abonementId);
}
async adminList({ status, limit = 200, offset = 0 } = {}) { async adminList({ status, limit = 200, offset = 0 } = {}) {
return this.repo.listAll({ status, limit, offset }); return this.repo.listAll({ status, limit, offset });
} }

View File

@ -0,0 +1,226 @@
const db = require('../../database/database');
function toTwo(value) {
return Number(Number(value || 0).toFixed(2));
}
function toFour(value) {
return Number(Number(value || 0).toFixed(4));
}
class PoolInflowService {
async analyzePaidInvoice({ invoiceId, paidAt }) {
const normalizedInvoiceId = Number(invoiceId);
if (!Number.isFinite(normalizedInvoiceId) || normalizedInvoiceId <= 0) {
return { ok: false, reason: 'invalid_invoice_id' };
}
const [invoiceRows] = await db.query(
`SELECT id, source_type, source_id, currency, status, context
FROM invoices
WHERE id = ?
LIMIT 1`,
[normalizedInvoiceId]
);
const invoice = invoiceRows?.[0];
if (!invoice) return { ok: false, reason: 'invoice_not_found' };
if (String(invoice.status) !== 'paid') return { ok: false, reason: 'invoice_not_paid', invoice };
if (String(invoice.source_type) !== 'subscription') return { ok: false, reason: 'unsupported_source_type', invoice };
const abonementId = Number(invoice.source_id);
if (!Number.isFinite(abonementId) || abonementId <= 0) {
return { ok: false, reason: 'missing_abonement_relation', invoice };
}
const paidAtCandidate = paidAt ? new Date(paidAt) : new Date();
const paidAtDate = Number.isFinite(paidAtCandidate.getTime()) ? paidAtCandidate : new Date();
let context = {};
try {
context = invoice.context && typeof invoice.context === 'string' ? JSON.parse(invoice.context) : (invoice.context || {});
} catch (_) {
context = {};
}
const periodStart = context?.period_start ? new Date(context.period_start) : null;
if (periodStart && Number.isFinite(periodStart.getTime()) && paidAtDate < periodStart) {
return { ok: false, reason: 'paid_before_period_start', invoice, abonementId, paidAtDate, periodStart };
}
const [abonRows] = await db.query(
`SELECT id, pack_breakdown, currency
FROM coffee_abonements
WHERE id = ?
LIMIT 1`,
[abonementId]
);
const abonement = abonRows?.[0];
if (!abonement) return { ok: false, reason: 'abonement_not_found', invoice, abonementId };
const breakdownRaw = abonement.pack_breakdown;
let breakdown = [];
try {
breakdown = breakdownRaw
? (typeof breakdownRaw === 'string' ? JSON.parse(breakdownRaw) : breakdownRaw)
: [];
} catch (_) {
breakdown = [];
}
const normalizedLines = Array.isArray(breakdown)
? breakdown
.map((line) => ({
coffeeId: Number(line?.coffee_table_id),
capsulesCount: Number(line?.packs || 0) * 10,
}))
.filter((line) => Number.isFinite(line.coffeeId) && line.coffeeId > 0 && Number.isFinite(line.capsulesCount) && line.capsulesCount > 0)
: [];
if (!normalizedLines.length) return { ok: false, reason: 'no_breakdown_lines', invoice, abonementId };
const byCoffee = new Map();
for (const line of normalizedLines) {
byCoffee.set(line.coffeeId, (byCoffee.get(line.coffeeId) || 0) + line.capsulesCount);
}
const coffeeIds = Array.from(byCoffee.keys());
const placeholders = coffeeIds.map(() => '?').join(',');
const [pools] = await db.query(
`SELECT id, pool_name, subscription_coffee_id, price
FROM pools
WHERE is_active = 1 AND subscription_coffee_id IN (${placeholders})`,
coffeeIds
);
if (!Array.isArray(pools) || pools.length === 0) {
return { ok: false, reason: 'no_linked_pools', invoice, abonementId, normalizedLines };
}
return {
ok: true,
reason: 'ok',
invoice,
abonementId,
paidAtDate,
byCoffee,
pools,
normalizedLines,
currency: invoice.currency || abonement.currency || 'EUR',
};
}
async bookForPaidInvoice({ invoiceId, paidAt, actorUserId = null }) {
const analysis = await this.analyzePaidInvoice({ invoiceId, paidAt });
if (!analysis.ok) {
return { inserted: 0, skipped: 0, reason: analysis.reason };
}
const normalizedInvoiceId = Number(analysis.invoice.id);
const abonementId = Number(analysis.abonementId);
const paidAtDate = analysis.paidAtDate;
const byCoffee = analysis.byCoffee;
const pools = analysis.pools;
const currency = analysis.currency;
const conn = await db.getConnection();
let inserted = 0;
try {
let alreadyExists = 0;
await conn.beginTransaction();
for (const pool of pools) {
const coffeeId = Number(pool.subscription_coffee_id);
const capsulesCount = Number(byCoffee.get(coffeeId) || 0);
if (!capsulesCount) continue;
const pricePerCapsuleNet = toFour(pool.price);
const amountNet = toTwo(capsulesCount * pricePerCapsuleNet);
const details = {
source: 'invoice_paid',
formula: 'capsules_count * price_per_capsule_net',
paid_at: paidAtDate,
};
const [res] = await conn.query(
`INSERT INTO pool_inflows
(pool_id, invoice_id, abonement_id, coffee_table_id, event_type, capsules_count, price_per_capsule_net, amount_net, currency, details, created_by_user_id, created_at)
VALUES (?, ?, ?, ?, 'invoice_paid', ?, ?, ?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE id = id`,
[
Number(pool.id),
normalizedInvoiceId,
abonementId,
coffeeId,
capsulesCount,
pricePerCapsuleNet,
amountNet,
currency,
JSON.stringify(details),
actorUserId || null,
]
);
if (res && Number(res.affectedRows) === 1) inserted += 1;
else alreadyExists += 1;
}
await conn.commit();
return { inserted, alreadyExists, skipped: Math.max(0, pools.length - inserted), reason: 'ok' };
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
}
async getInvoiceInflowDiagnostics({ invoiceId, paidAt }) {
const analysis = await this.analyzePaidInvoice({ invoiceId, paidAt });
if (!analysis.ok) {
return {
ok: false,
reason: analysis.reason,
invoiceId: Number(invoiceId),
};
}
const invoiceIdNum = Number(analysis.invoice.id);
const poolEntries = [];
for (const pool of analysis.pools) {
const coffeeId = Number(pool.subscription_coffee_id);
const capsulesCount = Number(analysis.byCoffee.get(coffeeId) || 0);
const pricePerCapsuleNet = toFour(pool.price);
const amountNet = toTwo(capsulesCount * pricePerCapsuleNet);
const [existingRows] = await db.query(
`SELECT id, created_at
FROM pool_inflows
WHERE pool_id = ? AND invoice_id = ? AND coffee_table_id = ? AND event_type = 'invoice_paid'
LIMIT 1`,
[Number(pool.id), invoiceIdNum, coffeeId]
);
poolEntries.push({
pool_id: Number(pool.id),
pool_name: pool.pool_name,
coffee_table_id: coffeeId,
capsules_count: capsulesCount,
price_per_capsule_net: pricePerCapsuleNet,
amount_net: amountNet,
already_booked: !!existingRows?.length,
existing_inflow_id: existingRows?.[0]?.id || null,
});
}
return {
ok: true,
reason: 'ok',
invoice_id: invoiceIdNum,
abonement_id: Number(analysis.abonementId),
paid_at: analysis.paidAtDate,
currency: analysis.currency,
candidates: poolEntries,
will_book_count: poolEntries.filter((x) => !x.already_booked).length,
already_booked_count: poolEntries.filter((x) => x.already_booked).length,
};
}
}
module.exports = new PoolInflowService();

View File

@ -1,22 +1,41 @@
const UnitOfWork = require('../../database/UnitOfWork'); const UnitOfWork = require('../../database/UnitOfWork');
const PoolRepository = require('../../repositories/pool/poolRepository'); const PoolRepository = require('../../repositories/pool/poolRepository');
const db = require('../../database/database');
function isValidPoolType(pool_type) { function isValidPoolType(pool_type) {
return pool_type === 'coffee' || pool_type === 'other'; return pool_type === 'coffee' || pool_type === 'other';
} }
async function createPool({ pool_name, description = null, price = 0.00, pool_type = 'other', is_active = true, created_by = null }) { async function createPool({ pool_name, description = null, price = 0.00, subscription_coffee_id = null, pool_type = 'other', is_active = true, created_by = null }) {
if (!isValidPoolType(pool_type)) { if (!isValidPoolType(pool_type)) {
const err = new Error('Invalid pool_type. Allowed: coffee, other'); const err = new Error('Invalid pool_type. Allowed: coffee, other');
err.status = 400; err.status = 400;
throw err; throw err;
} }
let normalizedSubscriptionCoffeeId = null;
if (subscription_coffee_id !== null && subscription_coffee_id !== undefined && String(subscription_coffee_id).trim() !== '') {
const sid = Number(subscription_coffee_id);
if (!Number.isFinite(sid) || sid <= 0) {
const err = new Error('Invalid subscription_coffee_id');
err.status = 400;
throw err;
}
const [rows] = await db.query('SELECT id FROM coffee_table WHERE id = ? LIMIT 1', [sid]);
if (!rows.length) {
const err = new Error('Selected subscription not found');
err.status = 400;
throw err;
}
normalizedSubscriptionCoffeeId = sid;
}
const uow = new UnitOfWork(); const uow = new UnitOfWork();
try { try {
console.debug('[PoolService.createPool] start', { pool_name, pool_type }); console.debug('[PoolService.createPool] start', { pool_name, pool_type, subscription_coffee_id: normalizedSubscriptionCoffeeId });
await uow.start(); await uow.start();
const repo = new PoolRepository(uow); const repo = new PoolRepository(uow);
const pool = await repo.create({ pool_name, description, price, pool_type, is_active, created_by }); const pool = await repo.create({ pool_name, description, price, subscription_coffee_id: normalizedSubscriptionCoffeeId, pool_type, is_active, created_by });
await uow.commit(); await uow.commit();
console.debug('[PoolService.createPool] success', { id: pool.id }); console.debug('[PoolService.createPool] success', { id: pool.id });
return pool; return pool;
@ -67,4 +86,35 @@ async function updatePoolState(id, is_active, actorUserId) {
} }
} }
module.exports = { createPool, listPools, updatePoolState }; async function updatePoolSubscription({ id, subscription_coffee_id = null, actorUserId = null }) {
let normalizedSubscriptionCoffeeId = null;
if (subscription_coffee_id !== null && subscription_coffee_id !== undefined && String(subscription_coffee_id).trim() !== '') {
const sid = Number(subscription_coffee_id);
if (!Number.isFinite(sid) || sid <= 0) {
const err = new Error('Invalid subscription_coffee_id');
err.status = 400;
throw err;
}
const [rows] = await db.query('SELECT id FROM coffee_table WHERE id = ? LIMIT 1', [sid]);
if (!rows.length) {
const err = new Error('Selected subscription not found');
err.status = 400;
throw err;
}
normalizedSubscriptionCoffeeId = sid;
}
const uow = new UnitOfWork();
try {
await uow.start();
const repo = new PoolRepository(uow);
const updated = await repo.updateSubscriptionLink(id, normalizedSubscriptionCoffeeId, actorUserId);
await uow.commit();
return updated;
} catch (err) {
try { await uow.rollback(); } catch (_) { console.warn('[PoolService.updatePoolSubscription] rollback failed'); }
throw err;
}
}
module.exports = { createPool, listPools, updatePoolState, updatePoolSubscription };