dev #32

Merged
Seazn merged 3 commits from dev into main 2026-06-07 19:21:13 +00:00
20 changed files with 927 additions and 437 deletions

View File

@ -1,6 +1,21 @@
const UnitOfWork = require('../../database/UnitOfWork');
const UserSettingsRepository = require('../../repositories/settings/UserSettingsRepository');
const I18nPreferencesRepository = require('../../repositories/settings/I18nPreferencesRepository');
const { logger } = require('../../middleware/logger'); // <-- import logger
const { normalizeLanguageCode } = require('../../utils/languageUtils');
const i18nPreferencesRepository = new I18nPreferencesRepository();
async function resolvePreferredLanguage(rawValue) {
if (rawValue === undefined) return undefined;
if (rawValue === null || String(rawValue).trim() === '') return null;
const normalized = normalizeLanguageCode(rawValue);
if (!normalized) return '';
const languages = await i18nPreferencesRepository.listLanguages({ enabledOnly: true });
return languages.some((entry) => entry.languageCode === normalized) ? normalized : '';
}
class UserSettingsController {
static async getSettings(req, res) {
@ -24,6 +39,47 @@ class UserSettingsController {
res.status(500).json({ success: false, message: error.message });
}
}
static async updateSettings(req, res) {
const userId = req.user.userId;
logger.info(`[UserSettingsController] updateSettings called for userId: ${userId}`);
const rawPreferredLanguage = req.body?.preferredLanguage
?? req.body?.preferred_language
?? req.body?.language
?? req.body?.lang;
if (rawPreferredLanguage === undefined) {
return res.status(400).json({
success: false,
message: 'No supported settings payload provided',
});
}
const preferredLanguage = await resolvePreferredLanguage(rawPreferredLanguage);
if (preferredLanguage === '') {
return res.status(400).json({
success: false,
message: 'Invalid preferred language',
});
}
const unitOfWork = new UnitOfWork();
await unitOfWork.start();
try {
const repo = new UserSettingsRepository(unitOfWork);
const settings = await repo.updatePreferredLanguage(userId, preferredLanguage, unitOfWork);
await unitOfWork.commit();
logger.info(`[UserSettingsController] updateSettings success for userId: ${userId}`, {
preferredLanguage: settings?.preferred_language || null,
});
return res.json({ success: true, settings });
} catch (error) {
logger.error(`[UserSettingsController] updateSettings error for userId: ${userId}`, { error });
await unitOfWork.rollback(error);
return res.status(500).json({ success: false, message: error.message });
}
}
}
module.exports = UserSettingsController;

View File

