M3 SaaS-Basis mit Default-Tenant vorbereiten
- Tenant/User/Membership/Participant-Tabellen additiv migrieren - Default-Tenant-Backfill fuer Legacy-Mitarbeiter und Admins ergaenzen - M3-Kontrollskript und Plan-Dokumentation aktualisieren
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?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,
|
||||
'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)'
|
||||
);
|
||||
$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']++;
|
||||
}
|
||||
|
||||
$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 "Users upserted: {$stats['users']}\n";
|
||||
echo "Memberships upserted: {$stats['memberships']}\n";
|
||||
Reference in New Issue
Block a user