- Mandantenauswahl fuer Benutzer mit mehreren Tenants bauen - feste Tenant-Domains ohne Wildcard-Abhaengigkeit vorbereiten - Login, Backfill, Smoke-Checks und M3-Dokumentation aktualisieren
237 lines
7.6 KiB
PHP
237 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require __DIR__ . '/dev-db.php';
|
|
|
|
function m3_env(string $name, string $default): string
|
|
{
|
|
$value = getenv($name);
|
|
if ($value === false || trim($value) === '') {
|
|
return $default;
|
|
}
|
|
|
|
return trim($value);
|
|
}
|
|
|
|
function m3_email_norm(?string $email): ?string
|
|
{
|
|
$normalized = strtolower(trim((string)$email));
|
|
|
|
return $normalized !== '' ? $normalized : null;
|
|
}
|
|
|
|
function m3_decimal_to_cents(mixed $value): int
|
|
{
|
|
$normalized = str_replace(',', '.', trim((string)$value));
|
|
if ($normalized === '') {
|
|
return 0;
|
|
}
|
|
|
|
$negative = str_starts_with($normalized, '-');
|
|
if ($negative) {
|
|
$normalized = substr($normalized, 1);
|
|
}
|
|
|
|
[$euros, $cents] = array_pad(explode('.', $normalized, 2), 2, '0');
|
|
$euros = preg_replace('/[^0-9]/', '', $euros) ?? '0';
|
|
$cents = preg_replace('/[^0-9]/', '', $cents) ?? '0';
|
|
$cents = str_pad(substr($cents, 0, 2), 2, '0');
|
|
|
|
$result = ((int)$euros * 100) + (int)$cents;
|
|
|
|
return $negative ? -$result : $result;
|
|
}
|
|
|
|
function m3_fetch_tenant_id(PDO $pdo, string $slug): int
|
|
{
|
|
$stmt = $pdo->prepare('SELECT id FROM tenants WHERE slug = ?');
|
|
$stmt->execute([$slug]);
|
|
$tenantId = $stmt->fetchColumn();
|
|
|
|
if ($tenantId === false) {
|
|
throw new RuntimeException("Tenant '{$slug}' was not created.");
|
|
}
|
|
|
|
return (int)$tenantId;
|
|
}
|
|
|
|
function m3_upsert_user(PDO $pdo, string $email, string $emailNorm, string $displayName): int
|
|
{
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO users (email, email_norm, display_name, status)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
email = VALUES(email),
|
|
display_name = VALUES(display_name),
|
|
status = VALUES(status)'
|
|
);
|
|
$stmt->execute([$email, $emailNorm, $displayName, 'active']);
|
|
|
|
$stmt = $pdo->prepare('SELECT id FROM users WHERE email_norm = ?');
|
|
$stmt->execute([$emailNorm]);
|
|
$userId = $stmt->fetchColumn();
|
|
|
|
if ($userId === false) {
|
|
throw new RuntimeException("User '{$emailNorm}' was not created.");
|
|
}
|
|
|
|
return (int)$userId;
|
|
}
|
|
|
|
$pdo = dev_pdo();
|
|
$applied = dev_apply_migrations($pdo);
|
|
|
|
$tenantSlug = m3_env('M3_DEFAULT_TENANT_SLUG', 'default');
|
|
$tenantName = m3_env('M3_DEFAULT_TENANT_NAME', 'Kaffeeliste Bestand');
|
|
$ownerEmailNorm = m3_email_norm(getenv('M3_DEFAULT_TENANT_OWNER_EMAIL') ?: null);
|
|
|
|
$stats = [
|
|
'applied_migrations' => count($applied),
|
|
'participants' => 0,
|
|
'stale_participants_removed' => 0,
|
|
'users' => 0,
|
|
'memberships' => 0,
|
|
];
|
|
|
|
$pdo->beginTransaction();
|
|
|
|
try {
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO tenants (slug, name, status, timezone, locale, currency_code)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
name = VALUES(name),
|
|
status = VALUES(status),
|
|
timezone = VALUES(timezone),
|
|
locale = VALUES(locale),
|
|
currency_code = VALUES(currency_code)'
|
|
);
|
|
$stmt->execute([$tenantSlug, $tenantName, 'active', 'Europe/Berlin', 'de-DE', 'EUR']);
|
|
$tenantId = m3_fetch_tenant_id($pdo, $tenantSlug);
|
|
|
|
$config = $pdo->query(
|
|
'SELECT KostenproStrich, paypaluse, paypallink, strichperweb
|
|
FROM kl_config
|
|
ORDER BY id
|
|
LIMIT 1'
|
|
)->fetch() ?: [
|
|
'KostenproStrich' => '0.20',
|
|
'paypaluse' => 0,
|
|
'paypallink' => '',
|
|
'strichperweb' => 1,
|
|
];
|
|
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO tenant_settings
|
|
(tenant_id, mark_price_cents, self_entry_enabled, paypal_enabled, paypal_url_template)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
mark_price_cents = VALUES(mark_price_cents),
|
|
self_entry_enabled = VALUES(self_entry_enabled),
|
|
paypal_enabled = VALUES(paypal_enabled),
|
|
paypal_url_template = VALUES(paypal_url_template)'
|
|
);
|
|
$stmt->execute([
|
|
$tenantId,
|
|
m3_decimal_to_cents($config['KostenproStrich']),
|
|
(int)$config['strichperweb'] === 1 ? 1 : 0,
|
|
(int)$config['paypaluse'] === 1 ? 1 : 0,
|
|
(string)$config['paypallink'],
|
|
]);
|
|
|
|
$members = $pdo->query(
|
|
'SELECT MitarbeiterID, Name, Email, paypalname, aktiv, admin
|
|
FROM kl_Mitarbeiter
|
|
ORDER BY MitarbeiterID'
|
|
)->fetchAll();
|
|
|
|
$upsertParticipant = $pdo->prepare(
|
|
'INSERT INTO participants
|
|
(tenant_id, display_name, email, email_norm, paypal_name, active, legacy_mitarbeiter_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
display_name = VALUES(display_name),
|
|
email = VALUES(email),
|
|
email_norm = VALUES(email_norm),
|
|
paypal_name = VALUES(paypal_name),
|
|
active = VALUES(active),
|
|
legacy_mitarbeiter_id = VALUES(legacy_mitarbeiter_id)'
|
|
);
|
|
$upsertMembership = $pdo->prepare(
|
|
'INSERT INTO tenant_memberships (tenant_id, user_id, role, status, joined_at)
|
|
VALUES (?, ?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
role = VALUES(role),
|
|
status = VALUES(status),
|
|
joined_at = COALESCE(tenant_memberships.joined_at, VALUES(joined_at))'
|
|
);
|
|
$linkParticipantUser = $pdo->prepare(
|
|
'UPDATE participants
|
|
SET user_id = ?
|
|
WHERE tenant_id = ?
|
|
AND legacy_mitarbeiter_id = ?'
|
|
);
|
|
|
|
foreach ($members as $member) {
|
|
$email = trim((string)$member['Email']);
|
|
$emailNorm = m3_email_norm($email);
|
|
$displayName = trim((string)$member['Name']);
|
|
if ($displayName === '') {
|
|
$displayName = $emailNorm ?? ('Mitarbeiter ' . (int)$member['MitarbeiterID']);
|
|
}
|
|
|
|
$upsertParticipant->execute([
|
|
$tenantId,
|
|
$displayName,
|
|
$email !== '' ? $email : null,
|
|
$emailNorm,
|
|
$member['paypalname'] !== null && trim((string)$member['paypalname']) !== ''
|
|
? trim((string)$member['paypalname'])
|
|
: null,
|
|
(int)$member['aktiv'] === 1 ? 1 : 0,
|
|
(int)$member['MitarbeiterID'],
|
|
]);
|
|
$stats['participants']++;
|
|
|
|
$isAdmin = (int)$member['admin'] === 1 && (int)$member['aktiv'] === 1;
|
|
$isOwner = $ownerEmailNorm !== null && $emailNorm === $ownerEmailNorm && (int)$member['aktiv'] === 1;
|
|
if ($emailNorm === null || (!$isAdmin && !$isOwner)) {
|
|
continue;
|
|
}
|
|
|
|
$userId = m3_upsert_user($pdo, $email, $emailNorm, $displayName);
|
|
$role = $isOwner ? 'owner' : 'admin';
|
|
$upsertMembership->execute([$tenantId, $userId, $role, 'active']);
|
|
$linkParticipantUser->execute([$userId, $tenantId, (int)$member['MitarbeiterID']]);
|
|
$stats['users']++;
|
|
$stats['memberships']++;
|
|
}
|
|
|
|
$removeStaleParticipants = $pdo->prepare(
|
|
'DELETE p
|
|
FROM participants p
|
|
LEFT JOIN kl_Mitarbeiter m
|
|
ON m.MitarbeiterID = p.legacy_mitarbeiter_id
|
|
WHERE p.tenant_id = ?
|
|
AND p.legacy_mitarbeiter_id IS NOT NULL
|
|
AND m.MitarbeiterID IS NULL'
|
|
);
|
|
$removeStaleParticipants->execute([$tenantId]);
|
|
$stats['stale_participants_removed'] = $removeStaleParticipants->rowCount();
|
|
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
$pdo->rollBack();
|
|
fwrite(STDERR, "M3 default tenant backfill failed: {$e->getMessage()}\n");
|
|
exit(1);
|
|
}
|
|
|
|
echo "M3 default tenant backfill complete.\n";
|
|
echo "Tenant: {$tenantSlug}\n";
|
|
echo "Applied migrations: {$stats['applied_migrations']}\n";
|
|
echo "Participants mirrored: {$stats['participants']}\n";
|
|
echo "Stale participants removed: {$stats['stale_participants_removed']}\n";
|
|
echo "Users upserted: {$stats['users']}\n";
|
|
echo "Memberships upserted: {$stats['memberships']}\n";
|