@ -12,7 +12,11 @@ const db = require('../../database/database');
const UnitOfWork = require('../../database/UnitOfWork');
const { logger } = require('../../middleware/logger');
const CompanyStampService = require('../../services/stamp/company/CompanyStampService');
const I18nPreferencesRepository = require('../../repositories/settings/I18nPreferencesRepository');
const { s3: sharedExoscaleClient } = require('../../utils/exoscaleUploader');
const { normalizeLanguageCode, resolveDocumentTemplateStorageFolder } = require('../../utils/languageUtils');
const i18nPreferencesRepository = new I18nPreferencesRepository();
// Ensure debug directory exists and helper to save files
function ensureDebugDir() {
@ -41,6 +45,29 @@ function normalizeInvoiceTaxMode(rawValue) {
return ['standard', 'reverse_charge', 'both'].includes(normalized) ? normalized : 'both';
}
async function resolveSupportedTemplateLanguage(rawValue) {
const normalized = normalizeLanguageCode(rawValue);
if (!normalized) return null;
const languages = await i18nPreferencesRepository.listLanguages({ enabledOnly: true });
return languages.some((entry) => entry.languageCode === normalized) ? normalized : null;
}
function getRequestedTemplateLanguage(req) {
return normalizeLanguageCode(
req?.query?.lang
|| req?.query?.language
|| req?.user?.lang
|| req?.user?.language
|| '',
) || null;
}
function buildTemplateStorageKey(lang, originalName) {
const langFolder = resolveDocumentTemplateStorageFolder(lang);
return `DocumentTemplates/${langFolder}/${Date.now()}_${originalName}`;
}
// Helper to remove/empty placeholders except allow-list
// Updated: match any content inside {{ ... }} (not only \w+) so placeholders like {{company.name}} are sanitized.
// allowList contains exact placeholder names to preserve (e.g. 'companyStamp', 'companyLogo', 'profitplanetSignature').
@ -119,7 +146,7 @@ async function enrichTemplate(template, s3, serverBaseUrl = null) {
// optional: upload sanitized preview to S3 (keeps earlier behavior for offline debugging)
try {
const langFolder = template.lang === 'en' ? 'english' : (template.lang === 'de' ? 'german' : 'other');
const langFolder = resolveDocumentTemplateStorageFolder(template.lang);
const previewKey = `DocumentTemplates/previews/${langFolder}/template_${template.id}_preview_${Date.now()}.html`;
const putCmd = new PutObjectCommand({
Bucket: process.env.EXOSCALE_BUCKET,
@ -350,7 +377,8 @@ async function fetchLatestActiveContractTemplateHtml({ userType, contractType })
? String(contractType).toLowerCase()
: 'contract';
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', safeContractType);
const lang = arguments[0]?.lang || null;
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', safeContractType, null, lang);
if (!latest) return { latest: null, html: '' };
const s3 = new S3Client({
@ -371,13 +399,13 @@ async function fetchLatestActiveContractTemplateHtml({ userType, contractType })
return { latest, html: ensureHtmlDocument(html) };
}
async function renderLatestActiveContractHtmlForUser({ targetUserId, userType, contractType, req = null }) {
async function renderLatestActiveContractHtmlForUser({ targetUserId, userType, contractType, req = null, lang = null }) {
const allowedContractTypes = ['contract', 'gdpr', 'abo'];
const safeContractType = allowedContractTypes.includes(String(contractType || '').toLowerCase())
? String(contractType).toLowerCase()
: 'contract';
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', safeContractType);
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', safeContractType, null, lang);
if (!latest) return { latest: null, html: '' };
const s3 = new S3Client({
@ -480,7 +508,8 @@ exports.uploadTemplate = async (req, res) => {
const user_type = allowed.includes(rawUserType) ? rawUserType : 'both';
const file = req.file;
if (!file) return res.status(400).json({ error: 'No file uploaded' });
if (!lang || !['en', 'de'].includes(lang)) return res.status(400).json({ error: 'Invalid or missing language' });
const supportedLang = await resolveSupportedTemplateLanguage(lang);
if (!supportedLang) return res.status(400).json({ error: 'Invalid or missing language' });
const allowedContractTypes = ['contract', 'gdpr', 'abo'];
const normalizedContractType = rawContractType !== undefined && rawContractType !== null
@ -491,9 +520,7 @@ exports.uploadTemplate = async (req, res) => {
: (type === 'contract' ? 'contract' : null);
const tax_mode = type === 'invoice' ? normalizeInvoiceTaxMode(rawTaxMode) : null;
// Use "english" for en, "german" for de
const langFolder = lang === 'en' ? 'english' : 'german';
const key = `DocumentTemplates/${langFolder}/${Date.now()}_${file.originalname}`;
const key = buildTemplateStorageKey(supportedLang, file.originalname);
const s3 = new S3Client({
region: process.env.EXOSCALE_REGION,
@ -510,7 +537,7 @@ exports.uploadTemplate = async (req, res) => {
ContentType: file.mimetype
}));
const template = await DocumentTemplateService.uploadTemplate({ name, type, contract_type, storageKey: key, description, lang, version: 1, user_type, tax_mode });
const template = await DocumentTemplateService.uploadTemplate({ name, type, contract_type, storageKey: key, description, lang: supportedLang, version: 1, user_type, tax_mode });
// Enrich with previewUrl, fileUrl, html
const enriched = await enrichTemplate(template, s3);
res.status(201).json(enriched);
@ -550,6 +577,12 @@ exports.updateTemplate = async (req, res) => {
const current = await DocumentTemplateService.getTemplate(id);
if (!current) return res.status(404).json({ error: 'Template not found' });
let nextLang = normalizeLanguageCode(current.lang) || current.lang;
if (lang !== undefined) {
nextLang = await resolveSupportedTemplateLanguage(lang);
if (!nextLang) return res.status(400).json({ error: 'Invalid or missing language' });
}
const nextType = type !== undefined ? type : current.type;
const allowedContractTypes = ['contract', 'gdpr', 'abo'];
let contract_type = null;
@ -566,10 +599,8 @@ exports.updateTemplate = async (req, res) => {
? normalizeInvoiceTaxMode(rawTaxMode !== undefined ? rawTaxMode : current.tax_mode)
: null;
// Use "english" for en, "german" for de
const langFolder = lang ? (lang === 'en' ? 'english' : 'german') : (current.lang === 'en' ? 'english' : 'german');
if (file) {
storageKey = `DocumentTemplates/${langFolder}/${Date.now()}_${file.originalname}`;
storageKey = buildTemplateStorageKey(nextLang, file.originalname);
const s3 = new S3Client({
region: process.env.EXOSCALE_REGION,
endpoint: process.env.EXOSCALE_ENDPOINT,
@ -592,7 +623,7 @@ exports.updateTemplate = async (req, res) => {
contract_type,
tax_mode,
description: description !== undefined ? description : current.description,
lang: lang !== undefined ? lang : current.lang,
lang: nextLang,
storageKey: storageKey || current.storageKey,
...(user_type !== undefined ? { user_type } : {})
};
@ -628,8 +659,10 @@ exports.reviseTemplate = async (req, res) => {
if (!previous) return res.status(404).json({ error: 'Template not found' });
const nextType = type !== undefined ? type : previous.type;
const nextLang = lang !== undefined ? lang : previous.lang;
if (!nextLang || !['en', 'de'].includes(nextLang)) return res.status(400).json({ error: 'Invalid or missing language' });
const nextLang = lang !== undefined
? await resolveSupportedTemplateLanguage(lang)
: (normalizeLanguageCode(previous.lang) || previous.lang);
if (!nextLang) return res.status(400).json({ error: 'Invalid or missing language' });
const allowedUserTypes = ['personal', 'company', 'both'];
const user_type = allowedUserTypes.includes(rawUserType)
@ -647,9 +680,7 @@ exports.reviseTemplate = async (req, res) => {
? normalizeInvoiceTaxMode(rawTaxMode !== undefined ? rawTaxMode : previous.tax_mode)
: null;
// Use "english" for en, "german" for de
const langFolder = nextLang === 'en' ? 'english' : 'german';
const storageKey = `DocumentTemplates/${langFolder}/${Date.now()}_${file.originalname}`;
const storageKey = buildTemplateStorageKey(nextLang, file.originalname);
const s3 = new S3Client({
region: process.env.EXOSCALE_REGION,
@ -2035,9 +2066,10 @@ exports.previewLatestForMe = async (req, res) => {
const contractTypeParam = (req.query.contract_type || req.query.contractType || '').toString().toLowerCase();
const allowedContractTypes = ['contract', 'gdpr', 'abo'];
const contractType = allowedContractTypes.includes(contractTypeParam) ? contractTypeParam : 'contract';
const lang = getRequestedTemplateLanguage(req);
try {
const { latest, html } = await fetchLatestActiveContractTemplateHtml({ userType, contractType });
const { latest, html } = await fetchLatestActiveContractTemplateHtml({ userType, contractType, lang });
if (!latest) {
logger.info('[previewLatestForMe] no active template', { userId: targetUserId, userType, contractType });
res.setHeader('Content-Type', 'text/html; charset=utf-8');
@ -2057,10 +2089,11 @@ exports.previewLatestAboForMe = async (req, res) => {
if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });
const targetUserId = req.user.id || req.user.userId;
const userType = (req.user.user_type || req.user.userType || '').toString().toLowerCase();
const lang = getRequestedTemplateLanguage(req);
if (!targetUserId || !userType) return res.status(400).json({ success: false, message: 'Invalid authenticated user' });
try {
const { latest, html } = await fetchLatestActiveContractTemplateHtml({ userType, contractType: 'abo' });
const { latest, html } = await fetchLatestActiveContractTemplateHtml({ userType, contractType: 'abo', lang });
if (!latest) {
logger.info('[previewLatestAboForMe] no active template', { userId: targetUserId, userType, contractType: 'abo' });
res.setHeader('Content-Type', 'text/html; charset=utf-8');

View File

@ -2,6 +2,7 @@ const UnitOfWork = require('../../database/UnitOfWork');
const ContractUploadService = require('../../services/contracts/ContractUploadService');
const UserDocumentRepository = require('../../repositories/documents/UserDocumentRepository');
const { logger } = require('../../middleware/logger');
const { normalizeLanguageCode } = require('../../utils/languageUtils');
function normalizeSignature(signatureImage) {
if (!signatureImage) return null;
@ -22,12 +23,28 @@ function normalizeSignature(signatureImage) {
}
}
function resolveRequestedLanguage(contractData, user) {
return normalizeLanguageCode(
contractData?.lang
?? contractData?.language
?? contractData?.locale
?? contractData?.preferred_language
?? contractData?.preferredLanguage
?? user?.preferred_language
?? user?.preferredLanguage
?? user?.language
?? user?.lang
?? ''
) || null;
}
class ContractUploadController {
static async uploadPersonalContract(req, res) {
const userId = req.user.userId;
logger.info(`[ContractUploadController] uploadPersonalContract called for userId: ${userId}`);
const file = req.file; // optional, we now generate from templates when absent
const contractData = req.body.contractData ? JSON.parse(req.body.contractData) : undefined;
const lang = resolveRequestedLanguage(contractData, req.user);
const signatureImage = req.body.signatureImage;
const unitOfWork = new UnitOfWork();
@ -63,6 +80,7 @@ class ContractUploadController {
contractCategory: 'personal',
unitOfWork,
contractData,
lang,
signatureImage: signatureMeta ? signatureMeta.base64 : null,
contract_type,
user_type: 'personal'
@ -110,6 +128,7 @@ class ContractUploadController {
logger.info(`[ContractUploadController] uploadCompanyContract called for userId: ${userId}`);
const file = req.file;
const contractData = req.body.contractData ? JSON.parse(req.body.contractData) : undefined;
const lang = resolveRequestedLanguage(contractData, req.user);
const signatureImage = req.body.signatureImage;
const unitOfWork = new UnitOfWork();
await unitOfWork.start();
@ -143,6 +162,7 @@ class ContractUploadController {
contractCategory: 'company',
unitOfWork,
contractData,
lang,
signatureImage: signatureMeta ? signatureMeta.base64 : null,
contract_type,
user_type: 'company'

View File

@ -846,6 +846,7 @@ const createDatabase = async () => {
high_contrast_mode BOOLEAN DEFAULT FALSE,
two_factor_auth_enabled BOOLEAN DEFAULT FALSE,
account_visibility ENUM('public', 'private') DEFAULT 'public',
preferred_language VARCHAR(16) NULL,
show_email BOOLEAN DEFAULT TRUE,
show_phone BOOLEAN DEFAULT TRUE,
data_export_requested BOOLEAN DEFAULT FALSE,
@ -857,6 +858,8 @@ const createDatabase = async () => {
`);
console.log('✅ User settings table created/verified');
await addColumnIfMissing(connection, 'user_settings', 'preferred_language', `VARCHAR(16) NULL AFTER account_visibility`);
// --- Company Settings (single-row, global invoice / company info) ---
await connection.query(`
CREATE TABLE IF NOT EXISTS company_settings (

View File

@ -2,6 +2,8 @@ const jwt = require('jsonwebtoken');
const { logger } = require('./logger');
const UnitOfWork = require('../database/UnitOfWork');
const UserStatusRepository = require('../repositories/status/UserStatusRepository');
const UserSettingsRepository = require('../repositories/settings/UserSettingsRepository');
const { normalizeLanguageCode } = require('../utils/languageUtils');
async function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
@ -57,10 +59,29 @@ async function authMiddleware(req, res, next) {
const unitOfWork = new UnitOfWork();
await unitOfWork.start();
unitOfWork.registerRepository('status', new UserStatusRepository(unitOfWork));
unitOfWork.registerRepository('settings', new UserSettingsRepository(unitOfWork));
const statusRepo = unitOfWork.getRepository('status');
const settingsRepo = unitOfWork.getRepository('settings');
const userStatus = await statusRepo.getStatusByUserId(normalizedUserId);
const userSettings = await settingsRepo.getSettingsByUserId(normalizedUserId, unitOfWork);
await unitOfWork.commit();
const preferredLanguage = normalizeLanguageCode(
userSettings?.preferred_language
|| userSettings?.preferredLanguage
|| req.user?.preferred_language
|| req.user?.preferredLanguage
|| req.user?.language
|| req.user?.lang
) || null;
if (preferredLanguage) {
req.user.preferred_language = preferredLanguage;
req.user.preferredLanguage = preferredLanguage;
req.user.language = preferredLanguage;
req.user.lang = preferredLanguage;
}
if (userStatus && userStatus.status === 'suspended') {
logger.warn('authMiddleware:user_suspended', {
userId: normalizedUserId,

View File

@ -1,4 +1,5 @@
const db = require('../../database/database');
const { mergeLanguageDescriptors } = require('../../utils/languageUtils');
class I18nPreferencesRepository {
_safeJsonArray(value) {
@ -39,6 +40,25 @@ class I18nPreferencesRepository {
return this._normalizeRow(rows[0]);
}
async listLanguages({ enabledOnly = true } = {}) {
try {
const [rows] = await db.query(
`SELECT language_code AS languageCode,
label,
is_enabled AS isEnabled,
is_custom AS isCustom
FROM i18n_languages
${enabledOnly ? 'WHERE is_enabled = 1' : ''}
ORDER BY language_code`
);
const languages = mergeLanguageDescriptors(rows || []);
return enabledOnly ? languages.filter((entry) => entry.isEnabled !== false) : languages;
} catch (_) {
return mergeLanguageDescriptors([]);
}
}
async upsert({ categories, globalKeys, updatedByUserId } = {}) {
const current = await this.get();

View File

@ -5,10 +5,10 @@ class UserSettingsRepository {
this.unitOfWork = unitOfWork;
}
async getSettingsByUserId(userId) {
async getSettingsByUserId(userId, unitOfWork = null) {
logger.info('UserSettingsRepository.getSettingsByUserId:start', { userId });
try {
const conn = this.unitOfWork.connection;
const conn = unitOfWork ? unitOfWork.connection : this.unitOfWork.connection;
const [rows] = await conn.query('SELECT * FROM user_settings WHERE user_id = ?', [userId]);
logger.info('UserSettingsRepository.getSettingsByUserId:success', { userId, found: rows.length > 0 });
return rows.length > 0 ? rows[0] : null;
@ -32,6 +32,32 @@ class UserSettingsRepository {
throw error;
}
}
async updatePreferredLanguage(userId, preferredLanguage, unitOfWork = null) {
logger.info('UserSettingsRepository.updatePreferredLanguage:start', { userId, preferredLanguage });
try {
const conn = unitOfWork ? unitOfWork.connection : this.unitOfWork.connection;
await conn.query(
`INSERT INTO user_settings (user_id, preferred_language)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE preferred_language = VALUES(preferred_language)` ,
[userId, preferredLanguage || null]
);
const settings = await this.getSettingsByUserId(userId, unitOfWork);
logger.info('UserSettingsRepository.updatePreferredLanguage:success', {
userId,
preferredLanguage: settings?.preferred_language || null,
});
return settings;
} catch (error) {
logger.error('UserSettingsRepository.updatePreferredLanguage:error', {
userId,
preferredLanguage,
error: error.message,
});
throw error;
}
}
}
module.exports = UserSettingsRepository;

View File

@ -1,5 +1,6 @@
const db = require('../../database/database');
const { logger } = require('../../middleware/logger');
const { normalizeLanguageCode } = require('../../utils/languageUtils');
const ALLOWED_USER_TYPES = new Set(['personal', 'company', 'both']);
const ALLOWED_CONTRACT_TYPES = new Set(['contract', 'gdpr', 'abo']);
@ -179,7 +180,7 @@ class DocumentTemplateRepository {
const safeContractType = (normalizedContractType === 'gdpr' || normalizedContractType === 'contract' || normalizedContractType === 'abo')
? normalizedContractType
: 'contract';
const safeLang = (lang === 'en' || lang === 'de') ? lang : 'en';
const safeLang = normalizeLanguageCode(lang) || 'en';
const safeUserType = (user_type === 'personal' || user_type === 'company' || user_type === 'both') ? user_type : 'both';
// user_type overlap rules
@ -222,7 +223,7 @@ class DocumentTemplateRepository {
// Deactivate other active invoice templates for the same language and user_type.
async deactivateOtherActiveInvoices({ excludeId, lang, user_type, tax_mode }, conn) {
logger.info('DocumentTemplateRepository.deactivateOtherActiveInvoices:start', { excludeId, lang, user_type, tax_mode });
const safeLang = (lang === 'en' || lang === 'de') ? lang : 'en';
const safeLang = normalizeLanguageCode(lang) || 'en';
const safeUserType = normalizeTemplateUserType(user_type);
const safeTaxMode = normalizeInvoiceTaxMode(tax_mode);

View File

@ -4,6 +4,7 @@ const router = express.Router();
const authMiddleware = require('../middleware/authMiddleware');
const adminOnly = require('../middleware/adminOnly');
const AdminUserController = require('../controller/admin/AdminUserController');
const UserSettingsController = require('../controller/auth/UserSettingsController');
const DocumentTemplateController = require('../controller/documentTemplate/DocumentTemplateController');
const CoffeeController = require('../controller/admin/CoffeeController');
const CompanySettingsController = require('../controller/admin/CompanySettingsController');
@ -18,6 +19,11 @@ router.put('/admin/users/:id/permissions', authMiddleware, adminOnly, AdminUserC
// PUT /document-templates/:id (moved from routes/documentTemplates.js)
router.put('/document-templates/:id', authMiddleware, upload.single('file'), DocumentTemplateController.updateTemplate);
// User: update persisted UI settings
router.put('/user/settings', authMiddleware, UserSettingsController.updateSettings);
router.put('/settings', authMiddleware, UserSettingsController.updateSettings);
// Admin: update coffee product (supports picture file replacement)
router.put('/admin/coffee/:id', authMiddleware, adminOnly, upload.single('picture'), CoffeeController.update);

View File

@ -0,0 +1,223 @@
const fs = require('fs');
const path = require('path');
const yauzl = require('yauzl');
const ABO_TEMPLATE_DIR = path.join(__dirname, '..', 'templates', 'abo');
const COMPARISONS = [
{
label: 'DE',
previous: path.join(ABO_TEMPLATE_DIR, 'abo-contract-DE.docx'),
next: path.join(ABO_TEMPLATE_DIR, 'new', 'abo-contract-DE-NEW.docx'),
},
{
label: 'SL',
previous: path.join(ABO_TEMPLATE_DIR, 'abo-contract-SL.docx'),
next: path.join(ABO_TEMPLATE_DIR, 'new', 'abo-contract-SL-NEW.docx'),
},
];
function decodeXml(value) {
return String(value || '')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'");
}
function openZip(zipPath) {
return new Promise((resolve, reject) => {
yauzl.open(zipPath, { lazyEntries: true }, (error, zipFile) => {
if (error) {
reject(error);
return;
}
resolve(zipFile);
});
});
}
function readEntry(zipFile, entry) {
return new Promise((resolve, reject) => {
zipFile.openReadStream(entry, (error, stream) => {
if (error) {
reject(error);
return;
}
const chunks = [];
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
stream.on('error', reject);
});
});
}
async function readWordXmlEntries(zipPath) {
const zipFile = await openZip(zipPath);
const documents = [];
return new Promise((resolve, reject) => {
zipFile.readEntry();
zipFile.on('entry', (entry) => {
if (!/^word\/(document|header\d+|footer\d+|footnotes|endnotes)\.xml$/i.test(entry.fileName)) {
zipFile.readEntry();
return;
}
readEntry(zipFile, entry)
.then((xml) => {
documents.push({ fileName: entry.fileName, xml });
zipFile.readEntry();
})
.catch(reject);
});
zipFile.on('end', () => {
zipFile.close();
resolve(documents);
});
zipFile.on('error', reject);
});
}
function xmlToLines(xml) {
const withStructuralBreaks = String(xml || '')
.replace(/<w:tab\b[^>]*\/>/gi, '\t')
.replace(/<w:(?:br|cr)\b[^>]*\/>/gi, '\n')
.replace(/<\/w:p>/gi, '\n')
.replace(/<\/w:tr>/gi, '\n')
.replace(/<\/w:tc>/gi, '\t')
.replace(/<w:t\b[^>]*>([\s\S]*?)<\/w:t>/gi, (_, value) => decodeXml(value))
.replace(/<w:delText\b[^>]*>([\s\S]*?)<\/w:delText>/gi, (_, value) => decodeXml(value));
const textOnly = decodeXml(withStructuralBreaks.replace(/<[^>]+>/g, ' '));
return textOnly
.split(/\r?\n/)
.map((line) => line.replace(/[ \t]+/g, ' ').trim())
.filter((line) => line.length > 0);
}
async function extractDocxLines(docxPath) {
const entries = await readWordXmlEntries(docxPath);
const ordered = entries.sort((left, right) => left.fileName.localeCompare(right.fileName));
return ordered.flatMap((entry) => xmlToLines(entry.xml));
}
function buildLcsMatrix(left, right) {
const matrix = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
for (let leftIndex = left.length - 1; leftIndex >= 0; leftIndex -= 1) {
for (let rightIndex = right.length - 1; rightIndex >= 0; rightIndex -= 1) {
if (left[leftIndex] === right[rightIndex]) {
matrix[leftIndex][rightIndex] = matrix[leftIndex + 1][rightIndex + 1] + 1;
} else {
matrix[leftIndex][rightIndex] = Math.max(
matrix[leftIndex + 1][rightIndex],
matrix[leftIndex][rightIndex + 1],
);
}
}
}
return matrix;
}
function diffLines(previousLines, nextLines) {
const matrix = buildLcsMatrix(previousLines, nextLines);
const changes = [];
let previousIndex = 0;
let nextIndex = 0;
while (previousIndex < previousLines.length && nextIndex < nextLines.length) {
if (previousLines[previousIndex] === nextLines[nextIndex]) {
previousIndex += 1;
nextIndex += 1;
continue;
}
if (matrix[previousIndex + 1][nextIndex] >= matrix[previousIndex][nextIndex + 1]) {
changes.push({ type: 'removed', line: previousIndex + 1, text: previousLines[previousIndex] });
previousIndex += 1;
} else {
changes.push({ type: 'added', line: nextIndex + 1, text: nextLines[nextIndex] });
nextIndex += 1;
}
}
while (previousIndex < previousLines.length) {
changes.push({ type: 'removed', line: previousIndex + 1, text: previousLines[previousIndex] });
previousIndex += 1;
}
while (nextIndex < nextLines.length) {
changes.push({ type: 'added', line: nextIndex + 1, text: nextLines[nextIndex] });
nextIndex += 1;
}
return changes;
}
async function comparePair({ label, previous, next }) {
const previousExists = fs.existsSync(previous);
const nextExists = fs.existsSync(next);
if (!previousExists || !nextExists) {
throw new Error(`${label}: missing input file(s)`);
}
const [previousLines, nextLines] = await Promise.all([
extractDocxLines(previous),
extractDocxLines(next),
]);
const changes = diffLines(previousLines, nextLines);
return {
label,
previous,
next,
previousLineCount: previousLines.length,
nextLineCount: nextLines.length,
changes,
};
}
function printComparison(result) {
console.log(`\n=== ${result.label} ===`);
console.log(`Old: ${path.basename(result.previous)} (${result.previousLineCount} lines)`);
console.log(`New: ${path.basename(result.next)} (${result.nextLineCount} lines)`);
if (!result.changes.length) {
console.log('No textual differences detected.');
return;
}
console.log(`Detected ${result.changes.length} text-level changes:`);
result.changes.forEach((change) => {
const marker = change.type === 'added' ? '+' : '-';
console.log(`${marker} [${change.line}] ${change.text}`);
});
}
async function main() {
const results = [];
for (const comparison of COMPARISONS) {
results.push(await comparePair(comparison));
}
results.forEach(printComparison);
}
main().catch((error) => {
console.error('[compareAboContractDocxVersions] failed:', error?.message || error);
process.exitCode = 1;
});

View File

@ -7,19 +7,20 @@ const DocumentTemplateService = require('../template/DocumentTemplateService');
const MailService = require('../email/MailService');
const pool = require('../../database/database');
const { logger } = require('../../middleware/logger');
const { normalizeLanguageCode } = require('../../utils/languageUtils');
class AboContractService {
constructor() {
this.templatePath = path.join(__dirname, '..', '..', 'templates', 'abo', 'abo-contract-template-new.html');
this.templateDir = path.join(__dirname, '..', '..', 'templates', 'abo');
}
/**
* Load the latest active abo contract template from the contract management system (DB + S3).
* Falls back to the local file if no active template is found.
*/
async _loadTemplate(userType = 'both') {
async _loadTemplate(userType = 'both', lang = null) {
try {
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', 'abo');
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', 'abo', null, lang);
if (latest?.storageKey) {
const command = new GetObjectCommand({
Bucket: process.env.EXOSCALE_BUCKET,
@ -41,8 +42,21 @@ class AboContractService {
} catch (e) {
logger.warn('AboContractService:s3_template_load_failed', { message: e?.message });
}
// Fallback: local file
return fs.readFileSync(this.templatePath, 'utf8');
const normalizedLang = normalizeLanguageCode(lang);
const candidates = [];
if (normalizedLang === 'sl') candidates.push('abo-contract-SL.html');
if (normalizedLang === 'de') candidates.push('abo-contract-DE.html');
if (normalizedLang !== 'de') candidates.push('abo-contract-DE.html');
for (const fileName of candidates) {
const fallbackPath = path.join(this.templateDir, fileName);
if (!fs.existsSync(fallbackPath)) continue;
logger.warn('AboContractService:using_local_fallback_template', { fileName, lang: normalizedLang || null });
return fs.readFileSync(fallbackPath, 'utf8');
}
throw new Error('ABO contract template missing');
}
_escapeHtml(value) {
@ -131,7 +145,7 @@ class AboContractService {
const userType = await this._resolveUserType(actorUser);
let template;
try {
template = await this._loadTemplate(userType);
template = await this._loadTemplate(userType, lang);
} catch (e) {
logger.error('AboContractService:template_missing', { message: e?.message });
throw new Error('ABO contract template missing');

View File

@ -9,6 +9,7 @@ const fs = require('fs');
const path = require('path');
const { logger } = require('../../middleware/logger');
const puppeteer = require('puppeteer');
const { normalizeLanguageCode } = require('../../utils/languageUtils');
// Robust stream/Body -> Buffer reader (supports async iterable, web streams, node streams, buffers)
async function streamToBuffer(body) {
@ -228,7 +229,23 @@ class ContractUploadService {
contract_type = 'contract',
user_type = 'personal'
}) {
logger.info('ContractUploadService.uploadContract:start', { userId, documentType, contractCategory, templateId, lang });
const resolvedLanguage = normalizeLanguageCode(
lang
?? contractData?.lang
?? contractData?.language
?? contractData?.locale
?? contractData?.preferred_language
?? contractData?.preferredLanguage
?? ''
) || null;
logger.info('ContractUploadService.uploadContract:start', {
userId,
documentType,
contractCategory,
templateId,
lang: resolvedLanguage,
});
let pdfBuffer, originalFilename, mimeType, fileSize;
let contractBody;
@ -240,10 +257,12 @@ class ContractUploadService {
try {
// If templateId and lang are provided, fetch HTML template from object storage
if (templateId && lang) {
logger.info('ContractUploadService.uploadContract:fetching_template', { templateId, lang });
if (templateId && resolvedLanguage) {
logger.info('ContractUploadService.uploadContract:fetching_template', { templateId, lang: resolvedLanguage });
const templateMeta = await DocumentTemplateService.getTemplate(templateId);
if (!templateMeta || templateMeta.lang !== lang) throw new Error('Template not found for specified language');
if (!templateMeta || normalizeLanguageCode(templateMeta.lang) !== resolvedLanguage) {
throw new Error('Template not found for specified language');
}
// Fetch HTML from object storage
const s3 = new S3Client({ region: process.env.EXOSCALE_REGION });
const getObj = await s3.send(new GetObjectCommand({
@ -315,7 +334,13 @@ class ContractUploadService {
fileSize = file.size;
} else {
// NEW: auto-generate PDF from latest active template (contract_type) when no file provided
const tmpl = await DocumentTemplateService.getLatestActiveForUserType(user_type, 'contract', contract_type);
const tmpl = await DocumentTemplateService.getLatestActiveForUserType(
user_type,
'contract',
contract_type,
null,
resolvedLanguage
);
if (!tmpl) {
throw new Error(`No active ${contract_type} template found for user type ${user_type}`);
}

View File

@ -8,6 +8,7 @@ const { s3: sharedExoscaleClient } = require('../../utils/exoscaleUploader');
const { logger } = require('../../middleware/logger');
const puppeteer = require('puppeteer');
const pool = require('../../database/database');
const { normalizeLanguageCode } = require('../../utils/languageUtils');
const CoffeeShippingFeeService = require('../subscriptions/CoffeeShippingFeeService');
@ -233,35 +234,52 @@ class InvoiceService {
_selectInvoiceTemplate(templates, { lang = 'en', userType = 'personal', taxMode = 'standard' } = {}) {
if (!Array.isArray(templates) || !templates.length) return null;
const safeLang = lang === 'de' ? 'de' : 'en';
const requestedLanguage = normalizeLanguageCode(lang) || 'en';
const languageCandidates = requestedLanguage === 'en' ? ['en'] : [requestedLanguage, 'en'];
const safeUserType = this._normalizeInvoiceUserType(userType);
const safeTaxMode = this._normalizeInvoiceTemplateTaxMode(taxMode);
const matchesTaxMode = (template, mode) => this._normalizeInvoiceTemplateTaxMode(template?.tax_mode) === mode;
const priorities = [
(template) => template?.lang === safeLang && template?.user_type === safeUserType && matchesTaxMode(template, safeTaxMode),
(template) => template?.lang === safeLang && template?.user_type === safeUserType && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode),
(template) => template?.lang === safeLang && template?.user_type === safeUserType && matchesTaxMode(template, 'both'),
(template) => template?.lang === safeLang && template?.user_type === 'both' && matchesTaxMode(template, safeTaxMode),
(template) => template?.lang === safeLang && template?.user_type === 'both' && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode),
(template) => template?.lang === safeLang && template?.user_type === 'both' && matchesTaxMode(template, 'both'),
(template) => template?.lang === 'en' && template?.user_type === safeUserType && matchesTaxMode(template, safeTaxMode),
(template) => template?.lang === 'en' && template?.user_type === safeUserType && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode),
(template) => template?.lang === 'en' && template?.user_type === safeUserType && matchesTaxMode(template, 'both'),
(template) => template?.lang === 'en' && template?.user_type === 'both' && matchesTaxMode(template, safeTaxMode),
(template) => template?.lang === 'en' && template?.user_type === 'both' && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode),
(template) => template?.lang === 'en' && template?.user_type === 'both' && matchesTaxMode(template, 'both'),
(template) => template?.user_type === safeUserType && matchesTaxMode(template, safeTaxMode),
(template) => template?.user_type === safeUserType && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode),
(template) => template?.user_type === safeUserType && matchesTaxMode(template, 'both'),
(template) => template?.user_type === 'both' && matchesTaxMode(template, safeTaxMode),
(template) => template?.user_type === 'both' && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode),
(template) => template?.user_type === 'both' && matchesTaxMode(template, 'both'),
() => true,
];
for (const matches of priorities) {
const selected = templates.find(matches);
if (selected) return selected;
const matchesLanguage = (template, candidateLanguage) => normalizeLanguageCode(template?.lang) === candidateLanguage;
const userTypeCandidates = [safeUserType, 'both'];
for (const candidateLanguage of languageCandidates) {
for (const candidateUserType of userTypeCandidates) {
const exact = templates.find((template) => (
matchesLanguage(template, candidateLanguage)
&& template?.user_type === candidateUserType
&& matchesTaxMode(template, safeTaxMode)
));
if (exact) return exact;
const legacyReverseCharge = templates.find((template) => (
matchesLanguage(template, candidateLanguage)
&& template?.user_type === candidateUserType
&& this._matchesLegacyReverseChargeTemplate(template, safeTaxMode)
));
if (legacyReverseCharge) return legacyReverseCharge;
const bothTaxModes = templates.find((template) => (
matchesLanguage(template, candidateLanguage)
&& template?.user_type === candidateUserType
&& matchesTaxMode(template, 'both')
));
if (bothTaxModes) return bothTaxModes;
}
}
for (const candidateUserType of userTypeCandidates) {
const exact = templates.find((template) => template?.user_type === candidateUserType && matchesTaxMode(template, safeTaxMode));
if (exact) return exact;
const legacyReverseCharge = templates.find((template) => (
template?.user_type === candidateUserType
&& this._matchesLegacyReverseChargeTemplate(template, safeTaxMode)
));
if (legacyReverseCharge) return legacyReverseCharge;
const bothTaxModes = templates.find((template) => template?.user_type === candidateUserType && matchesTaxMode(template, 'both'));
if (bothTaxModes) return bothTaxModes;
}
return templates[0];

View File

@ -1,6 +1,27 @@
const db = require('../../database/database');
const SYSTEM_POOL_NAMES = ['ABO 60', 'ABO 120', 'Business', 'Gigantea', 'Core'];
const ABO_60_POOL_NAME = 'ABO 60';
const ABO_120_POOL_NAME = 'ABO 120';
function resolveAboPoolName(totalPacks) {
const safeTotalPacks = Number(totalPacks || 0);
if (!Number.isFinite(safeTotalPacks) || safeTotalPacks <= 0) return ABO_60_POOL_NAME;
return safeTotalPacks <= 11 ? ABO_60_POOL_NAME : ABO_120_POOL_NAME;
}
function selectPoolsForAbonement(pools, totalPacks) {
if (!Array.isArray(pools) || !pools.length) return [];
const targetAboPoolName = resolveAboPoolName(totalPacks);
return pools.filter((pool) => {
const poolName = String(pool?.pool_name || '').trim();
if (poolName === ABO_60_POOL_NAME || poolName === ABO_120_POOL_NAME) {
return poolName === targetAboPoolName;
}
return true;
});
}
function toTwo(value) {
return Number(Number(value || 0).toFixed(2));
@ -11,6 +32,28 @@ function toFour(value) {
}
class PoolInflowService {
async removeStaleAboPoolInflowsForInvoice({ conn, invoiceId, selectedAboPoolName }) {
if (!conn || !invoiceId || !selectedAboPoolName) return 0;
const [res] = await conn.query(
`DELETE pi
FROM pool_inflows pi
INNER JOIN pools p ON p.id = pi.pool_id
WHERE pi.invoice_id = ?
AND pi.event_type = 'invoice_paid'
AND p.pool_name IN (?, ?)
AND p.pool_name <> ?`,
[
Number(invoiceId),
ABO_60_POOL_NAME,
ABO_120_POOL_NAME,
selectedAboPoolName,
]
);
return Number(res?.affectedRows || 0);
}
async upsertCapsuleSalesForInvoice({ conn, invoiceId, abonementId, paidAtDate, currency, byCoffee }) {
const entries = Array.from(byCoffee.entries());
for (const [coffeeId, capsulesCountRaw] of entries) {
@ -126,13 +169,23 @@ class PoolInflowService {
? breakdown
.map((line) => ({
coffeeId: Number(line?.coffee_table_id),
packsCount: Number(line?.packs || 0),
capsulesCount: Number(line?.packs || 0) * 10,
}))
.filter((line) => Number.isFinite(line.coffeeId) && line.coffeeId > 0 && Number.isFinite(line.capsulesCount) && line.capsulesCount > 0)
.filter((line) => (
Number.isFinite(line.coffeeId)
&& line.coffeeId > 0
&& Number.isFinite(line.packsCount)
&& line.packsCount > 0
&& Number.isFinite(line.capsulesCount)
&& line.capsulesCount > 0
))
: [];
if (!normalizedLines.length) return { ok: false, reason: 'no_breakdown_lines', invoice, abonementId };
const totalPacks = normalizedLines.reduce((sum, line) => sum + Number(line.packsCount || 0), 0);
const byCoffee = new Map();
for (const line of normalizedLines) {
byCoffee.set(line.coffeeId, (byCoffee.get(line.coffeeId) || 0) + line.capsulesCount);
@ -157,6 +210,11 @@ class PoolInflowService {
return { ok: false, reason: 'no_active_system_pools', invoice, abonementId, normalizedLines };
}
const selectedPools = selectPoolsForAbonement(pools, totalPacks);
if (!selectedPools.length) {
return { ok: false, reason: 'no_matching_system_pools', invoice, abonementId, normalizedLines, totalPacks };
}
return {
ok: true,
reason: 'ok',
@ -164,8 +222,10 @@ class PoolInflowService {
abonementId,
paidAtDate,
byCoffee,
pools,
pools: selectedPools,
normalizedLines,
totalPacks,
selectedAboPoolName: resolveAboPoolName(totalPacks),
currency: invoice.currency || abonement.currency || 'EUR',
};
}
@ -182,12 +242,20 @@ class PoolInflowService {
const byCoffee = analysis.byCoffee;
const pools = analysis.pools;
const currency = analysis.currency;
const selectedAboPoolName = analysis.selectedAboPoolName;
const conn = await db.getConnection();
let inserted = 0;
try {
let alreadyExists = 0;
let staleRemoved = 0;
await conn.beginTransaction();
staleRemoved = await this.removeStaleAboPoolInflowsForInvoice({
conn,
invoiceId: normalizedInvoiceId,
selectedAboPoolName,
});
await this.upsertCapsuleSalesForInvoice({
conn,
invoiceId: normalizedInvoiceId,
@ -221,6 +289,8 @@ class PoolInflowService {
paid_at: paidAtDate,
booking_basis: 'gross',
compatibility_note: 'gross values stored in existing net columns',
total_packs: analysis.totalPacks,
selected_abo_pool: resolveAboPoolName(analysis.totalPacks),
member_multiplier: memberMultiplier,
core_members_count: pool.pool_name === 'Core' ? memberMultiplier : null,
};
@ -250,7 +320,13 @@ class PoolInflowService {
}
await conn.commit();
return { inserted, alreadyExists, skipped: Math.max(0, totalCandidates - inserted - alreadyExists), reason: 'ok' };
return {
inserted,
alreadyExists,
staleRemoved,
skipped: Math.max(0, totalCandidates - inserted - alreadyExists),
reason: 'ok',
};
} catch (err) {
await conn.rollback();
throw err;

View File

@ -1,6 +1,7 @@
const DocumentTemplateRepository = require('../../repositories/template/DocumentTemplateRepository');
const UnitOfWork = require('../../database/UnitOfWork');
const { logger } = require('../../middleware/logger');
const { normalizeLanguageCode } = require('../../utils/languageUtils');
const ALLOWED_USER_TYPES = new Set(['personal', 'company', 'both']);
const ALLOWED_CONTRACT_TYPES = new Set(['contract', 'gdpr', 'abo']);
@ -20,6 +21,23 @@ function normalizeInvoiceTaxMode(value) {
return ALLOWED_INVOICE_TAX_MODES.has(normalized) ? normalized : 'both';
}
function selectTemplateByLanguage(list, requestedLanguage) {
if (!Array.isArray(list) || !list.length) return null;
const preferredLanguage = normalizeLanguageCode(requestedLanguage);
const candidates = [];
if (preferredLanguage) candidates.push(preferredLanguage);
if (!candidates.includes('en')) candidates.push('en');
for (const candidate of candidates) {
const match = list.find((template) => normalizeLanguageCode(template?.lang) === candidate);
if (match) return match;
}
return list[0];
}
class DocumentTemplateService {
async listTemplates() {
logger.info('DocumentTemplateService.listTemplates:start');
@ -44,7 +62,7 @@ class DocumentTemplateService {
const rawContractType = (data.contract_type || data.contractType);
const normalizedContractType = normalizeContractType(rawContractType);
const user_type = normalizeTemplateUserType(data.user_type || data.userType);
const contract_type = (data.type === 'contract' && ALLOWED_CONTRACT_TYPES.includes(normalizedContractType))
const contract_type = (data.type === 'contract' && ALLOWED_CONTRACT_TYPES.has(normalizedContractType))
? normalizedContractType
: (data.type === 'contract' ? 'contract' : null);
const tax_mode = data.type === 'invoice'
@ -107,7 +125,7 @@ class DocumentTemplateService {
}
const nextType = data.type !== undefined ? data.type : current.type;
const contract_type = nextType === 'contract'
? (ALLOWED_CONTRACT_TYPES.includes(normalizeContractType(data.contract_type || data.contractType || current.contract_type))
? (ALLOWED_CONTRACT_TYPES.has(normalizeContractType(data.contract_type || data.contractType || current.contract_type))
? normalizeContractType(data.contract_type || data.contractType || current.contract_type)
: 'contract')
: null;
@ -200,7 +218,7 @@ class DocumentTemplateService {
const effectiveType = data.type || previous.type;
const effectiveUserType = normalizeTemplateUserType(data.user_type || data.userType || previous.user_type);
const effectiveContractType = effectiveType === 'contract'
? (ALLOWED_CONTRACT_TYPES.includes(normalizeContractType(data.contract_type || data.contractType || previous.contract_type))
? (ALLOWED_CONTRACT_TYPES.has(normalizeContractType(data.contract_type || data.contractType || previous.contract_type))
? normalizeContractType(data.contract_type || data.contractType || previous.contract_type)
: 'contract')
: null;
@ -267,11 +285,11 @@ class DocumentTemplateService {
}
// Convenience: return the most recent active template for a user type (by createdAt desc)
async getLatestActiveForUserType(userType, templateType = 'contract', contractType = null, taxMode = null) {
logger.info('DocumentTemplateService.getLatestActiveForUserType:start', { userType, templateType, contractType, taxMode });
async getLatestActiveForUserType(userType, templateType = 'contract', contractType = null, taxMode = null, lang = null) {
logger.info('DocumentTemplateService.getLatestActiveForUserType:start', { userType, templateType, contractType, taxMode, lang });
try {
const list = await DocumentTemplateRepository.findActiveByUserType(userType, templateType, contractType, taxMode);
const latest = Array.isArray(list) && list.length ? list[0] : null;
const latest = selectTemplateByLanguage(list, lang);
logger.info('DocumentTemplateService.getLatestActiveForUserType:result', { found: !!latest, id: latest?.id });
return latest;
} catch (error) {

View File

@ -371,7 +371,7 @@
<p>Preis ohne ABO ( BRUTTO )</p>
</td>
<td>
<p><strong>2.97,-€</strong></p>
<p><strong>1,59,-€</strong></p>
</td>
<td>
<p><strong>1,97,-€</strong></p>
@ -385,7 +385,7 @@
<p>Preis mit ABO ( BRUTTO )</p>
</td>
<td>
<p><strong>1.22,-€</strong></p>
<p><strong>1,20,-€</strong></p>
</td>
<td>
<p><strong>0,97,-€</strong></p>
@ -514,48 +514,6 @@
</td>
<td></td>
</tr>
<tr>
<td>
<p>Mighty Matcha Tea</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Coffee Lungo Crema 7</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Coffee Crema 10</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Espresso 12</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Espresso Intenso 13</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Ristretto 14</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Decaffeinato Lungo</p>
</td>
<td></td>
</tr>
</table>
<p>Bei Angabe einer automatischen Wiederbestellung, gemäß den Regelungen in nachstehendem <strong>Punkt 3</strong>, erhält der Kunde in regelmäßigen Abständen, <strong>BEGINNEND AM </strong>(Unterzeichnung des Vertrages) vorstehend eingetragene BIO Kaffee-Teemenge für die Dauer des Vertrages oder bis zum Widerruf der automatischen Wiederbestellung. Der BIO Kaffee-Tee wird automatisch <strong>im Abstand von</strong> (zutreffendes bitte ankreuzen)</p>
<p> <input checked="checked" type="checkbox" /> 1 Monat </p>
@ -564,7 +522,7 @@
<p><strong>Bitte wählen Sie Ihre Zahlungsart: </strong></p>
<div class="box">
<div class="checkline">
<span class="check"><span class="checkbox {{paymentSepaClass}}"></span> <strong>Sepa</strong></span>
<span class="check"><span class="checkbox {{paymentSepaClass}}"></span> <strong>SEPA</strong></span>
</div>
<div class="checkline">
<span class="check"><span class="checkbox {{invoiceByEmailClass}}"></span> <strong>Bitte senden Sie mir meine Rechnung per E-Mail zu!</strong></span>
@ -587,7 +545,7 @@
</ol>
<p><strong>§ 4 Verpflichtung zur Verwendung von Produkten der Profit Planet GmbH Vertragsstrafe/Liefervereinbarung</strong></p>
<ol>
<li>Der Kunde verpflichtet sich, während der Laufzeit des Vertrags auf den von Profit Planet GmbH zur Verfügung gestellten Kaffeemaschinen ausschließlich Produkte <a id="_Hlk74558723"></a>der <a id="_Hlk73956971"></a>Profit Planet GmbH ,,LANATURA, zu verwenden und einzusetzen, maximal jedoch für einen Zeitraum von drei (3) und/oder fünf (5) Jahren ab Vertragsschluss.</li>
<li>Der Kunde verpflichtet sich, während der Laufzeit des Vertrags auf den von Profit Planet GmbH zur Verfügung gestellten Kaffeemaschinen ausschließlich Produkte <a id="_Hlk74558723"></a>der <a id="_Hlk73956971"></a>Profit Planet GmbH "LaNatura", zu verwenden und einzusetzen, maximal jedoch für einen Zeitraum von drei (3) und/oder fünf (5) Jahren ab Vertragsschluss.</li>
<li>Der Kaffee wird wiederkehrend zugestellt laut Bestellung.</li>
<li>Verstößt der Kunde gegen seine Verpflichtung aus Abs. 1, so ist die Profit Planet GmbH zur außerordentlichen fristlosen Kündigung aus wichtigem Grund berechtigt. Darüber hinaus vereinbaren die Parteien die Zahlung einer verschuldensunabhängigen Vertragsstrafe durch den Kunden an die Profit Planet GmbH in angemessener Höhe, wobei die Profit Planet GmbH die Höhe nach billigem Ermessen bestimmen wird und die Angemessenheit der Vertragsstrafe im Streitfall von dem zuständigen Gericht überprüft werden kann. Die Geltendmachung weiteren Schadensersatzes bleibt vorbehalten. </li>
</ol>
@ -639,7 +597,7 @@
<p><strong>Allgemeine Geschäftsbedingungen Kaffee-Service</strong></p>
<p><strong>§ 1 Geltungsbereich, Form</strong></p>
<ol>
<li>Die vorliegenden Allgemeinen Geschäftsbedingungen (AGB) gelten für alle unsere Geschäftsbeziehungen zwischen unseren Kunden und uns, der Profit Planet GmbH, Liebenauer Hauptstraße 82c, 8041 Graz, Österreich. Die AGB gelten gegenüber Verbrauchern und Unternehmern; zwingende Verbraucherschutzbestimmungen (insbesondere nach KSchG und FAGG) gehen im Zweifel diesen AGB vor. </li>
<li>Die vorliegenden Allgemeinen Geschäftsbedingungen (AGB) gelten für alle unsere Geschäftsbeziehungen zwischen unseren Kunden und uns, der Profit Planet GmbH, Kärntner Straße 227, 8053 Graz, Österreich. Die AGB gelten gegenüber Verbrauchern und Unternehmern; zwingende Verbraucherschutzbestimmungen (insbesondere nach KSchG und FAGG) gehen im Zweifel diesen AGB vor. </li>
<li>Die AGB gelten insbesondere für Verträge über den Verkauf und/oder die Lieferung beweglicher Sachen („Ware“), ohne Rücksicht darauf, ob wir die Ware selbst herstellen oder bei Zulieferern einkaufen. Sofern nichts anderes vereinbart, gelten die AGB in der zum Zeitpunkt der Bestellung des Käufers gültigen bzw. jedenfalls in der ihm zuletzt in Textform mitgeteilten Fassung als Rahmenvereinbarung auch für gleichartige künftige Verträge, ohne dass wir in jedem Einzelfall wieder auf sie hinweisen müssten.</li>
<li>Unsere AGB gelten ausschließlich. Abweichende, entgegenstehende oder ergänzende Allgemeine Geschäftsbedingungen des Käufers werden nur dann und insoweit Vertragsbestandteil, als wir ihrer Geltung ausdrücklich zugestimmt haben. Dieses Zustimmungserfordernis gilt in jedem Fall, beispielsweise auch dann, wenn wir in Kenntnis der AGB des Käufers die Lieferung an ihn vorbehaltlos ausführen.</li>
<li>Im Einzelfall getroffene, individuelle Vereinbarungen mit dem Käufer (einschließlich Nebenabreden, Ergänzungen und Änderungen) haben in jedem Fall Vorrang vor diesen AGB. Für den Inhalt derartiger Vereinbarungen ist, vorbehaltlich des Gegenbeweises, ein schriftlicher Vertrag bzw. unsere schriftliche Bestätigung maßgebend.</li>
@ -717,14 +675,14 @@
<p><a id="page4"></a></p>
<p><strong>Informationen für Verbraucher über das Rücktrittsrecht (Widerrufsrecht)</strong><br /></p>
<p><em>Widerrufsrecht:</em> Sie haben das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen. Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag, an dem Sie (oder ein von Ihnen benannter Dritter, der nicht der Beförderer ist) die erste Ware im Rahmen dieses Vertrages in Besitz genommen haben. Bei einem Vertrag über Dienstleistungen (z.B. Miete einer Kaffeemaschine) beginnt die Widerrufsfrist mit dem Tag des Vertragsabschlusses.</p>
<p>Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (Profit Planet GmbH, Liebenauer Hauptstraße 82c, 8041 Graz, E-Mail: office@profit-planet.com) mittels einer eindeutigen Erklärung (z.B. ein mit der Post versandter Brief oder E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. Sie können dafür das unten angefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist. Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden.</p>
<p>Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (Profit Planet GmbH, Kärntner Straße 227, 8053 Graz, E-Mail: office@profit-planet.com) mittels einer eindeutigen Erklärung (z.B. ein mit der Post versandter Brief oder E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. Sie können dafür das unten angefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist. Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden.</p>
<p><em>Folgen des Widerrufs:</em> Wenn Sie diesen Vertrag widerrufen, haben wir Ihnen alle Zahlungen, die wir von Ihnen erhalten haben einschließlich etwaiger Lieferkosten (mit Ausnahme jener zusätzlichen Kosten, die sich daraus ergeben, dass Sie eine andere Art der Lieferung, als die von uns angebotene günstigste Standardlieferung gewählt haben)  unverzüglich und spätestens binnen vierzehn Tagen ab dem Tag zurückzuzahlen, an dem die Mitteilung über Ihren Widerruf bei uns eingegangen ist. Für diese Rückzahlung verwenden wir dasselbe Zahlungsmittel, das Sie bei der ursprünglichen Transaktion eingesetzt haben, es sei denn, mit Ihnen wurde ausdrücklich etwas anderes vereinbart. Ihnen werden wegen dieser Rückzahlung keine Entgelte berechnet.</p>
<p>Handelt es sich bei dem widerrufenen Vertrag um einen Kaufvertrag über Waren, können wir die Rückzahlung verweigern, bis wir die Waren wieder zurückerhalten haben oder Sie den Nachweis erbracht haben, dass Sie die Waren abgesandt haben  je nachdem, welcher Zeitpunkt früher eintritt. Sie haben die Waren in diesem Fall unverzüglich und in jedem Fall spätestens binnen vierzehn Tagen ab dem Tag, an dem Sie uns über den Widerruf dieses Vertrags unterrichten, an uns zurückzusenden oder zu übergeben. Die Frist ist gewahrt, wenn Sie die Waren vor Ablauf der Frist von vierzehn Tagen absenden. Sie tragen die unmittelbaren Kosten der Rücksendung der Waren.</p>
<p>Sie müssen für einen etwaigen Wertverlust der Waren nur aufkommen, wenn dieser Wertverlust auf einen zur Prüfung der Beschaffenheit, Eigenschaften und Funktionsweise der Waren nicht notwendigen Umgang mit ihnen zurückzuführen ist.</p>
<p>Haben Sie verlangt, dass eine Dienstleistung (oder die regelmäßige Lieferung von Waren) während der Widerrufsfrist beginnen soll, so haben Sie uns einen angemessenen Betrag zu zahlen, der dem Anteil, der bis zu dem Zeitpunkt der Widerrufsausübung bereits erbrachten Leistungen im Vergleich zum Gesamtumfang der im Vertrag vorgesehenen Leistungen entspricht.</p>
<p><strong>Muster-Widerrufsformular</strong><br /></p>
<p>(Wenn Sie den Vertrag widerrufen wollen, können Sie dieses Formular ausfüllen und an uns zurücksenden.)</p>
<p> An Profit Planet GmbH, Liebenauer Hauptstraße 82c, 8041 Graz, E-Mail: <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>:</p>
<p> An Profit Planet GmbH, Kärntner Straße 227, 8053 Graz, E-Mail: <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>:</p>
<p> Hiermit widerrufe(n) ich/wir (<strong>)</strong></p>
<p><strong>den von mir/uns (</strong>) abgeschlossenen Vertrag über den Kauf der folgenden Ware(n)/die Erbringung der folgenden Dienstleistung</p>
<p> Bestellt am (<strong>) / erhalten am (</strong>)</p>

View File

@ -244,7 +244,7 @@
<div class="header">
<div class="brand">
<p class="title">ABO pogodba</p>
<p class="subtitle">Ponudba za sklenitev kupoprodajne/najemne pogodbe za kapsulo za kavno postrežbo</p>
<p class="subtitle">Ponudba za sklenitev kupno-najemne pogodbe za kavni servis - kapsule</p>
</div>
<div class="company">
<div><strong>PROFIT PLANET GMBH</strong></div>
@ -259,7 +259,7 @@
<div style="display:flex; justify-content: space-between; gap: 12px; align-items: flex-end;">
<div>
<h1>do</h1>
<h1>za</h1>
<p class="muted" style="margin: 4px 0 0;">Prosimo, izpolnite vsa polja in označite ustrezne možnosti.</p>
</div>
<div class="metaGrid">
@ -283,7 +283,7 @@
<div class="box">
<div class="row">
<div class="label">IME PODRUŽNICE</div>
<div class="label">IME AFFILIATE</div>
<div class="value"><span class="fill wide"></span></div>
</div>
<div class="row">
@ -302,7 +302,7 @@
</div>
<div class="row"><div class="label">Ime in priimek</div><div class="value"><span class="fill wide">{{shippingFullName}}</span></div></div>
<div class="row"><div class="label">Naslov</div><div class="value"><span class="fill full">{{shippingStreet}}</span></div></div>
<div class="row"><div class="label">Poštna številka / Lokacija</div><div class="value"><span class="fill">{{shippingPostalCode}}</span> <span class="fill wide">{{shippingCity}}</span></div></div>
<div class="row"><div class="label">Poštna številka / Kraj</div><div class="value"><span class="fill">{{shippingPostalCode}}</span> <span class="fill wide">{{shippingCity}}</span></div></div>
</div>
<div>
<div class="row"><div class="label">Telefonska številka</div><div class="value"><span class="fill">{{shippingPhone}}</span></div></div>
@ -312,7 +312,7 @@
</div>
<div class="checkline" style="margin-top: 6px;">
<span class="check"><strong>Naslov za izstavitev računa:</strong></span>
<span class="check">{{invoiceSameAsShippingMark}} kot na primer naslov za dostavo</span>
<span class="check">{{invoiceSameAsShippingMark}} enak kot za dostavo</span>
</div>
</div>
@ -326,7 +326,7 @@
</div>
<div class="row"><div class="label">Ime in priimek</div><div class="value"><span class="fill wide">{{invoiceFullName}}</span></div></div>
<div class="row"><div class="label">Naslov</div><div class="value"><span class="fill full">{{invoiceStreet}}</span></div></div>
<div class="row"><div class="label">Poštna številka / Lokacija</div><div class="value"><span class="fill">{{invoicePostalCode}}</span> <span class="fill wide">{{invoiceCity}}</span></div></div>
<div class="row"><div class="label">Poštna številka / Kraj</div><div class="value"><span class="fill">{{invoicePostalCode}}</span> <span class="fill wide">{{invoiceCity}}</span></div></div>
</div>
<div>
<div class="row"><div class="label">Telefonska številka</div><div class="value"><span class="fill">{{invoicePhone}}</span></div></div>
@ -339,292 +339,146 @@
<span class="check"><span class="checkbox {{atuCheckedClass}}"></span> ATU <span class="fill">{{atuNumber}}</span></span>
</div>
</div>
<p><strong>Prosimo, označite ustrezno polje:</strong></p>
<div class="box">
<div class="checkline" style="align-items:flex-start;">
<span class="check"><span class="checkbox {{entrepreneurClass}}"></span></span>
<span>Stranka/kupec sklepa zadevni pravni posel kot podjetnik v smislu 1. odstavka 1. člena Zakona o varstvu potrošnikov (KSchG), kar pomeni, da je posel del poslovanja njegovega podjetja.</span>
<span>Stranka / kupec sklepa predmetni pravni posel kot podjetnik v smislu § 1 odst. 1 tč. 1 KSchG, kar pomeni, da posel spada v dejavnost njegovega podjetja.</span>
</div>
<div class="checkline" style="align-items:flex-start;">
<span class="check"><span class="checkbox {{consumerClass}}"></span></span>
<span>Kupec/stranka sklepa ta pravni posel kot potrošnik v smislu 2. točke 1. odstavka 1. člena Zakona o varstvu potrošnikov (KSchG).</span>
<span>Stranka/kupec sklepa predmetni pravni posel kot potrošnik v smislu § 1 odst. 1 tč. 2 KSchG.</span>
</div>
</div>
<p><strong>PONUDBE</strong></p>
<p><strong>Minimalna količina naročila </strong>Za ekološko kavo, ekološki čaj in ekološki kakav je količina naročila 60 kapsul.</p>
<p>Cene in pogoji so v skladu z veljavno tarifo PROFIT PLANET GMBH.</p>
<p><strong>PONUDBA</strong></p>
<p><strong>Minimalna količina naročila</strong> za BIO kavo, BIO čaj in BIO kakav znaša pri vsakem naročilu <strong>60 kapsul</strong>.</p>
<p>Cene in pogoji veljajo v skladu z veljavnim cenikom družbe <strong>PROFIT PLANET GMBH</strong>.</p>
<table>
<tr>
<td></td>
<td>
<p><strong>Vitamin Kapsul</strong></p>
</td>
<td>
<p><strong>Matcha &amp; Dubai Kapsul</strong></p>
</td>
<td>
<p><strong>Basic Kapsul</strong></p>
</td>
<td><p><strong>VITAMIN KAFFEE</strong></p></td>
<td><p><strong>MATCHA &amp; DUBAI</strong></p></td>
<td><p><strong>BASIC KAFFEE</strong></p></td>
</tr>
<tr>
<td>
<p>Stranka brez naročnine bruto</p>
</td>
<td>
<p><strong>2,97 €</strong></p>
</td>
<td>
<p><strong>1,97 €</strong></p>
</td>
<td>
<p><strong>0,99 €</strong></p>
</td>
<td><p>Stranka brez naročnine (BRUTO)</p></td>
<td><p><strong>1,59,-€</strong></p></td>
<td><p><strong>1,97,-€</strong></p></td>
<td><p><strong>0,99,-€</strong></p></td>
</tr>
<tr>
<td>
<p>Stranka z naročnino bruto</p>
</td>
<td>
<p><strong>1,22 €</strong></p>
</td>
<td>
<p><strong>0,97 €</strong></p>
</td>
<td>
<p><strong>0,69 €</strong></p>
</td>
<td><p>Stranka z naročnino (BRUTO)</p></td>
<td><p><strong>1,20,-€</strong></p></td>
<td><p><strong>0,97,-€</strong></p></td>
<td><p><strong>0,69,-€</strong></p></td>
</tr>
<tr>
<td>
<p>Poslovno brez davka neto</p>
</td>
<td>
<p><strong>1,00 €</strong></p>
</td>
<td>
<p><strong>0,79 €</strong></p>
</td>
<td>
<p><strong>0,56 €</strong></p>
</td>
<td><p>Business ABO ( NETO )</p></td>
<td><p><strong>1,00,-€</strong></p></td>
<td><p><strong>0,79,-€</strong></p></td>
<td><p><strong>0,56,-€</strong></p></td>
</tr>
</table>
<table>
<tr>
<td>
<p><strong>Superživilska kava</strong></p>
</td>
<td>
<p><strong>___________KAPSUL</strong></p>
</td>
</tr>
<tr>
<td>
<p>Beauty Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Focus Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Focus Superfood Cafe Espresso</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Fitness Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Fitnes Superfood Cafe Espresso</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Sleep Well Superfood Cafe Brez kofeina Lungo </p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Glow Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Kavarna Glow Superfood Espresso</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Glow Collagen Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Kavarna z gobami dolgoživosti Espresso</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Longevity Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Anti Aging Superfood Cafe Lungo Forte Crema</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Espresso s superhrano proti staranju</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Lepotilni Superfood Cafe Espresso Pink Edition</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Beauty Superfood Cafe Espresso Violet Edition</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Kava v Dubaju v čokoladnem slogu</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Močan čaj Matcha</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Kava Lungo Crema 7</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Kava Crema 10</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Espresso 12</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Espresso Intenso 13</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Ristretto 14</p>
</td>
<td></td>
</tr>
<tr>
<td>
<p>Brez kofeina Lungo</p>
</td>
<td></td>
<td><p><strong>Superživilska kava</strong></p></td>
<td><p><strong>___________KAPSUL</strong></p></td>
</tr>
<tr><td><p>Beauty Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Focus Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Focus Superfood Cafe Espresso</p></td><td></td></tr>
<tr><td><p>Fitness Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Fitnes Superfood Cafe Espresso</p></td><td></td></tr>
<tr><td><p>Sleep Well Superfood Cafe Brez kofeina Lungo</p></td><td></td></tr>
<tr><td><p>Glow Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Kavarna Glow Superfood Espresso</p></td><td></td></tr>
<tr><td><p>Glow Collagen Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Kavarna z gobami dolgoživosti Espresso</p></td><td></td></tr>
<tr><td><p>Longevity Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Anti Aging Superfood Cafe Lungo Forte Crema</p></td><td></td></tr>
<tr><td><p>Espresso s superhrano proti staranju</p></td><td></td></tr>
<tr><td><p>Lepotilni Superfood Cafe Espresso Pink Edition</p></td><td></td></tr>
<tr><td><p>Beauty Superfood Cafe Espresso Violet Edition</p></td><td></td></tr>
<tr><td><p>Kava v Dubaju v čokoladnem slogu</p></td><td></td></tr>
</table>
<p>Z določitvijo samodejnega ponovnega naročila v skladu z določbami v točki 3 spodaj bo stranka prej omenjeno količino ekološke kave in čaja prejemala v rednih intervalih, začenši z (datum podpisa pogodbe), za čas trajanja pogodbe ali do preklica samodejnega ponovnega naročila. Ekološka kava in čaj bosta dostavljena samodejno v intervalih (prosimo, označite ustrezno).</p>
<p>Pri navedbi avtomatskega ponovnega naročila v skladu z določbami iz spodnje točke 3 bo stranka v rednih časovnih intervalih, <strong>začenši z dnem</strong> (podpis pogodbe), prejemala zgoraj navedeno količino BIO kave/čaja za čas trajanja pogodbe oziroma do preklica avtomatskega ponovnega naročila. BIO kava/čaj se bo samodejno dobavljala v naslednjem intervalu (ustrezno označite):</p>
<p><input checked="checked" type="checkbox" /> 1 mesec</p>
<p>fakturirano in dostavljeno stranki v treh do petih delovnih dneh.</p>
<p> </p>
<p><strong>Izberite način plačila:</strong></p>
<p>in bo obračunana ter dostavljena stranki v roku treh do petih delovnih dni.</p>
<p><strong>Prosimo, izberite način plačila:</strong></p>
<div class="box">
<div class="checkline">
<span class="check"><span class="checkbox {{paymentSepaClass}}"></span> <strong>Sepa</strong></span>
<span class="check"><span class="checkbox {{paymentSepaClass}}"></span> <strong>SEPA</strong></span>
</div>
<div class="checkline">
<span class="check"><span class="checkbox {{invoiceByEmailClass}}"></span> <strong>Prosim, pošljite mi račun po e-pošti!</strong></span>
</div>
</div>
<p><strong>§ 1 Predmet pogodbe / Veljavnost splošnih pogojev poslovanja</strong></p>
<ol>
<li><a id="_Hlk68781926"></a>Splošni pogoji poslovanja (GTC, glej spodaj) družbe Profit Planet GmbH so sestavni in zavezujoči del te pogodbe. Kakršni koli drugačni, nasprotujoči si ali dopolnilni pogoji poslovanja stranke postanejo del pogodbe le, če in v obsegu, v katerem se je družba Profit Planet GmbH izrecno strinjala z njihovo veljavnostjo. Ta zahteva po izrecnem soglasju velja v vseh primerih, vključno z na primer takrat, ko družba Profit Planet GmbH, poznajoč pogoje poslovanja stranke, brez pridržkov dostavi blago stranki.<a id="_Hlk73957015"></a></li>
<li>Splošni pogoji poslovanja (SPP, glej spodaj) družbe Profit Planet GmbH so zavezujoč sestavni del te pogodbe. Odstopajoči, nasprotujoči si ali dopolnilni splošni pogoji poslovanja stranke postanejo del pogodbe samo v primeru in v obsegu, v katerem je družba Profit Planet GmbH izrecno soglašala z njihovo veljavnostjo. Ta zahteva po soglasju velja v vsakem primeru, na primer tudi takrat, kadar družba Profit Planet GmbH ob poznavanju SPP stranke izvede dobavo brez pridržkov.</li>
</ol>
<p><strong>§ 2 Trajanje pogodbe/Plačilo z direktno obremenitvijo/Izstavitev računa</strong></p>
<p><strong>§ 2 Trajanje pogodbe / Plačilo z direktno obremenitvijo / Obračun</strong></p>
<ol>
<li>ThePogodba ima rok trajanja 36 mesecev.</li>
<li>Če je stranka podjetnik, se pogodba po preteku začetnih 36 mesecev podaljšuje za 3 mesece, razen če jo katera koli stranka ne odpove s štiritedenskim odpovednim rokom pred iztekom pogodbe.</li>
<li>Če je stranka potrošnik, bo družba Profit Planet GmbH potrošnika pisno obvestila o skorajšnji prekinitvi pogodbe in samodejnem podaljšanju najpozneje tri mesece in ne prej kot pet mesecev pred iztekom pogodbe. Če tako obvestilo ni podano, pogodba preneha veljati ob koncu prvotnega pogodbenega obdobja. Po pravočasnem obvestilu se pogodba samodejno podaljšuje za trimesečna obdobja, tudi s potrošniki, razen če jo katera koli stranka ne odpove vsaj štiri tedne pred iztekom ustreznega pogodbenega obdobja.</li>
<li>Plačilo s kreditno kartico, direktno obremenitvijo/bančnim nakazilom, računom in plačilom po povzetju je predpogoj za sklenitev pogodbe (mandat za direktno obremenitev SEPA).</li>
<li>Pogodba se sklepa za obdobje 36 mesecev.</li>
<li>Če je stranka podjetnik, se pogodba po poteku 36 mesecev samodejno podaljšuje za nadaljnja obdobja po 3 mesece, razen če jo ena od pogodbenih strank odpove najmanj 4 tedne pred iztekom pogodbe.</li>
<li>Če je stranka potrošnik, bo družba Profit Planet GmbH potrošnika najpozneje tri mesece in najprej pet mesecev pred iztekom pogodbe v pisni obliki opozorila na bližajoči se konec pogodbe in samodejno podaljšanje. Če takšno obvestilo ni poslano, pogodba preneha s potekom prvotnega pogodbenega obdobja. Če je bilo obvestilo poslano pravočasno, se pogodba tudi za potrošnike podaljša za nadaljnja obdobja po 3 mesece, če je nobena od pogodbenih strank ne odpove najpozneje 4 tedne pred iztekom posameznega obdobja.</li>
<li>Plačilo s kreditno kartico, direktno obremenitvijo/bremenitvijo računa, računom ali po povzetju je pogoj za veljavnost pogodbe (SEPA mandat za direktno obremenitev).</li>
</ol>
<p><strong>§ 3 Samodejna ponovna naročila</strong></p>
<p><strong>§ 3 Avtomatska ponovna naročila</strong></p>
<ol>
<li>Če stranka nastavi samodejno ponovno naročanje, bosta organska kava in čaj poslana na njen trenutni naslov za dostavo v izbranem intervalu naročila, glede na količino, prikazano v tabeli na straneh 2, 3, 4 in 5. Izbor vrst kave in čaja se lahko spremeni v dveh delovnih dneh od datuma dostave, ki ga stranka izbere, pod pogojem, da je zahteva za spremembo prejeta v pisni obliki.</li>
<li>Če stranka izbere avtomatsko ponovno naročilo, ji bo v izbranem naročilnem intervalu poslana BIO kava oziroma čaj v količini, izbrani v tabeli na straneh 2, 3, 4 in 5, na trenutno veljavni naslov za dostavo. Sestavo vrst kave in čaja je mogoče spremeniti do dva delovna dneva pred datumom odpreme, ki ga je izbrala stranka, pod pogojem, da je zahteva za spremembo prejeta v pisni obliki.</li>
</ol>
<p><strong>§ 4 Obveznost uporabe izdelkov podjetja Profit Planet GmbH Kazen/Dobavna pogodba</strong></p>
<p><strong>§ 4 Obveznost uporabe izdelkov družbe Profit Planet GmbH / Pogodbena kazen / Dogovor o dobavi</strong></p>
<ol>
<li>Stranka se zavezuje, da bo med trajanjem pogodbe pozorno upoštevala informacije, ki ji jih posreduje [ponudnik]. Kavni avtomati, ki jih dobavlja Profit Planet GmbH, se lahko uporabljajo izključno za izdelke podjetja Profit Planet GmbH »LANATURA«, vendar največ tri (3) oziroma pet (5) let od sklenitve pogodbe.<a id="_Hlk74558723"></a><a id="_Hlk73956971"></a></li>
<li>Kava bo dostavljena redno glede na naročilo.</li>
<li>Če stranka krši svojo obveznost iz odstavka 1, potem Družba Profit Planet GmbH ima pravico odpovedati pogodbo brez odpovednega roka iz utemeljenega razloga. Poleg tega se stranki strinjata, da mora stranka družbi Profit Planet GmbH plačati razumno kazen, ne glede na krivdo. Družba Profit Planet GmbH bo višino kazni določila po lastni presoji, njeno razumnost pa lahko v primeru spora preveri pristojno sodišče. Pravica do zahtevka za nadaljnjo odškodnino ostaja pridržana.</li>
<li>Stranka se zavezuje, da bo v času trajanja pogodbe na kavnih aparatih, ki jih zagotavlja družba Profit Planet GmbH, uporabljala izključno izdelke Profit Planet GmbH »LANATURA«, in sicer največ za obdobje treh (3) oziroma petih (5) let od sklenitve pogodbe.</li>
<li>Kava se dobavlja periodično v skladu z naročilom.</li>
<li>Če stranka krši svojo obveznost iz prvega odstavka, ima družba Profit Planet GmbH pravico do izredne odpovedi pogodbe brez odpovednega roka iz utemeljenega razloga. Poleg tega se pogodbeni stranki dogovorita o plačilu pogodbene kazni s strani stranke družbi Profit Planet GmbH v primerni višini, pri čemer višino po pravični presoji določi Profit Planet GmbH. Primernost pogodbene kazni lahko v primeru spora preveri pristojno sodišče. Pravica do uveljavljanja dodatne odškodnine ostaja pridržana.</li>
</ol>
<p><strong>§ 5 Vzdrževanje in popravila</strong></p>
<p> (1) Vzdrževanje in popravila kavnih avtomatov so vključena v določbo, kot sledi:</p>
<p>(1) Vzdrževanje in popravila kavnih aparatov vključujejo naslednje storitve:</p>
<ol>
<li>Vsa vzdrževalna in popravila se izvajajo med tednom, od ponedeljka do petka, v rednem delovnem času (9:00 - 17:00). Lahko nas kontaktirate po telefonu na številki +43 676 3440274 ali pisno na <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>. Storitve se izvajajo ob vikendih in praznikih, vendar so te izključene in jih lahko stranka uredi le sama proti doplačilu prek našega pooblaščenega servisnega partnerja.</li>
<li>Popravila, ki so posledica neustreznega vzdrževanja ali nepravilne uporabe, niso vključena. Zlasti neupoštevanje navodil za uporabo in čiščenje, nepravilno ali nezadostno odstranjevanje vodnega kamna ter napake pri uporabi so odgovornost stranke.</li>
<li>Profit Planet GmbHsi pridržuje pravico, da stranki izstavi račun za nadomestno napravo po proizvajalčevi priporočeni maloprodajni ceni (MSRP), če nadomestne naprave ne vrne službi za stranke v 4 tednih od prejema popravljene naprave.</li>
</ol>
<p> </p>
<ol>
<li>V pogodbeno vzdrževanje in popravilo izrecno niso vključene: spremembe lokacije, spremembe izdelkov, degustacije, modifikacije, odpravljanje napak, ki so posledica popravil ali sprememb s strani stranke ali tretjih oseb, odpravljanje napak, ki so posledica nepravilnega upravljanja ali neustreznega čiščenja ali vzdrževanja, odpravljanje napak, katerih vzrok je zunaj kavnega avtomata, kot so napake v oskrbi z vodo in električno energijo, naravne nesreče, zloraba, drugi izredni vplivi in tujki.</li>
<li>(a) Vsa vzdrževalna dela in popravila se izvajajo med delovniki od ponedeljka do petka v običajnem delovnem času (09:00-17:00) po telefonu: 0043 676 3440274 ali pisno na <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>. Storitve ob vikendih in praznikih niso vključene in jih lahko stranka naroči le proti doplačilu pri pooblaščenem servisnem partnerju, ki ga določi družba Profit Planet GmbH.</li>
<li>(b) Popravila, ki so posledica neustreznega vzdrževanja ali nepravilne uporabe, niso vključena. Zlasti neupoštevanje navodil za uporabo in čiščenje, nepravilno ali pomanjkljivo odstranjevanje vodnega kamna ter napake pri uporabi bremenijo stranko.</li>
<li>(c) Profit Planet GmbH si pridržuje pravico, da stranki obračuna nadomestni aparat po priporočeni maloprodajni ceni (UVP), če nadomestni aparat ni vrnjen servisni službi v roku 4 tednov po prejemu popravljenega aparata.</li>
<li>(d) Izrecno niso vključeni v pogodbeni servis vzdrževanja in popravil: sprememba lokacije aparata, sprememba izdelkov, degustacije, predelave, odprava motenj, ki so posledica popravil ali sprememb s strani stranke ali tretjih oseb, odprava motenj zaradi nepravilne uporabe ali pomanjkljivega čiščenja oziroma vzdrževanja ter odprava motenj, katerih vzrok ni v kavnem aparatu, kot so okvare pri oskrbi z vodo ali elektriko, naravne nesreče, zlorabe, drugi izredni vplivi in tuji predmeti.</li>
</ol>
<p><strong>§ 6 Izredna odpoved</strong></p>
<ol>
<li>Obe pogodbeni stranki imata pravico odpovedati to pogodbo brez odpovednega roka, če druga pogodbena stranka kljub opozorilu in razumnemu roku krši bistvene določbe te pogodbe.</li>
<li>
Profit Planet GmbHima še posebej pravico odpovedati pogodbo brez odpovednega roka, če stranka
<li>Obe pogodbeni stranki imata pravico to pogodbo izredno odpovedati brez odpovednega roka, če druga pogodbena stranka kljub opominu in določitvi primernega roka krši bistvene določbe te pogodbe.</li>
<li>Družba Profit Planet GmbH je zlasti upravičena do izredne odpovedi pogodbe brez odpovednega roka, če stranka:
<ol>
<li>s plačilom v celoti oz.je delno v zamudi in Profit Planet GmbH plačuje strankineuspešno določil razumen rok za plačilo neporavnanega zneska, ali</li>
<li>je bila minimalna količina nakupa, dogovorjena v tej pogodbi, v obdobju šestih mesecev nižja za več kot povprečno 20 %, ali</li>
<li>Tuje izdelke so pripravljali na zagotovljenih kavnih avtomatih.</li>
<li>v celoti ali delno zamuja s plačilom in družba Profit Planet GmbH stranki neuspešno določi primeren rok za poravnavo zapadlega zneska, ali</li>
<li>v obdobju šestih (6) mesecev za več kot povprečno 20 % ne doseže pogodbeno dogovorjene minimalne količine odjema, ali</li>
<li>na dobavljenih kavnih aparatih pripravlja izdelke drugih proizvajalcev.</li>
</ol>
</li>
</ol>
<p>(3) V primeru izredne odpovedi brez odpovednega roka s strani družbe Profit Planet GmbH si družba pridržuje pravico, da stranki zaračuna odškodnino za preostalo obdobje v višini 25 % dogovorjene minimalne količine nakupa, kot tudi pogodbeno dogovorjene najemnine. Pravica do zahtevka za nadaljnjo odškodnino ostaja pridržana. Stranka ima pravico dokazati, da ni nastala nobena škoda ali pa je nastala bistveno manjša škoda.</p>
<p><strong>§ 7 Lastništvo</strong></p>
<p>Dobavljeni stroji ostanejo last podjetja Profit Planet GmbH.</p>
<p><strong>§ 8 Notranji sistemi za naročanje in naročila, varstvo podatkov</strong></p>
<p>Poleg tega se strinjam s prejemanjem ekskluzivnih ponudb in informacij, kot sledi:</p>
<p><input type="checkbox" /> Strinjam se, da lahko Profit Planet GmbH obdeluje posredovane podatke in me obvešča o ekskluzivnih ponudbah in drugih informacijah prek e-novic. To soglasje lahko kadar koli prekličem s pošiljanjem e-pošte na [e-poštni naslov manjka]<a href="mailto:.office@profit-planet.com">.office@profit-planet.com</a>.To soglasje je mogoče preklicati. S tem sprejemam politiko zasebnosti.</p>
<p><input type="checkbox" /> Strinjam se, da lahko Profit Planet GmbH obdeluje posredovane podatke in me po telefonu kontaktira glede ekskluzivnih ponudb in drugih informacij. To soglasje lahko kadar koli prekličem po elektronski pošti na naslov <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>. To soglasje je mogoče preklicati. S tem sprejemam politiko zasebnosti.</p>
<p>Predhodno ste nam dali soglasje in že prejemate informacije in ponudbe o naših izdelkih, zdaj pa želite to soglasje preklicati: To soglasje lahko kadar koli prekličete tako, da pošljete e-pošto na naslov <a href="mailto:office@profit-planet.com">office@profit-planet.com</a> biti razveljavljen.</p>
<p><strong>S tem podpisom je/so naprava/e, omenjene na strani 1, sprejete/sprejete pod navedenimi pogoji.</strong></p>
<p>Velja naslednje: <strong>Splošni pogoji poslovanja</strong> pogodbo s podjetjem Profit Planet GmbH. Stranka s tem izrecno izjavlja, da <strong>Splošni pogoji poslovanja</strong> in <strong>Pravilnik o zasebnosti</strong> je prebral/a te pogoje in se z njimi strinja. Pogodba začne veljati z dnem, ko jo sprejme Profit Planet GmbH.<strong> </strong>Pogodba je sklenjena in veljavna pod pogojem pozitivnega kreditnega preverjanja.</p>
<p>Kraj, {{signingCity}} datum: {{currentDate}}</p>
<p>V primeru izredne odpovedi brez odpovednega roka s strani družbe Profit Planet GmbH si ta pridržuje pravico, da stranki za preostanek pogodbenega obdobja obračuna nadomestilo v višini 25 % pogodbeno dogovorjene minimalne količine odjema ter pogodbeno dogovorjenih najemnin. Pravica do uveljavljanja dodatne odškodnine ostaja pridržana. Stranka ima pravico dokazovati, da škoda ni nastala oziroma da je nastala bistveno manjša škoda.</p>
<p><strong>§ 7 Lastninska razmerja</strong></p>
<p>Dobavljeni aparati ostanejo last družbe Profit Planet GmbH.</p>
<p><strong>§ 8 Interni sistemi naročanja in naročila, varstvo podatkov</strong></p>
<p>Poleg tega soglašam s prejemanjem ekskluzivnih ponudb in informacij, kot sledi:</p>
<p><input type="checkbox" /> Soglašam, da družba Profit Planet GmbH obdeluje navedene podatke in jih uporablja za obveščanje o ekskluzivnih ponudbah ter drugih informacijah preko e-poštnih novic. Soglasje lahko kadar koli prekličem po e-pošti na <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>. S tem sprejemam določila o varstvu podatkov.</p>
<p><input type="checkbox" /> Soglašam, da družba Profit Planet GmbH obdeluje navedene podatke in jih uporablja za telefonsko obveščanje o ekskluzivnih ponudbah ter drugih informacijah. Soglasje lahko kadar koli prekličem po e-pošti na <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>. S tem sprejemam določila o varstvu podatkov.</p>
<p>Če ste nam soglasje že podali in že prejemate informacije ter ponudbe o naših izdelkih, vendar ga želite sedaj preklicati, lahko soglasje kadar koli prekličete po e-pošti na <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>.</p>
<p><strong>S tem podpisom se naprava oziroma naprave, navedene na strani 1, prevzamejo pod navedenimi pogoji.</strong></p>
<p>Dogovorjeno je, da veljajo <strong>Splošni pogoji poslovanja</strong> družbe Profit Planet GmbH. Stranka izrecno izjavlja, da je prebrala <strong>Splošne pogoje poslovanja</strong> in določila o <strong>varstvu podatkov</strong> ter se z njimi strinja. Pogodba začne veljati s sprejemom s strani družbe Profit Planet GmbH in je veljavna pod pogojem pozitivne kreditne preveritve.</p>
<p>Kraj: {{signingCity}} Datum: {{currentDate}}</p>
<div class="signature-grid">
<div class="signature-box">
<div class="pp-stamp">{{profitplanetSignature}}</div>
@ -632,106 +486,135 @@
</div>
<div class="signature-box">
<div>{{signatureImage}}</div>
<div>Žig/Podpis stranke</div>
<div>Žig / podpis stranke</div>
</div>
</div>
<p><em>Informacije o sestavinah, hranilnih vrednostih itd.Najdete ga na domači strani dobavitelja.www.lanaturalifestyle.com ali po telefonu: 0043 552 322 960 na voljo</em>.</p>
<p><strong>Splošni pogoji poslovanja za postrežbo kave</strong></p>
<p><em>Informacije o sestavinah, hranilnih vrednostih itd. najdete na domači strani dobavitelja www.lanaturalifestyle.com ali www.coffee-revolution.com oziroma po telefonu: 0043 552 322 960.</em></p>
<p><strong>Splošni pogoji poslovanja za kavo in servis</strong></p>
<p><strong>§ 1 Področje uporabe, oblika</strong></p>
<ol>
<li>Ti splošni pogoji poslovanja (GTC) veljajo za vse naše poslovne odnose med našimi strankami in nami. Profit Planet GmbH, Kärntner Straße 227, 8053 Gradec, Avstrija.Ti pogoji poslovanja veljajo za potrošnike in podjetja; v primeru dvoma imajo pred temi pogoji poslovanja prednost obvezne določbe o varstvu potrošnikov (zlasti v skladu z Zakonom o varstvu potrošnikov in Zakonom o prodaji na daljavo). </li>
<li>Ti splošni pogoji poslovanja veljajo zlasti za pogodbe o prodaji in/ali dobavi premičnin (»blago«), ne glede na to, ali blago izdelujemo sami ali ga kupujemo od dobaviteljev. Če ni drugače dogovorjeno, se ti splošni pogoji poslovanja v različici, veljavni v času naročila kupca, ali v vsakem primeru v različici, ki je bila kupcu nazadnje sporočena pisno, uporabljajo kot okvirni sporazum tudi za podobne prihodnje pogodbe, ne da bi se morali nanje v vsakem posameznem primeru ponovno sklicevati.</li>
<li>Veljajo izključno naši splošni pogoji poslovanja. Vsakršni drugačni, nasprotujoči si ali dopolnilni pogoji poslovanja kupca postanejo del pogodbe le, če in v obsegu, v katerem smo se z njihovo veljavnostjo izrecno strinjali. Ta zahteva po izrecnem soglasju velja v vseh primerih, na primer tudi če kupcu dobavimo blago brez zadržkov, čeprav smo seznanjeni s pogoji poslovanja kupca.</li>
<li>Posamezni dogovori, sklenjeni s kupcem v posameznih primerih (vključno s stranskimi dogovori, spremembami in dopolnitvami), imajo vedno prednost pred temi Splošnimi pogoji poslovanja. Za vsebino takih dogovorov je odločilna pisna pogodba ali naša pisna potrditev, razen če se dokaže nasprotno.</li>
<li>Pravno relevantne izjave in obvestila kupca v zvezi s pogodbo (npr. določitev rokov, prijava napak, odstop od pogodbe ali znižanje cene) morajo biti predložene pisno, tj. v pisni ali elektronski obliki (npr. pismo, e-pošta, faks). Zakonske zahteve glede oblike in nadaljnja dokazila, zlasti v primeru dvoma o pooblastilu izjavitelja, ostanejo nespremenjena.</li>
<li>Sklicevanja na uporabljivost zakonskih določb so zgolj za pojasnitev. Tudi brez takšne pojasnitve veljajo zakonske določbe, razen če so v teh pogojih poslovanja neposredno spremenjene ali izrecno izključene.</li>
<li>Ti Splošni pogoji poslovanja (SPP) veljajo za vse poslovne odnose med našimi strankami in nami, družbo Profit Planet GmbH, Kärntner Straße 227, 8053 Graz, Avstrija. SPP veljajo tako za potrošnike kot za podjetnike; obvezne določbe za varstvo potrošnikov (zlasti po KSchG in FAGG) imajo v primeru dvoma prednost pred temi SPP.</li>
<li>SPP veljajo zlasti za pogodbe o prodaji in/ali dobavi premičnih stvari ("blago"), ne glede na to, ali blago izdelujemo sami ali ga kupujemo pri dobaviteljih. Če ni dogovorjeno drugače, veljajo SPP v različici, veljavni ob oddaji naročila kupca oziroma v zadnji različici, ki je bila kupcu posredovana v pisni obliki, tudi kot okvirni dogovor za prihodnje enakovrstne pogodbe, ne da bi bilo potrebno na njih posebej opozarjati.</li>
<li>Naši SPP veljajo izključno. Odstopajoči, nasprotujoči si ali dopolnilni splošni pogoji kupca postanejo del pogodbe le v primeru in obsegu, v katerem smo izrecno soglašali z njihovo veljavnostjo. Ta zahteva po soglasju velja vedno, tudi če dobavo izvedemo brez pridržkov, čeprav poznamo SPP kupca.</li>
<li>Individualni dogovori s kupcem (vključno s stranskimi dogovori, dopolnitvami in spremembami) imajo v vsakem primeru prednost pred temi SPP. Za vsebino takšnih dogovorov je, razen če se dokaže drugače, odločilna pisna pogodba oziroma naša pisna potrditev.</li>
<li>Pravno pomembne izjave in obvestila kupca v zvezi s pogodbo (npr. določitev roka, prijava napake, odstop od pogodbe ali znižanje kupnine) morajo biti podana v pisni obliki, torej v obliki pisma, e-pošte ali telefaksa. Zakonske zahteve glede oblike in dodatni dokazi, zlasti v primeru dvoma o upravičenosti podpisnika, ostanejo nespremenjeni.</li>
<li>Sklici na zakonske določbe imajo zgolj pojasnjevalni pomen. Tudi brez takšnega pojasnila veljajo zakonske določbe, kolikor niso v teh SPP neposredno spremenjene ali izrecno izključene.</li>
</ol>
<p><strong>§ 2 Sklenitev pogodbe</strong></p>
<ol>
<li>Naše ponudbe se lahko spremenijo in niso zavezujoče. To velja tudi, če smo kupcu posredovali kataloge, tehnično dokumentacijo (npr. risbe, načrte, izračune, sklicevanja na standarde DIN), druge opise izdelkov ali dokumente tudi v elektronski obliki za katere si pridržujemo lastništvo in avtorske pravice.</li>
<li>Naročilo kupca predstavlja zavezujočo ponudbo za sklenitev pogodbe. Če v naročilu ni drugače navedeno, smo upravičeni, da to ponudbo sprejmemo v 30 dneh od njenega prejema.</li>
<li>Sprejem se lahko izjavi pisno (npr. s potrditvijo naročila) ali z izročitvijo blaga kupcu.</li>
<li>Naše ponudbe niso zavezujoče in so brez obveznosti. To velja tudi, če kupcu posredujemo kataloge, tehnično dokumentacijo (npr. risbe, načrte, izračune, kalkulacije, sklice na DIN standarde), druge opise izdelkov ali dokumentacijo - tudi v elektronski obliki - za katere si pridržujemo lastninske in avtorske pravice.</li>
<li>Naročilo blaga s strani kupca velja kot zavezujoča ponudba za sklenitev pogodbe. Če iz naročila ne izhaja drugače, smo upravičeni to ponudbo sprejeti v roku 30 dni od njenega prejema.</li>
<li>Sprejem se lahko izvede pisno (npr. s potrditvijo naročila) ali z dobavo blaga kupcu.</li>
</ol>
<p><strong>§ 3 Dobavni rok in zamuda pri dobavi</strong></p>
<ol>
<li>Dobavni rok se bo dogovoril individualno ali pa ga bomo določili mi ob sprejemu naročila. Če temu ni tako, je dobavni rok približno 1421 dni od sklenitve pogodbe.</li>
<li>Če iz razlogov, na katere nimamo vpliva (nedobavljivost blaga ali storitev), ne bomo mogli izpolniti zavezujočih dobavnih rokov, bomo stranko o tem nemudoma obvestili in ji posredovali nov predvideni datum dobave. Če blago ali storitve tudi v novem dobavnem roku ostanejo nedobavljive, smo upravičeni do celotnega ali delnega odstopa od pogodbe; vsa plačila, ki jih je stranka že opravila, ji bomo takoj povrnili. Nedobavljivost blaga ali storitev v tem smislu vključuje zlasti neizpolnitev obveznosti pravočasne dobave s strani našega dobavitelja, pod pogojem, da smo sklenili skladen posel varovanja pred tveganjem, pri čemer nismo ne mi ne naš dobavitelj krivi in v konkretnem primeru nismo dolžni nabaviti blaga ali storitev.</li>
<li>Našo odgovornost za zamudo pri dobavi urejajo zakonski predpisi. V vsakem primeru je potreben opomin stranke.</li>
<li>Dobavni rok se določi individualno oziroma ga navedemo ob sprejemu naročila. Če ni določeno drugače, znaša dobavni rok približno 14 do 21 dni od sklenitve pogodbe.</li>
<li>Če zaradi razlogov, za katere ne odgovarjamo, ne moremo spoštovati zavezujočih dobavnih rokov (nedobavljivost storitve), bomo stranko o tem nemudoma obvestili in hkrati navedli predvideni novi dobavni rok. Če storitev ni na voljo niti v novem roku, smo upravičeni v celoti ali delno odstopiti od pogodbe; morebitna že prejeta plačila bomo nemudoma vrnili. Kot primer nedobavljivosti velja zlasti nepravočasna dobava s strani našega dobavitelja, če smo sklenili ustrezen kritni posel in niti mi niti dobavitelj nismo odgovorni za zamudo.</li>
<li>Nastop naše zamude pri dobavi se določa po zakonskih predpisih. V vsakem primeru pa je potreben opomin stranke.</li>
<li>Pravice stranke in naše zakonske pravice, zlasti v primeru izključitve obveznosti izpolnitve (npr. zaradi nemožnosti ali nesorazmernosti izpolnitve), ostanejo nespremenjene.</li>
</ol>
<p> </p>
<p><strong>§ 4 Dobava, prehod nevarnosti, prevzem, zamuda pri prevzemu</strong></p>
<ol>
<li>Pravice stranke in naše zakonske pravice, zlasti v primeru izključitve obveznosti izpolnitve (npr. zaradi nemožnosti ali nerazumnosti izpolnitve in/ali naknadne izpolnitve), ostanejo nespremenjene.</li>
</ol>
<p><strong>§ 4 Dobava, prenos tveganja, prevzem, neizpolnitev prevzema</strong></p>
<ol>
<li>Dobava je franko tovarna, kar je tudi kraj izpolnitve za dobavo in morebitno nadaljnjo izpolnitev. Na zahtevo in stroške kupca bo blago odposlano na drug namembni kraj (prodaja z odpremo). Če ni drugače dogovorjeno, smo upravičeni določiti način odpreme (zlasti prevoznika, pot dostave in embalažo).</li>
<li>Tveganje za naključno izgubo ali naključno poškodbo blaga preide na kupca najkasneje ob dostavi.Pri nakupih po pošti tveganje za nenamerno izgubo ali poškodbo blaga med prevozom preide na kupca ob izročitvi blaga prevozniku/špediterju za podjetja; za potrošnike tveganje preide šele, ko je blago izročeno potrošniku ali tretji osebi, ki jo določi potrošnik (ki ni prevoznik). Če je potrošnik sam uredil prevozno pogodbo brez našega posredovanja, tveganje preide na kupca ob izročitvi blaga prevozniku.</li>
<li>Če kupec ne sprejme naročila, ne sodeluje ali če se naša dobava zamuja iz drugih razlogov, ki jih je mogoče pripisati kupcu, smo upravičeni zahtevati odškodnino za nastalo škodo, vključno z dodatnimi stroški (npr. stroški skladiščenja). Naša pravica do dokazovanja višje škode in naše zakonske pravice (zlasti povračilo dodatnih stroškov, razumno odškodnino in odpoved pogodbe) ostanejo nespremenjene.</li>
<li>Dobava se opravi iz skladišča, ki je hkrati kraj izpolnitve za dobavo in morebitno naknadno izpolnitev. Na zahtevo in stroške kupca se blago lahko pošlje na drug kraj dostave (prodaja z odpremo). Če ni dogovorjeno drugače, smo upravičeni sami določiti način pošiljanja (zlasti prevoznika, pot dostave in embalažo).</li>
<li>Nevarnost naključnega uničenja ali poslabšanja blaga preide na kupca najpozneje ob izročitvi blaga. Pri prodaji z odpremo nevarnost za podjetnike preide že z izročitvijo blaga prevozniku, za potrošnike pa šele z izročitvijo blaga potrošniku ali tretji osebi, ki jo je določil potrošnik.</li>
<li>Če kupec zamuja s prevzemom ali ne izpolni svojih dolžnosti sodelovanja oziroma če se dobava zavleče iz razlogov na strani kupca, smo upravičeni zahtevati povračilo nastale škode in dodatnih stroškov.</li>
</ol>
<p><strong>§ 5 Cene in plačilni pogoji</strong></p>
<ol>
<li>Če ni v posameznih primerih dogovorjeno drugače, veljajo naše cene, veljavne v času sklenitve pogodbe, ex works, plus zakonsko določen DDV.</li>
<li>V primeru nakupov po pošti kupec krije stroške prevoza iz skladišča in stroške morebitnega zavarovanja prevoza, ki ga zahteva kupec. Razen če v posameznem primeru izstavimo račun za dejanske stroške prevoza, se šteje, da je dogovorjena pavšalna cena prevoza (brez zavarovanja prevoza) v višini 200 EUR. Morebitne carine, pristojbine, davke in druge javne dajatve krije kupec.</li>
<li>Kupnina zapade v plačilo v 14 dneh od izstavitve računa in dobave oziroma prevzema blaga. Vendar si tudi v okviru tekočega poslovnega odnosa pridržujemo pravico, da zahtevamo predplačilo za celotno ali delno dobavo. Takšno rezervacijo bomo podali najkasneje ob potrditvi naročila.</li>
<li>Kupec bo v zamudi po izteku zgoraj navedenega plačilnega roka. V času zamude se bodo na kupnino obračunavale obresti po veljavni zakonski obrestni meri zamudnih obresti. Pridržujemo si pravico do uveljavljanja nadaljnje odškodnine zaradi zamude. V odnosu do trgovcev naša pravica do komercialnih zamudnih obresti (§ 352 UGB) ostane nespremenjena.</li>
<li>Kupec je upravičen do pobota ali pridržanja le, če je njegova terjatev pravnomočno uveljavljena ali nesporna. V primeru napak pri dobavi ostanejo kupčeve protizahteve, zlasti tiste v skladu s 7. členom teh splošnih pogojev, nespremenjene.Ta omejitev ne velja za zahtevke zoper potrošnike, ki so pravno povezani z njihovo odgovornostjo.</li>
<li>Če se po sklenitvi pogodbe izkaže, da je naša terjatev do plačila kupnine ogrožena zaradi neplačilne sposobnosti kupca ali grozeče insolventnosti na primer zaradi vložitve predloga za začetek insolventnega postopka ali podobnih okoliščin smo upravičeni zavrniti izpolnitev in kupcu določiti razumen rok za plačilo protiplačila ali zavarovanja. Če ta rok poteče brez rezultata, smo upravičeni do odstopa od pogodbe.</li>
<li>Če ni v posameznem primeru dogovorjeno drugače, veljajo naše veljavne cene v času sklenitve pogodbe, in sicer iz skladišča, povečane za zakonsko določen DDV.</li>
<li>Pri prodaji z odpremo nosi kupec stroške prevoza od skladišča ter stroške morebitnega transportnega zavarovanja, ki ga želi kupec. Če dejanski stroški prevoza niso posebej obračunani, velja pavšalni znesek za prevoz (brez zavarovanja) v višini 200 EUR. Morebitne carine, dajatve, davke in druge javne prispevke nosi kupec.</li>
<li>Kupnina zapade v plačilo v roku 14 dni od izdaje računa ter dobave oziroma prevzema blaga. Tudi v okviru stalnega poslovnega sodelovanja smo upravičeni dobavo v celoti ali delno izvesti samo proti predplačilu. Takšen pridržek sporočimo najpozneje s potrditvijo naročila.</li>
<li>Po poteku navedenega plačilnega roka kupec zapade v zamudo. Kupnina se v času zamude obrestuje po vsakokrat veljavni zakonski zamudni obrestni meri. Pridržujemo si pravico do uveljavljanja dodatne škode zaradi zamude. Pri podjetnikih ostaja neokrnjena tudi pravica do gospodarskih zamudnih obresti po § 352 UGB.</li>
<li>Kupec ima pravico do pobota ali zadržanja plačila le, če je njegova terjatev pravnomočno ugotovljena ali nesporna. V primeru napak pri dobavi ostajajo pravice kupca po § 7 teh SPP nespremenjene. Za potrošnike ta omejitev ne velja za zahtevke, ki so v pravni povezavi z njihovo obveznostjo.</li>
<li>Če po sklenitvi pogodbe postane očitno, da je naš zahtevek za plačilo kupnine ogrožen zaradi slabe ali grozeče plačilne nesposobnosti kupca - na primer zaradi začetka insolvenčnega postopka ali primerljivih okoliščin - smo upravičeni zavrniti svojo izpolnitev ter kupcu določiti primeren rok za plačilo ali predložitev ustreznega zavarovanja. Po neuspešnem poteku tega roka smo upravičeni odstopiti od pogodbe.</li>
</ol>
<p><strong>§ 6 Pridržek lastništva</strong></p>
<p><strong>§ 6 Pridržek lastninske pravice</strong></p>
<ol>
<li>Lastništvo prodanega blaga si pridržujemo do celotnega poplačila vseh naših trenutnih in prihodnjih terjatev, ki izhajajo iz kupoprodajne pogodbe in tekočega poslovnega razmerja (zavarovane terjatve).</li>
<li>Blago, za katero velja pridržek lastništva, se ne sme zastaviti ali prenesti kot zavarovanje na tretje osebe pred popolnim poplačilom zavarovanih terjatev. Kupec nas mora o tem nemudoma pisno obvestiti, če je vložen predlog za začetek postopka zaradi insolventnosti ali če tretje osebe poskušajo zaseči (ali zapleniti) blago, ki je v naši lasti.</li>
<li>V primeru kršitve pogodbe s strani kupca, zlasti v primeru neplačila kupnine v roku, smo v skladu z zakonskimi določbami upravičeni odstopiti od pogodbe in/ali zahtevati vračilo blaga na podlagi našega pridržka lastništva. Zahteva za vračilo ne pomeni samodejno izjave o odstopu od pogodbe; temveč smo upravičeni zahtevati le vračilo blaga in si pridržujemo pravico do odstopa od pogodbe. Če kupec ne plača kupnine v roku, lahko te pravice uveljavljamo le, če smo mu predhodno brez uspeha določili razumen rok za plačilo ali če določitev takega roka v skladu z zakonskimi določbami ni potrebna.</li>
<li>Do popolnega plačila vseh sedanjih in prihodnjih terjatev iz kupoprodajne pogodbe ter trajnega poslovnega razmerja ostaja prodano blago v naši lasti.</li>
<li>Blaga, ki je predmet pridržka lastninske pravice, pred popolnim plačilom ni dovoljeno zastaviti ali prenesti v zavarovanje tretjim osebam. Kupec nas mora nemudoma pisno obvestiti, če je vložen predlog za začetek insolvenčnega postopka ali če pride do posega tretjih oseb (npr. rubeža) na blagu, ki je v naši lasti.</li>
<li>Če kupec ravna v nasprotju s pogodbo, zlasti če ne poravna zapadle kupnine, smo upravičeni v skladu z zakonom odstopiti od pogodbe in/ali zahtevati vrnitev blaga na podlagi pridržka lastninske pravice. Zahteva za vrnitev blaga sama po sebi ne pomeni odstopa od pogodbe. Plačilo kupnine lahko zahtevamo šele po predhodni določitvi primernega roka za plačilo, razen če zakon določa drugače.</li>
</ol>
<p>.</p>
<p><strong>§ 7 Bistvene napake</strong></p>
<p>(1) V nujnih primerih, npr. če je ogrožena obratovalna varnost ali če je treba preprečiti nesorazmerno škodo, ima kupec pravico, da napako odpravi sam in od nas zahteva povračilo objektivno potrebnih (razumnih) nastalih stroškov. O takšni samoodpravi nas je treba nemudoma če je mogoče, predhodno obvestiti.Pravica do samoreklamacije ne obstaja, če bi bili upravičeni zavrniti ustrezno naknadno izpolnitev v skladu z zakonskimi določbami.</p>
<p>(2) Če naknadna izpolnitev ni uspešna ali če razumen rok, ki ga je kupec določil za naknadno izpolnitev, poteče brez uspeha ali če naknadna izpolnitev v skladu z zakonskimi določbami ni potrebna, lahko kupec odstopi od kupoprodajne pogodbe ali zniža kupnino. Vendar pa v primeru nepomembne napake ni pravice do odstopa od pogodbe. Ne glede na zgornje določbe veljajo zakonske garancijske pravice za potrošnike brez omejitev. Zlasti garancijski rok za potrošnike je dve leti od dobave blaga.</p>
<p>(3) Kupčeve zahtevke za odškodnino ali povračilo nepotrebnih stroškov obstajajo tudi v primeru napak le v skladu z 8. členom in so sicer izključeni.</p>
<p><strong>§ 7 Stvarne napake</strong></p>
<ol>
<li>V nujnih primerih, npr. kadar je ogrožena varnost poslovanja ali obstaja nevarnost nesorazmerne škode, ima kupec pravico napako odpraviti sam in od nas zahtevati povračilo objektivno potrebnih in primernih stroškov. O takšni samostojni odpravi nas mora nemudoma obvestiti, po možnosti še pred izvedbo. Pravica do samostojne odprave ne obstaja, če bi bili po zakonu upravičeni zavrniti naknadno izpolnitev.</li>
<li>Če naknadna izpolnitev ni uspela ali je primeren rok za naknadno izpolnitev neuspešno potekel oziroma po zakonu ni potreben, lahko kupec odstopi od pogodbe ali zahteva znižanje kupnine. Pri neznatnih napakah pravica do odstopa ne obstaja. Za potrošnike veljajo zakonske garancijske pravice brez omejitev. Garancijska doba za potrošnike znaša dve leti od izročitve blaga.</li>
<li>Zahtevki kupca za odškodnino oziroma povračilo zaman nastalih stroškov zaradi napak obstajajo samo v okviru določb § 8 teh SPP.</li>
</ol>
<p><strong>§ 8 Druga odgovornost</strong></p>
<p>(1) Razen če ni v teh pogojih poslovanja, vključno z naslednjimi določbami, določeno drugače, odgovarjamo za kršitve pogodbenih in nepogodbenih obveznosti v skladu z zakonskimi predpisi.</p>
<p>(2) Ne glede na pravno podlago odgovarjamo v okviru odgovornosti za krivdo v primerih naklepa in hude malomarnosti. V primerih lahke malomarnosti odgovarjamo le.</p>
<p>a) za škodo, nastalo zaradi poškodbe življenja, telesa ali zdravja,</p>
<p>b) za škodo, ki izhaja iz kršitve bistvenih pogodbenih obveznosti (tj. obveznosti, katerih izpolnitev je bistvena za pravilno izvajanje pogodbe in na katere spoštovanje se pogodbeni partner lahko redno zanese). V tem primeru pa je naša odgovornost omejena na odškodnino za tipično, predvidljivo škodo.</p>
<p>(3) Zgoraj navedene omejitve odgovornosti veljajo tudi v korist tretjih oseb in za kršitve dolžnosti oseb, katerih krivda je v skladu z zakonskimi določbami naša. Ne veljajo, če smo goljufivo prikrili napako ali prevzeli jamstvo za kakovost blaga, niti ne veljajo za zahtevke po Zakonu o odgovornosti za izdelke.</p>
<p>(4) V obsegu, ki ga dovoljuje zakonodaja, ne odgovarjamo za posredno škodo, posledično škodo ali izgubljeni dobiček. Ta izključitev odgovornosti ne velja za potrošnike, če obstaja vzročna zveza s kršitvijo bistvenih pogodbenih obveznosti.</p>
<p>(5) Odstop od pogodbe ali odpoved pogodbe zaradi kršitve pogodbe, ki ne temelji na napaki blaga, je dovoljena le, če smo zanjo odgovorni.Vsaka nadaljnja prosta pravica kupca do odstopa od pogodbe ali odpovedi je izključena v obsegu, ki je zakonsko dovoljen.</p>
<p>(6) Zgornji predpisi ne vplivajo na obvezne določbe Zakona o odgovornosti za izdelke in odgovornost za namerno ali hudo malomarno ravnanje.</p>
<p><strong>§ 9 Zastaralni rok</strong></p>
<p>(1) V pogodbah s podjetji, kot so opredeljene v 1. členu avstrijskega zakona o varstvu potrošnikov (KSchG), se zakonsko določena garancijska doba za premičnine v skladu z 933. členom avstrijskega civilnega zakonika (ABGB) skrajša na eno leto od dobave. To ne velja v primerih goljufivega prikrivanja ali če je bila dana garancija za kakovost blaga.</p>
<p>(2) Skrajšanje roka iz 1. odstavka ne velja za zahtevke kupca za odškodnino, ki je nastala zaradi poškodbe življenja, telesa ali zdravja, v primerih hude malomarnosti ali namernega ravnanja ali za zahtevke v skladu z Zakonom o odgovornosti za izdelke.</p>
<p>(3) Zahtevki za odškodnino zaradi napak (§ 933a ABGB) zastarajo v zakonskem roku treh let od vedenja za škodo in oškodovalca, ne glede na skrajšano garancijsko dobo.</p>
<p>(4) Zakonski garancijski in zastaralni roki veljajo za potrošnike brez omejitev (§§ 922 in naslednji ABGB, § 9 KSchG).</p>
<p><strong>§ 10 Izbira prava in pristojnosti</strong></p>
<p>(1) Vsa pravna razmerja med nami in kupcem so predmet naslednjega:izključno materialno pravo Republike Avstrije, z izjemo Konvencije ZN o pogodbah o mednarodni prodaji blaga (CISG) in drugih mednarodnih kolizijskih pravil, razen če obvezni predpisi o varstvu potrošnikov določajo drugače.</p>
<p>(2) Če je kupec podjetnik v smislu 1. člena avstrijskega zakona o varstvu potrošnikov (KSchG), je za vse spore, ki izhajajo iz te pogodbe ali so z njo povezani, vključno z njeno veljavnostjo in izpolnjevanjem, pristojno sodišče v Gradcu. Vendar pa smo upravičeni tudi do vložitve tožbe na splošnem kraju pristojnosti kupca ali na katerem koli drugem zakonsko dovoljenem kraju pristojnosti.</p>
<p>(3) Za potrošnike veljajo zakonska pravila o pristojnosti. S potrošniki se ne bo sklenil noben odstopni dogovor o pristojnosti.</p>
<ol>
<li>Če iz teh SPP ne izhaja drugače, odgovarjamo za kršitve pogodbenih in nepogodbenih obveznosti v skladu z zakonskimi predpisi.</li>
<li>Ne glede na pravno podlago odgovarjamo v okviru krivdne odgovornosti za naklep in hudo malomarnost. Pri lahki malomarnosti odgovarjamo samo:
<ol>
<li>za škodo zaradi poškodbe življenja, telesa ali zdravja,</li>
<li>za škodo zaradi kršitve bistvenih pogodbenih obveznosti (obveznosti, katerih izpolnitev sploh omogoča pravilno izvajanje pogodbe in na katere se pogodbeni partner upravičeno zanaša). V tem primeru je naša odgovornost omejena na tipično predvidljivo škodo.</li>
</ol>
</li>
<li>Navedene omejitve odgovornosti veljajo tudi v korist tretjih oseb ter za kršitve obveznosti oseb, katerih ravnanje se nam pripisuje po zakonu. Ne veljajo, če smo napako naklepno prikrili ali prevzeli jamstvo za lastnosti blaga oziroma pri zahtevkih po Zakonu o odgovornosti za proizvode.</li>
<li>Kolikor zakon dopušča, ne odgovarjamo za posredno škodo, posledično škodo ali izgubljeni dobiček. Za potrošnike ta izključitev odgovornosti ne velja, če obstaja vzročna zveza s kršitvijo bistvenih pogodbenih obveznosti.</li>
<li>Odstop ali odpoved zaradi kršitve obveznosti, ki ne temelji na napaki blaga, je dovoljena le, če smo za kršitev odgovorni. Morebitna dodatna pravica kupca do prostega odstopa ali odpovedi je, kolikor je zakonsko dopustno, izključena.</li>
<li>Obvezne določbe Zakona o odgovornosti za proizvode ter odgovornost za naklepno ali hudo malomarno ravnanje ostajajo nespremenjene.</li>
</ol>
<p><strong>§ 9 Zastaranje</strong></p>
<ol>
<li>Pri pogodbah s podjetniki v smislu § 1 KSchG se zakonski garancijski rok za premične stvari po § 933 ABGB skrajša na eno leto od izročitve. To ne velja v primeru prevare ali prevzema jamstva za lastnosti blaga.</li>
<li>Skrajšanje roka iz prvega odstavka ne velja za zahtevke zaradi škode na življenju, telesu ali zdravju, za primere hude malomarnosti ali naklepa ter za zahtevke po Zakonu o odgovornosti za proizvode.</li>
<li>Odškodninski zahtevki zaradi napak (§ 933a ABGB) zastarajo ne glede na skrajšan garancijski rok v zakonskem roku treh let od seznanitve s škodo in povzročiteljem.</li>
<li>Za potrošnike veljajo brez omejitev zakonski garancijski in zastaralni roki (§§ 922 dalje ABGB in § 9 KSchG).</li>
</ol>
<p><strong>§ 10 Izbira prava in pristojnost sodišča</strong></p>
<ol>
<li>Za vsa pravna razmerja med nami in kupcem velja izključno materialno pravo Republike Avstrije, z izključitvijo Konvencije ZN o mednarodni prodaji blaga (CISG) ter drugih mednarodnih kolizijskih pravil, kolikor temu ne nasprotujejo obvezne določbe o varstvu potrošnikov.</li>
<li>Če je kupec podjetnik v smislu § 1 KSchG, je za vse spore iz te pogodbe ali v povezavi z njo, vključno z njeno veljavnostjo in izvajanjem, pristojno stvarno pristojno sodišče v Gradcu. Pridržujemo si pravico vložiti tožbo tudi pri splošno pristojnem sodišču kupca ali drugem zakonsko dopustnem sodišču.</li>
<li>Za potrošnike veljajo zakonske določbe o krajevni pristojnosti. Posebni dogovori o krajevni pristojnosti s potrošniki niso sklenjeni.</li>
</ol>
<p><strong>§ 11 Končne določbe</strong></p>
<p>Če bi katera koli določba te pogodbe bila ali postala neveljavna ali neizvršljiva, to ne vpliva na veljavnost preostalih določb. Stranki se zavezujeta, da bosta vse neveljavne ali neizvršljive določbe nadomestili z novimi določbami, ki v obsegu, ki je pravno dovoljen, odražajo ekonomski namen neveljavnih ali neizvršljivih določb. Enako velja, če se v tej pogodbi ugotovi vrzel. Da bi zapolnili takšno vrzel, se stranki zavezujeta, da si bosta prizadevali za vzpostavitev ustreznih določb v tej pogodbi, ki se bodo čim bolj približale temu, kar bi pogodbeni stranki nameravali v skladu z namenom in namenom te pogodbe, če bi zadevo upoštevali.</p>
<p>Pogoji poslovanja za storitev Profit Planet Coffee, veljavni od 1. avgusta 2025</p>
<p>Če bi posamezne določbe te pogodbe bile ali postale neveljavne ali nične, to ne vpliva na veljavnost ostalih določb pogodbe. Pogodbeni stranki se zavezujeta, da bosta neveljavne ali nične določbe nadomestili z novimi določbami, ki bodo v največji možni meri pravno dopustno odražale gospodarski namen prvotne določbe.</p>
<p>Enako velja, če se v pogodbi izkaže pravna praznina. V tem primeru si pogodbeni stranki prizadevata za sprejem ustrezne ureditve, ki se najbolj približa tistemu, kar bi pogodbeni stranki določili, če bi to vprašanje ob sklenitvi pogodbe upoštevali.</p>
<p>Veljavna različica Splošnih pogojev poslovanja Profit Planet Kaffee-Service: 01.08.2025</p>
<table>
<tr>
<td></td>
</tr>
</table>
<p><a id="page4"></a></p>
<p><strong>Informacije za potrošnike o pravici do odstopa od pogodbe (pravica do preklica)</strong><br /></p>
<p><em>Pravica do odstopa od pogodbe:</em>Od te pogodbe imate pravico odstopiti v 14 dneh brez navedbe razloga. Odstopni rok poteče 14 dni od dneva, ko vi ali tretja oseba, ki ni prevoznik in jo določite vi, pridobi fizično posest prvega blaga iz te pogodbe. V primeru pogodbe o storitvah (npr. najem kavnega avtomata) odstopni rok začne teči z dnem sklenitve pogodbe.</p>
<p>Za uveljavljanje pravice do odstopa od pogodbe nas morate (Profit Planet GmbH, Liebenauer Hauptstraße 82c, 8041 Gradec, Avstrija, e-pošta: office@profit-planet.com ) obvestiti o svoji odločitvi o odstopu od te pogodbe z nedvoumno izjavo (npr. s pismom, poslanim po pošti ali elektronski pošti). Uporabite lahko priloženi vzorčni obrazec za odstop od pogodbe, vendar to ni obvezno. Za spoštovanje roka za odstop od pogodbe zadostuje, da nam sporočilo o uveljavljanju pravice do odstopa od pogodbe pošljete pred iztekom roka za odstop od pogodbe.</p>
<p><em>Posledice preklica:</em>Če odstopite od te pogodbe, vam bomo brez nepotrebnega odlašanja in najkasneje v štirinajstih dneh od dneva, ko smo prejeli vaše obvestilo o odstopu od pogodbe, povrnili vsa prejeta plačila vključno s stroški dostave (razen morebitnih dodatnih stroškov, ki bi nastali, če bi izbrali način dostave, ki ni naša najcenejša standardna možnost dostave) povrnili vsa plačila, ki ste jih prejeli od vas vključno s stroški dostave (razen morebitnih dodatnih stroškov, ki bi nastali, če bi izbrali način dostave, ki ni naša najcenejša standardna možnost dostave). Za to povračilo bomo uporabili isti način plačila, kot ste ga uporabili za prvotno transakcijo, razen če ni izrecno dogovorjeno drugače. Za to povračilo vam ne bomo zaračunali nobenih stroškov.</p>
<p>Če je odpovedana pogodba kupoprodajna pogodba za blago, lahko zadržimo vračilo kupnine, dokler ne prejmemo blaga nazaj ali dokler nam ne predložite dokazila o vrnitvi blaga, kar nastopi prej. V tem primeru nam morate blago vrniti ali izročiti brez nepotrebnega odlašanja in v vsakem primeru najpozneje v štirinajstih dneh od dneva, ko nas obvestite o odstopu od te pogodbe. Rok se šteje za izpolnjen, če blago pošljete pred iztekom štirinajstdnevnega roka. Neposredne stroške vračila blaga krijete sami.</p>
<p>Za izgubo vrednosti blaga odgovarjate le, če je ta izguba vrednosti posledica ravnanja z blagom, ki ni potrebno za preizkus njegovega stanja, lastnosti in funkcionalnosti.</p>
<p>Če ste zahtevali, da se storitev (ali redna dobava blaga) začne v roku za odstop od pogodbe, nam morate plačati razumen znesek, ki ustreza deležu storitev, ki so bile že opravljene do trenutka uveljavljanja vaše pravice do odstopa od pogodbe, v primerjavi s celotnim obsegom storitev, predvidenih v pogodbi.</p>
<p><strong>Vzorec obrazca za odpoved</strong><br /></p>
<p>(Če želite preklicati pogodbo, lahko izpolnite ta obrazec in nam ga vrnete.)</p>
<p> Za Profit Planet GmbH, Liebenauer Hauptstraße 82c, 8041 Graz, E-pošta:<a href="mailto:office@profit-planet.com">office@profit-planet.com</a>:</p>
<p> S tem prekličem/prekličemo ()</p>
<p><strong>tisti od mene/nas (</strong>) sklenjena pogodba za nakup naslednjega blaga/opravljanje naslednje storitve</p>
<p> Naročeno dne () / prejeto dne ()</p>
<p> Ime potrošnika/potrošnikov</p>
<p> Naslov potrošnika(-ov)</p>
<p><strong>Informacije za potrošnike o pravici do odstopa od pogodbe</strong><br /></p>
<p><em>Pravica do odstopa od pogodbe</em></p>
<p>Imate pravico, da v roku štirinajstih dni brez navedbe razloga odstopite od te pogodbe.</p>
<p>Odstopni rok znaša štirinajst dni od dneva, ko ste vi ali tretja oseba, ki ste jo določili in ni prevoznik, prevzeli prvo blago v okviru te pogodbe.</p>
<p>Pri pogodbah o storitvah (npr. najem kavnega aparata) začne odstopni rok teči z dnem sklenitve pogodbe.</p>
<p>Za uveljavitev pravice do odstopa nas morate obvestiti: Profit Planet GmbH, Kärntner Straße 227, 8053 Graz, e-pošta: <a href="mailto:office@profit-planet.com">office@profit-planet.com</a>, z jasno izjavo (npr. pismo po pošti ali e-pošta) o svoji odločitvi za odstop od pogodbe.</p>
<p>Za to lahko uporabite spodnji vzorec obrazca za odstop, vendar njegova uporaba ni obvezna. Za spoštovanje roka zadostuje, da obvestilo o uveljavljanju pravice do odstopa pošljete pred iztekom odstopnega roka.</p>
<p><em>Posledice odstopa</em></p>
<p>Če odstopite od pogodbe, vam bomo vrnili vsa prejeta plačila, vključno s stroški dostave (razen dodatnih stroškov, ki izhajajo iz vaše izbire dražjega načina dostave od najugodnejše standardne dostave, ki jo ponujamo), nemudoma in najpozneje v štirinajstih dneh od prejema vašega obvestila o odstopu.</p>
<p>Vračilo bomo izvedli z enakim plačilnim sredstvom, kot ste ga uporabili pri prvotni transakciji, razen če je izrecno dogovorjeno drugače. Za vračilo vam ne bomo zaračunali nobenih stroškov.</p>
<p>Če gre za kupoprodajno pogodbo za blago, lahko vračilo zadržimo do prejema vrnjenega blaga ali dokaza o odpremi blaga, odvisno od tega, kateri dogodek nastopi prej. Blago morate vrniti najpozneje v štirinajstih dneh od dneva, ko ste nas obvestili o odstopu. Rok je spoštovan, če blago odpošljete pred iztekom štirinajstdnevnega roka.</p>
<p>Neposredne stroške vračila blaga nosite sami. Za morebitno zmanjšanje vrednosti blaga odgovarjate le, če je posledica ravnanja, ki ni bilo potrebno za ugotovitev narave, lastnosti in delovanja blaga.</p>
<p>Če ste zahtevali, da se izvajanje storitve (ali redna dobava blaga) začne že v času odstopnega roka, nam morate plačati sorazmeren znesek za storitve, opravljene do trenutka odstopa.</p>
<p><strong>Vzorec obrazca za odstop</strong><br /></p>
<p>(Če želite odstopiti od pogodbe, izpolnite ta obrazec in nam ga pošljite.)</p>
<p>Za: Profit Planet GmbH, Kärntner Straße 227, 8053 Graz, e-pošta: <a href="mailto:office@profit-planet.com">office@profit-planet.com</a></p>
<p>- S tem odstopam(o) od pogodbe, ki sem jo / smo jo sklenili za nakup naslednjega blaga / izvedbo naslednje storitve</p>
<p>- Naročeno dne __________ / prejeto dne __________</p>
<p>- Ime potrošnika(-ov)</p>
<p>- Naslov potrošnika(-ov)</p>
<p>- Datum</p>
<p> Podpis potrošnika(-ov) (samo za obvestila na papirju)</p>
<p>- Podpis potrošnika(-ov) (samo pri obvestilu v papirni obliki)</p>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

89
utils/languageUtils.js Normal file
View File

@ -0,0 +1,89 @@
const BUILTIN_LANGUAGES = [
{ languageCode: 'de', label: 'Deutsch', isEnabled: true, isCustom: false },
{ languageCode: 'en', label: 'English', isEnabled: true, isCustom: false },
{ languageCode: 'sl', label: 'Slovenian', isEnabled: true, isCustom: false },
];
function normalizeLanguageCode(value) {
const raw = String(value == null ? '' : value).trim();
if (!raw) return '';
const normalized = raw.replace('_', '-');
if (!/^[a-z]{2,5}(?:-[a-zA-Z0-9]{2,8})?$/.test(normalized)) {
return '';
}
return normalized.toLowerCase();
}
function normalizeBoolean(value, fallback) {
if (value === undefined || value === null) return fallback;
if (typeof value === 'boolean') return value;
const normalized = String(value).trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
return fallback;
}
function compareLanguageCodes(left, right) {
const order = ['de', 'en', 'sl'];
const leftIndex = order.indexOf(left);
const rightIndex = order.indexOf(right);
if (leftIndex !== -1 || rightIndex !== -1) {
if (leftIndex === -1) return 1;
if (rightIndex === -1) return -1;
return leftIndex - rightIndex;
}
return left.localeCompare(right);
}
function mergeLanguageDescriptors(rows = []) {
const merged = new Map();
BUILTIN_LANGUAGES.forEach((entry) => {
merged.set(entry.languageCode, { ...entry });
});
rows.forEach((row) => {
const languageCode = normalizeLanguageCode(
row?.languageCode ?? row?.language_code ?? row?.code ?? row?.lang,
);
if (!languageCode) return;
const existing = merged.get(languageCode) || {
languageCode,
label: languageCode.toUpperCase(),
isEnabled: true,
isCustom: true,
};
const label = String(row?.label ?? row?.name ?? existing.label ?? languageCode.toUpperCase()).trim() || existing.label;
merged.set(languageCode, {
...existing,
languageCode,
label,
isEnabled: normalizeBoolean(row?.isEnabled ?? row?.is_enabled, existing.isEnabled),
isCustom: normalizeBoolean(row?.isCustom ?? row?.is_custom, existing.isCustom),
});
});
return Array.from(merged.values()).sort((left, right) => compareLanguageCodes(left.languageCode, right.languageCode));
}
function resolveDocumentTemplateStorageFolder(languageCode) {
const normalized = normalizeLanguageCode(languageCode);
if (normalized === 'en') return 'english';
if (normalized === 'de') return 'german';
return normalized || 'other';
}
module.exports = {
BUILTIN_LANGUAGES,
mergeLanguageDescriptors,
normalizeLanguageCode,
resolveDocumentTemplateStorageFolder,
};