Darstellung alle Benutzer aktualisiert

This commit is contained in:
2026-04-09 12:01:19 +02:00
parent e6146da778
commit 5f4f962bb9
3 changed files with 252 additions and 29 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"sqltools.connections": [
{
"mysqlOptions": {
"authProtocol": "default",
"enableSsl": "Disabled"
},
"ssh": "Disabled",
"previewLimit": 50,
"server": "mysql2f5b.netcup.net",
"port": 3306,
"driver": "MySQL",
"username": "k25330_kaffeetest",
"password": "k0Zg48~g75634",
"database": "k25330_kaffeetest",
"name": "kaffeeliste Test"
}
]
}
+201 -27
View File
@@ -808,6 +808,68 @@ function app_members_support_payment_reference(PDO $pdo): bool
return app_table_has_column($pdo, 'members', 'payment_reference');
}
function app_ensure_member_rows_for_tenant(PDO $pdo, string $tenantId): void
{
if ($tenantId === '' || !scripts_table_exists($pdo, 'members') || !scripts_table_exists($pdo, 'tenant_users')) {
return;
}
$missingRows = app_query_all(
$pdo,
<<<'SQL'
SELECT
tu.id AS tenant_user_id,
tu.status AS tenant_user_status,
u.email,
u.display_name
FROM tenant_users tu
INNER JOIN users u ON u.id = tu.user_id
LEFT JOIN members m ON m.tenant_user_id = tu.id AND m.tenant_id = tu.tenant_id
WHERE tu.tenant_id = :tenant_id
AND m.id IS NULL
SQL,
['tenant_id' => $tenantId]
);
if ($missingRows === []) {
return;
}
$now = date('Y-m-d H:i:s');
foreach ($missingRows as $row) {
$status = ((string) ($row['tenant_user_status'] ?? 'active')) === 'inactive' ? 'inactive' : 'active';
$params = [
'id' => app_uuid(),
'tenant_id' => $tenantId,
'tenant_user_id' => (string) ($row['tenant_user_id'] ?? ''),
'display_name' => trim((string) ($row['display_name'] ?? '')) !== ''
? trim((string) ($row['display_name'] ?? ''))
: trim((string) ($row['email'] ?? 'Mitglied')),
'email' => strtolower(trim((string) ($row['email'] ?? ''))),
'status' => $status,
'created_at' => $now,
'updated_at' => $now,
];
if (app_members_support_payment_reference($pdo)) {
$params['payment_reference'] = null;
app_execute(
$pdo,
'INSERT INTO members (id, tenant_id, tenant_user_id, display_name, email, payment_reference, status, created_at, updated_at) VALUES (:id, :tenant_id, :tenant_user_id, :display_name, :email, :payment_reference, :status, :created_at, :updated_at)',
$params
);
continue;
}
app_execute(
$pdo,
'INSERT INTO members (id, tenant_id, tenant_user_id, display_name, email, status, created_at, updated_at) VALUES (:id, :tenant_id, :tenant_user_id, :display_name, :email, :status, :created_at, :updated_at)',
$params
);
}
}
function app_tenant_user_by_id(PDO $pdo, string $tenantUserId, string $tenantId): ?array
{
$paymentReferenceSelect = app_members_support_payment_reference($pdo)
@@ -3086,15 +3148,45 @@ SQL,
function app_members_for_tenant(PDO $pdo, string $tenantId): array
{
app_ensure_member_rows_for_tenant($pdo, $tenantId);
$paymentReferenceSelect = app_members_support_payment_reference($pdo)
? 'm.payment_reference,'
: 'NULL AS payment_reference,';
$sql = <<<'SQL'
$tenantUserSql = <<<'SQL'
SELECT
m.id,
tu.id AS tenant_user_id,
tu.user_id,
COALESCE(m.display_name, u.display_name) AS display_name,
COALESCE(m.email, u.email) AS email,
__PAYMENT_REFERENCE__
COALESCE(m.status, tu.status) AS status,
u.display_name AS user_display_name,
GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS roles,
GROUP_CONCAT(DISTINCT r.role_key ORDER BY r.role_key SEPARATOR ',') AS role_keys,
COALESCE((
SELECT SUM(le.amount)
FROM ledger_entries le
WHERE le.member_id = m.id
AND le.tenant_id = tu.tenant_id
), 0) AS current_balance
FROM tenant_users tu
INNER JOIN users u ON u.id = tu.user_id
LEFT JOIN members m ON m.tenant_user_id = tu.id AND m.tenant_id = tu.tenant_id
LEFT JOIN tenant_user_roles tur ON tur.tenant_user_id = tu.id
LEFT JOIN roles r ON r.id = tur.role_id
WHERE tu.tenant_id = :tenant_id
GROUP BY m.id, tu.id, tu.user_id, m.display_name, m.email, payment_reference, m.status, tu.status, u.display_name, u.email
ORDER BY COALESCE(m.display_name, u.display_name) ASC
SQL;
$memberOnlySql = <<<'SQL'
SELECT
m.id,
m.tenant_user_id,
tu.user_id,
m.display_name,
m.email,
__PAYMENT_REFERENCE__
@@ -3109,16 +3201,70 @@ SELECT
AND le.tenant_id = m.tenant_id
), 0) AS current_balance
FROM members m
LEFT JOIN tenant_users tu ON tu.id = m.tenant_user_id
LEFT JOIN tenant_users tu ON tu.id = m.tenant_user_id AND tu.tenant_id = m.tenant_id
LEFT JOIN users u ON u.id = tu.user_id
LEFT JOIN tenant_user_roles tur ON tur.tenant_user_id = tu.id
LEFT JOIN roles r ON r.id = tur.role_id
WHERE m.tenant_id = :tenant_id
GROUP BY m.id, tu.id, tu.user_id, m.display_name, m.email, payment_reference, m.status, u.display_name
AND (
m.tenant_user_id IS NULL
OR tu.id IS NULL
)
GROUP BY m.id, m.tenant_user_id, tu.user_id, m.display_name, m.email, payment_reference, m.status, u.display_name
ORDER BY m.display_name ASC
SQL;
return app_query_all($pdo, str_replace('__PAYMENT_REFERENCE__', $paymentReferenceSelect, $sql), ['tenant_id' => $tenantId]);
$rows = app_query_all(
$pdo,
str_replace('__PAYMENT_REFERENCE__', $paymentReferenceSelect, $tenantUserSql),
['tenant_id' => $tenantId]
);
foreach (
app_query_all(
$pdo,
str_replace('__PAYMENT_REFERENCE__', $paymentReferenceSelect, $memberOnlySql),
['tenant_id' => $tenantId]
) as $row
) {
$rows[] = $row;
}
$deduped = [];
foreach ($rows as $row) {
$id = trim((string) ($row['id'] ?? ''));
$tenantUserId = trim((string) ($row['tenant_user_id'] ?? ''));
$email = strtolower(trim((string) ($row['email'] ?? '')));
$displayName = trim((string) ($row['display_name'] ?? ''));
$key = $id !== ''
? 'member:' . $id
: ($tenantUserId !== ''
? 'tenant-user:' . $tenantUserId
: ($email !== '' ? 'email:' . $email : 'name:' . $displayName));
if (!isset($deduped[$key])) {
$deduped[$key] = $row;
continue;
}
$existing = $deduped[$key];
$existingHasMemberId = trim((string) ($existing['id'] ?? '')) !== '';
$rowHasMemberId = $id !== '';
if (!$existingHasMemberId && $rowHasMemberId) {
$deduped[$key] = $row;
}
}
$result = array_values($deduped);
usort(
$result,
static fn(array $left, array $right): int => strcasecmp(
trim((string) ($left['display_name'] ?? '')),
trim((string) ($right['display_name'] ?? ''))
)
);
return $result;
}
function app_normalize_datetime_input(?string $value): string
@@ -3205,32 +3351,50 @@ SQL,
function app_members_for_scope(PDO $pdo, string $tenantId, string $scope = 'all'): array
{
$coffeeActiveSql = app_finance_entry_active_sql($pdo, 'coffee_entries', 'ce');
$rows = app_query_all(
$members = array_values(array_filter(
app_members_for_tenant($pdo, $tenantId),
static function (array $member): bool {
$status = strtolower(trim((string) ($member['status'] ?? 'active')));
return !in_array($status, ['inactive', 'disabled', 'archived'], true);
}
));
if ($members === []) {
return [];
}
$recentStrokeRows = app_query_all(
$pdo,
str_replace(
'__COFFEE_ACTIVE__',
$coffeeActiveSql,
<<<'SQL'
SELECT
m.id,
m.display_name,
COALESCE((
SELECT SUM(ce.strokes)
FROM coffee_entries ce
WHERE ce.tenant_id = m.tenant_id
AND ce.member_id = m.id
AND __COFFEE_ACTIVE__
AND ce.booked_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 100 DAY)
), 0) AS recent_strokes
FROM members m
WHERE m.tenant_id = :tenant_id
AND m.status = 'active'
ORDER BY m.display_name ASC
SQL,
ce.member_id,
COALESCE(SUM(ce.strokes), 0) AS recent_strokes
FROM coffee_entries ce
WHERE ce.tenant_id = :tenant_id
AND __COFFEE_ACTIVE__
AND ce.booked_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 100 DAY)
GROUP BY ce.member_id
SQL
),
['tenant_id' => $tenantId]
);
$recentStrokeMap = [];
foreach ($recentStrokeRows as $row) {
$recentStrokeMap[(string) ($row['member_id'] ?? '')] = (int) ($row['recent_strokes'] ?? 0);
}
$rows = array_map(
static function (array $member) use ($recentStrokeMap): array {
$member['recent_strokes'] = $recentStrokeMap[(string) ($member['id'] ?? '')] ?? 0;
return $member;
},
$members
);
if ($scope === 'front') {
return array_values(array_filter($rows, static fn(array $row): bool => (int) ($row['recent_strokes'] ?? 0) >= 10));
}
@@ -3279,16 +3443,26 @@ function app_paypal_amount_links(array $settings, float $balance): array
function app_ledger_for_tenant(PDO $pdo, string $tenantId): array
{
$coffeeStatusSelect = app_finance_entries_support_status($pdo, 'coffee_entries')
$coffeeSupportsStatus = app_finance_entries_support_status($pdo, 'coffee_entries');
$paymentSupportsStatus = app_finance_entries_support_status($pdo, 'payment_entries');
$coffeeStatusSelect = $coffeeSupportsStatus
? 'ce.status AS coffee_reference_status,'
: "'booked' AS coffee_reference_status,";
$coffeeCancelledSelect = app_finance_entries_support_status($pdo, 'coffee_entries')
$coffeeCancelledSelect = $coffeeSupportsStatus
? 'ce.cancelled_at AS coffee_reference_cancelled_at,'
: 'NULL AS coffee_reference_cancelled_at,';
$paymentStatusSelect = app_finance_entries_support_status($pdo, 'payment_entries')
$paymentStatusSelect = $paymentSupportsStatus
? 'pe.status AS payment_reference_status, pe.cancelled_at AS payment_cancelled_at,'
: "'booked' AS payment_reference_status, NULL AS payment_cancelled_at,";
$referenceStatusSql = app_finance_entries_support_status($pdo, 'coffee_entries') || app_finance_entries_support_status($pdo, 'payment_entries')
$referenceCancelledAtSql = match (true) {
$coffeeSupportsStatus && $paymentSupportsStatus => 'COALESCE(ce.cancelled_at, pe.cancelled_at) AS reference_cancelled_at,',
$coffeeSupportsStatus => 'ce.cancelled_at AS reference_cancelled_at,',
$paymentSupportsStatus => 'pe.cancelled_at AS reference_cancelled_at,',
default => 'NULL AS reference_cancelled_at,',
};
$referenceStatusSql = $coffeeSupportsStatus || $paymentSupportsStatus
? <<<'SQL'
CASE
WHEN le.reference_type = 'coffee_entry' THEN COALESCE(ce.status, 'booked')
@@ -3300,7 +3474,7 @@ SQL
$sql = str_replace(
['__COFFEE_STATUS__', '__COFFEE_CANCELLED__', '__PAYMENT_STATUS__', '__REFERENCE_STATUS__', '__REFERENCE_CANCELLED_AT__'],
[$coffeeStatusSelect, $coffeeCancelledSelect, $paymentStatusSelect, $referenceStatusSql, 'COALESCE(coffee_reference_cancelled_at, payment_cancelled_at) AS reference_cancelled_at,'],
[$coffeeStatusSelect, $coffeeCancelledSelect, $paymentStatusSelect, $referenceStatusSql, $referenceCancelledAtSql],
<<<'SQL'
SELECT
le.id,
@@ -3331,8 +3505,8 @@ SQL
function app_payments_for_tenant(PDO $pdo, string $tenantId): array
{
$paymentStatusSelect = app_finance_entries_support_status($pdo, 'payment_entries')
? 'pe.status, pe.cancelled_at, pe.cancellation_reason,'
: "'booked' AS status, NULL AS cancelled_at, NULL AS cancellation_reason,";
? 'pe.status, pe.cancelled_at, pe.cancellation_reason'
: "'booked' AS status, NULL AS cancelled_at, NULL AS cancellation_reason";
$sql = str_replace(
'__PAYMENT_STATUS__',
+32 -2
View File
@@ -362,6 +362,21 @@ if ($auth !== null && $pdo instanceof PDO) {
? (string) ($_GET['scope'] ?? 'all')
: 'all';
$ledgerMembers = app_members_for_scope($pdo, (string) $auth['tenant_id'], $ledgerScope);
if ($ledgerMembers === [] && $members !== []) {
$ledgerMembers = array_map(
static function (array $member): array {
$member['recent_strokes'] = (int) ($member['recent_strokes'] ?? 0);
return $member;
},
array_values(array_filter(
$members,
static function (array $member): bool {
$status = strtolower(trim((string) ($member['status'] ?? 'active')));
return !in_array($status, ['inactive', 'disabled', 'archived'], true);
}
))
);
}
}
if ($page === 'payments') {
@@ -369,6 +384,21 @@ if ($auth !== null && $pdo instanceof PDO) {
? (string) ($_GET['scope'] ?? 'all')
: 'all';
$paymentMembers = app_members_for_scope($pdo, (string) $auth['tenant_id'], $paymentScope);
if ($paymentMembers === [] && $members !== []) {
$paymentMembers = array_map(
static function (array $member): array {
$member['recent_strokes'] = (int) ($member['recent_strokes'] ?? 0);
return $member;
},
array_values(array_filter(
$members,
static function (array $member): bool {
$status = strtolower(trim((string) ($member['status'] ?? 'active')));
return !in_array($status, ['inactive', 'disabled', 'archived'], true);
}
))
);
}
}
if ($page === 'content' || $page === 'dashboard') {
@@ -1505,7 +1535,7 @@ $marketing = app_marketing_messages();
</div>
<form method="post" action="/ledger/" class="stack">
<input type="hidden" name="action" value="bulk-record-coffee">
<input type="hidden" name="scope" value="<?= h((string) ($_GET['scope'] ?? 'all')) ?>">
<input type="hidden" name="scope" value="<?= h($ledgerScope) ?>">
<div class="grid">
<label>Preis pro Strich<input type="number" name="unit_price" min="0.01" step="0.01" value="<?= h((string) ($tenantSettings['default_unit_price'] ?? '0.50')) ?>"></label>
<label>Buchungszeit<input type="datetime-local" name="booked_at" value="<?= date('Y-m-d\TH:i') ?>"></label>
@@ -1560,7 +1590,7 @@ $marketing = app_marketing_messages();
</div>
<form method="post" action="/payments/" class="stack">
<input type="hidden" name="action" value="bulk-record-payment">
<input type="hidden" name="scope" value="<?= h((string) ($_GET['scope'] ?? 'all')) ?>">
<input type="hidden" name="scope" value="<?= h($paymentScope) ?>">
<div class="grid">
<label>Zahlungsart<select name="payment_method"><option value="manual">Manuell</option><option value="paypal">PayPal</option><option value="bank">Bank</option></select></label>
<label>Buchungszeit<input type="datetime-local" name="booked_at" value="<?= date('Y-m-d\TH:i') ?>"></label>