update project
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Support\Architecture\ArchitectureScanner;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class ArchitectureScanCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:architecture-scan {--format=console : console or json} {--fail-on=error : error, warning, or none}';
|
||||
protected $description = 'Scan the codebase for modular architecture boundary violations.';
|
||||
|
||||
public function handle(ArchitectureScanner $scanner): int
|
||||
{
|
||||
$violations = $scanner->scan();
|
||||
|
||||
if ($this->option('format') === 'json') {
|
||||
$this->line(json_encode(array_map(fn ($v) => $v->toArray(), $violations), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
} else {
|
||||
if ($violations === []) {
|
||||
$this->info('No architecture violations found. Suspiciously civilized.');
|
||||
}
|
||||
foreach ($violations as $violation) {
|
||||
$this->line(sprintf(
|
||||
'[%s] %s %s:%d %s',
|
||||
strtoupper($violation->severity),
|
||||
$violation->ruleId,
|
||||
$violation->file,
|
||||
$violation->line,
|
||||
$violation->message
|
||||
));
|
||||
if ($violation->suggestedFix) {
|
||||
$this->line(' Fix: '.$violation->suggestedFix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$failOn = (string) $this->option('fail-on');
|
||||
if ($failOn === 'none') {
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
foreach ($violations as $violation) {
|
||||
if ($violation->severity === 'error' || ($failOn === 'warning' && $violation->severity === 'warning')) {
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Support\Architecture\DependencyMapGenerator;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
final class DependencyMapCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:dependency-map {--markdown : Also write docs/architecture/dependency-map.md}';
|
||||
protected $description = 'Generate a module dependency map for governance review.';
|
||||
|
||||
public function handle(DependencyMapGenerator $generator): int
|
||||
{
|
||||
$map = $generator->generate();
|
||||
Storage::disk('local')->put('generated/dependency-map.json', json_encode($map, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
if ($this->option('markdown')) {
|
||||
$lines = ['# Dependency Map', '', 'Generated from PHP imports. Treat this as a smoke alarm, not a philosophical essay.', ''];
|
||||
foreach ($map['files'] as $file => $imports) {
|
||||
$lines[] = '## `'.$file.'`';
|
||||
$lines[] = '';
|
||||
foreach ($imports as $import) {
|
||||
$lines[] = '- `'.$import.'`';
|
||||
}
|
||||
$lines[] = '';
|
||||
}
|
||||
@mkdir(base_path('docs/architecture'), 0777, true);
|
||||
file_put_contents(base_path('docs/architecture/dependency-map.md'), implode(PHP_EOL, $lines).PHP_EOL);
|
||||
}
|
||||
|
||||
$this->info('Dependency map generated. Now the imports have fewer places to hide.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class DocumentationCoverageCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:docs-coverage {--inventory=storage/app/generated/api-route-inventory.json} {--openapi=docs/openapi.phase8.yaml}';
|
||||
protected $description = 'Check that canonical routes and deprecated routes are represented in documentation artifacts.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$inventoryPath = base_path((string) $this->option('inventory'));
|
||||
$openApiPath = base_path((string) $this->option('openapi'));
|
||||
$deprecationsPath = base_path('docs/deprecations.md');
|
||||
|
||||
if (! is_file($inventoryPath) || ! is_file($openApiPath)) {
|
||||
$this->error('Inventory or OpenAPI file is missing. Documentation cannot be trusted by vibes alone.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$rows = json_decode(file_get_contents($inventoryPath) ?: '[]', true) ?: [];
|
||||
$openApi = file_get_contents($openApiPath) ?: '';
|
||||
$deprecations = is_file($deprecationsPath) ? (file_get_contents($deprecationsPath) ?: '') : '';
|
||||
$failures = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$uri = '/'.ltrim((string) ($row['uri'] ?? ''), '/');
|
||||
$status = (string) ($row['canonical_status'] ?? 'unknown');
|
||||
if ($status === 'canonical' && ! str_contains($openApi, $uri)) {
|
||||
$failures[] = $uri.' is canonical but missing from OpenAPI.';
|
||||
}
|
||||
if ($status === 'deprecated' && ! str_contains($deprecations, $uri)) {
|
||||
$failures[] = $uri.' is deprecated but missing from docs/deprecations.md.';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($failures as $failure) {
|
||||
$this->error($failure);
|
||||
}
|
||||
|
||||
return $failures === [] ? self::SUCCESS : self::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
final class GenerateApiRouteInventoryCommand extends Command
|
||||
{
|
||||
protected $signature = 'api:route-inventory {--markdown : Also write docs/api-route-inventory.md}';
|
||||
protected $description = 'Generate API route inventory for controller cleanup and OpenAPI coverage.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$classification = config('api_controller_classification.controllers', []);
|
||||
$rows = collect(RouteFacade::getRoutes())
|
||||
->filter(fn (Route $route) => str_starts_with($route->uri(), 'api/'))
|
||||
->map(function (Route $route) use ($classification): array {
|
||||
$action = $route->getActionName();
|
||||
[$controller, $method] = str_contains($action, '@') ? explode('@', $action, 2) : [$action, '__invoke'];
|
||||
$meta = $classification[$controller] ?? [];
|
||||
|
||||
return [
|
||||
'methods' => array_values(array_diff($route->methods(), ['HEAD'])),
|
||||
'uri' => $route->uri(),
|
||||
'name' => $route->getName(),
|
||||
'controller' => $controller,
|
||||
'action' => $method,
|
||||
'middleware' => $route->gatherMiddleware(),
|
||||
'module' => $meta['module'] ?? 'unknown',
|
||||
'risk' => $meta['risk'] ?? 'unknown',
|
||||
'canonical_status' => $meta['status'] ?? config('api_controller_classification.default_status', 'unknown'),
|
||||
'contract' => $meta['contract'] ?? null,
|
||||
'auth_required' => collect($route->gatherMiddleware())->contains(fn ($m) => str_starts_with((string) $m, 'auth')),
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
|
||||
Storage::disk('local')->put('generated/api-route-inventory.json', json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
if ($this->option('markdown')) {
|
||||
$markdown = ["# API Route Inventory", "", "| Method | URI | Name | Controller | Action | Module | Status | Risk | Auth |", "|---|---|---|---|---|---|---|---|---|"];
|
||||
foreach ($rows as $row) {
|
||||
$markdown[] = sprintf('| %s | `%s` | `%s` | `%s` | `%s` | %s | %s | %s | %s |',
|
||||
implode(',', $row['methods']), $row['uri'], $row['name'] ?? '', $row['controller'], $row['action'], $row['module'], $row['canonical_status'], $row['risk'], $row['auth_required'] ? 'yes' : 'no'
|
||||
);
|
||||
}
|
||||
@mkdir(base_path('docs'), 0777, true);
|
||||
file_put_contents(base_path('docs/api-route-inventory.md'), implode(PHP_EOL, $markdown).PHP_EOL);
|
||||
}
|
||||
|
||||
$this->info('API route inventory written. Unknown routes are now visible, the bare minimum for civilization.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
final class LegacyUnsafeRouteAuditCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:legacy-unsafe-route-audit';
|
||||
protected $description = 'Audit known unsafe legacy routes that must be disabled or removed before final rollout.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$items = config('release_readiness.unsafe_legacy_routes', []);
|
||||
$report = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'items' => array_map(fn (string $key): array => [
|
||||
'key' => $key,
|
||||
'status' => 'requires_project_mapping',
|
||||
'note' => 'Map this key to actual legacy route names in the real app and disable/remove before production readiness signoff.',
|
||||
], $items),
|
||||
];
|
||||
|
||||
$path = storage_path('app/generated/legacy-unsafe-route-audit.json');
|
||||
File::ensureDirectoryExists(dirname($path));
|
||||
File::put($path, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
$this->line(json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Support\Release\FeatureFlagRegistry;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
final class ReleaseFeatureFlagsCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:release-feature-flags {--format=json}';
|
||||
protected $description = 'Export modular rollout feature flag map.';
|
||||
|
||||
public function handle(FeatureFlagRegistry $registry): int
|
||||
{
|
||||
$data = ['flags' => $registry->export()];
|
||||
$path = storage_path('app/generated/release-feature-flags.json');
|
||||
File::ensureDirectoryExists(dirname($path));
|
||||
File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->info('Feature flag rollout map written to '.$path);
|
||||
$this->line(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Support\Release\MigrationValidationRunner;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
final class ReleaseMigrationValidateCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:release-migration-validate {--module=all}';
|
||||
protected $description = 'Run modular migration validation checks.';
|
||||
|
||||
public function handle(MigrationValidationRunner $runner): int
|
||||
{
|
||||
$results = array_map(fn ($result): array => $result->toArray(), $runner->runAll());
|
||||
$summary = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'results' => $results,
|
||||
'status' => collect($results)->every(fn ($result): bool => $result['status'] === 'pass') ? 'pass' : 'fail',
|
||||
];
|
||||
|
||||
$path = storage_path('app/generated/release-validation/validation-summary.json');
|
||||
File::ensureDirectoryExists(dirname($path));
|
||||
File::put($path, json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->info('Migration validation summary written to '.$path);
|
||||
$this->line(json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return $summary['status'] === 'pass' ? self::SUCCESS : self::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Support\Release\ReleaseReadinessGate;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class ReleaseReadinessGateCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:release-readiness-gate {--warning-mode}';
|
||||
protected $description = 'Evaluate modular release readiness artifacts and gates.';
|
||||
|
||||
public function handle(ReleaseReadinessGate $gate): int
|
||||
{
|
||||
$result = $gate->evaluate();
|
||||
$this->line(json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
if ($result['status'] !== 'pass') {
|
||||
$this->warn('Release readiness gate failed. Fix artifacts or run in warning mode only for early rollout rehearsals.');
|
||||
return $this->option('warning-mode') ? self::SUCCESS : self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('Release readiness gate passed. Disturbingly competent.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Support\Release\ShadowComparisonRunner;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
final class ReleaseShadowCompareCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:release-shadow-compare {--module=all}';
|
||||
protected $description = 'Run shadow comparisons between legacy and modular outputs.';
|
||||
|
||||
public function handle(ShadowComparisonRunner $runner): int
|
||||
{
|
||||
$results = array_map(fn ($result): array => $result->toArray(), $runner->runAll());
|
||||
$summary = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'results' => $results,
|
||||
'status' => collect($results)->contains(fn ($result): bool => $result['status'] === 'fail') ? 'fail' : 'warning',
|
||||
];
|
||||
|
||||
$path = storage_path('app/generated/shadow-comparisons/shadow-summary.json');
|
||||
File::ensureDirectoryExists(dirname($path));
|
||||
File::put($path, json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->warn('Shadow comparisons are scaffolded. Wire legacy comparisons before production cutover.');
|
||||
$this->line(json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class RouteInventoryCheckCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:route-inventory-check {--path=storage/app/generated/api-route-inventory.json}';
|
||||
protected $description = 'Check generated route inventory for governance metadata gaps.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$path = base_path((string) $this->option('path'));
|
||||
if (! is_file($path)) {
|
||||
$this->error('Route inventory missing. Run php artisan api:route-inventory first.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$rows = json_decode(file_get_contents($path) ?: '[]', true) ?: [];
|
||||
$failures = [];
|
||||
foreach ($rows as $row) {
|
||||
$uri = (string) ($row['uri'] ?? '');
|
||||
$methods = $row['methods'] ?? [];
|
||||
$mutation = count(array_intersect($methods, ['POST', 'PUT', 'PATCH', 'DELETE'])) > 0;
|
||||
if (($row['module'] ?? 'unknown') === 'unknown') {
|
||||
$failures[] = $uri.' is missing module classification.';
|
||||
}
|
||||
if ($mutation && empty($row['auth_required'])) {
|
||||
$failures[] = $uri.' is a mutation route without auth metadata.';
|
||||
}
|
||||
if (str_contains($uri, 'islamic-sunday-school') && ! str_contains(json_encode($row['middleware'] ?? []), 'domain.profile')) {
|
||||
$failures[] = $uri.' is an extension route without domain profile middleware.';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($failures as $failure) {
|
||||
$this->error($failure);
|
||||
}
|
||||
|
||||
return $failures === [] ? self::SUCCESS : self::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Contracts\AttendancePolicyContract;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class IslamicSundaySchoolAttendancePolicy implements AttendancePolicyContract
|
||||
{
|
||||
public function isSchoolDay(SchoolContext $context, DateTimeInterface $date): bool
|
||||
{
|
||||
return (int) $date->format('N') === 7;
|
||||
}
|
||||
|
||||
public function determineStatus(SchoolContext $context, DateTimeInterface $scanTime, array $session): string
|
||||
{
|
||||
$startsAt = $session['starts_at'] ?? null;
|
||||
if ($startsAt instanceof DateTimeInterface && $scanTime > $startsAt) {
|
||||
return 'late';
|
||||
}
|
||||
|
||||
return 'present';
|
||||
}
|
||||
|
||||
public function duplicateScanWindowSeconds(SchoolContext $context): int
|
||||
{
|
||||
return 600;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Attendance\Policies\DefaultAttendancePolicy;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
|
||||
final class IslamicSundaySchoolAttendancePolicy extends DefaultAttendancePolicy
|
||||
{
|
||||
public function duplicateScanWindowSeconds(SchoolContext $context): int
|
||||
{
|
||||
return 180;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Attendance\Contracts\AttendanceStatusPolicyContract;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceStatusResult;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class IslamicSundaySchoolAttendanceStatusPolicy implements AttendanceStatusPolicyContract
|
||||
{
|
||||
public function statusForScan(SchoolContext $context, AttendanceSessionView $session, DateTimeInterface $timestamp): AttendanceStatusResult
|
||||
{
|
||||
$lateThreshold = $session->startsAt->modify('+15 minutes');
|
||||
$status = $timestamp > $lateThreshold ? 'late' : 'present';
|
||||
|
||||
return new AttendanceStatusResult($status, $status === 'late', $status === 'late' ? 'Configured extension late window.' : null);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Policies;
|
||||
|
||||
final class IslamicSundaySchoolSessionPolicy
|
||||
{
|
||||
public function supportsProgramSessionType(string $type): bool
|
||||
{
|
||||
return in_array($type, ['program', 'group', 'class', 'event'], true);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Providers;
|
||||
|
||||
use App\Domain\IslamicSundaySchool\Attendance\Policies\IslamicSundaySchoolAttendancePolicy;
|
||||
use App\Domain\IslamicSundaySchool\Attendance\Policies\IslamicSundaySchoolAttendanceStatusPolicy;
|
||||
use App\Domain\IslamicSundaySchool\Attendance\Services\IslamicSundaySchoolSessionResolver;
|
||||
use App\Domain\SchoolCore\Attendance\Contracts\AttendancePolicyContract;
|
||||
use App\Domain\SchoolCore\Attendance\Contracts\AttendanceSessionResolverContract;
|
||||
use App\Domain\SchoolCore\Attendance\Contracts\AttendanceStatusPolicyContract;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
final class IslamicSundaySchoolAttendanceServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->bind(AttendancePolicyContract::class, IslamicSundaySchoolAttendancePolicy::class);
|
||||
$this->app->bind(AttendanceStatusPolicyContract::class, IslamicSundaySchoolAttendanceStatusPolicy::class);
|
||||
$this->app->bind(AttendanceSessionResolverContract::class, IslamicSundaySchoolSessionResolver::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Attendance
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Reports;
|
||||
|
||||
final class IslamicSundaySchoolAttendanceReportBuilder
|
||||
{
|
||||
public function labels(): array
|
||||
{
|
||||
return [
|
||||
'present' => 'Present',
|
||||
'late' => 'Late',
|
||||
'absent' => 'Absent',
|
||||
];
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Services;
|
||||
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class IslamicSundaySchoolLateArrivalEvaluator
|
||||
{
|
||||
public function isLate(AttendanceSessionView $session, DateTimeInterface $timestamp): bool
|
||||
{
|
||||
return $timestamp > $session->startsAt->modify('+15 minutes');
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Attendance\Services;
|
||||
|
||||
use App\Domain\SchoolCore\Attendance\Contracts\AttendanceSessionResolverContract;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSubjectView;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use App\Domain\SchoolCore\Attendance\Services\AttendanceSessionResolver;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class IslamicSundaySchoolSessionResolver implements AttendanceSessionResolverContract
|
||||
{
|
||||
public function __construct(private AttendanceSessionResolver $fallback) {}
|
||||
|
||||
public function resolveForScan(SchoolContext $context, AttendanceSubjectView $subject, DateTimeInterface $timestamp): AttendanceSessionView
|
||||
{
|
||||
return $this->fallback->resolveForScan($context, $subject, $timestamp);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Policies; use App\Domain\SchoolCore\Communication\Policies\DefaultCommunicationPreferencePolicy; final class IslamicSundaySchoolCommunicationPreferencePolicy extends DefaultCommunicationPreferencePolicy {}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Policies; final class IslamicSundaySchoolRecipientAuthorizationPolicy {}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Providers; use Illuminate\Support\ServiceProvider; use App\Domain\IslamicSundaySchool\Communication\Resolvers\{SundayClassRecipientResolver,VolunteerTeamRecipientResolver,MasjidFamilyRecipientResolver,VolunteerRecipientResolver,IslamicSundaySchoolProgramRecipientResolver}; final class IslamicSundaySchoolCommunicationServiceProvider extends ServiceProvider { public function register(): void { $this->app->tag([SundayClassRecipientResolver::class,VolunteerTeamRecipientResolver::class,MasjidFamilyRecipientResolver::class,VolunteerRecipientResolver::class,IslamicSundaySchoolProgramRecipientResolver::class], 'communication.recipient_resolvers'); } }
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Resolvers; use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Communication\Contracts\RecipientResolverContract; use App\Domain\SchoolCore\Communication\DTO\{RecipientData,RecipientQueryData}; use App\Domain\SchoolCore\Communication\Support\RecipientSet; final class IslamicSundaySchoolProgramRecipientResolver implements RecipientResolverContract { public function supports(RecipientQueryData $query): bool { return $query->audienceType === 'program_attendees'; } public function resolve(SchoolContext $context, RecipientQueryData $query): RecipientSet { return new RecipientSet([new RecipientData('guardian',$context->schoolId*2000+1,'Extension Recipient','program_attendees@example.test','+15555550200',$query->language,[],null,null,$context->schoolId,[$query->channel->value],[],['extension'=>'islamic_sunday_school'],['program_attendees'])]); } }
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Resolvers; use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Communication\Contracts\RecipientResolverContract; use App\Domain\SchoolCore\Communication\DTO\{RecipientData,RecipientQueryData}; use App\Domain\SchoolCore\Communication\Support\RecipientSet; final class MasjidFamilyRecipientResolver implements RecipientResolverContract { public function supports(RecipientQueryData $query): bool { return $query->audienceType === 'masjid_family'; } public function resolve(SchoolContext $context, RecipientQueryData $query): RecipientSet { return new RecipientSet([new RecipientData('guardian',$context->schoolId*2000+1,'Extension Recipient','masjid_family@example.test','+15555550200',$query->language,[],null,null,$context->schoolId,[$query->channel->value],[],['extension'=>'islamic_sunday_school'],['masjid_family'])]); } }
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Resolvers; use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Communication\Contracts\RecipientResolverContract; use App\Domain\SchoolCore\Communication\DTO\{RecipientData,RecipientQueryData}; use App\Domain\SchoolCore\Communication\Support\RecipientSet; final class SundayClassRecipientResolver implements RecipientResolverContract { public function supports(RecipientQueryData $query): bool { return $query->audienceType === 'sunday_class'; } public function resolve(SchoolContext $context, RecipientQueryData $query): RecipientSet { return new RecipientSet([new RecipientData('guardian',$context->schoolId*2000+1,'Extension Recipient','sunday_class@example.test','+15555550200',$query->language,[],null,null,$context->schoolId,[$query->channel->value],[],['extension'=>'islamic_sunday_school'],['sunday_class'])]); } }
|
||||
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Resolvers; use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Communication\Contracts\RecipientResolverContract; use App\Domain\SchoolCore\Communication\DTO\{RecipientData,RecipientQueryData}; use App\Domain\SchoolCore\Communication\Support\RecipientSet; final class VolunteerRecipientResolver implements RecipientResolverContract { public function supports(RecipientQueryData $query): bool { return $query->audienceType === 'volunteers'; } public function resolve(SchoolContext $context, RecipientQueryData $query): RecipientSet { return new RecipientSet([new RecipientData('guardian',$context->schoolId*2000+1,'Extension Recipient','volunteers@example.test','+15555550200',$query->language,[],null,null,$context->schoolId,[$query->channel->value],[],['extension'=>'islamic_sunday_school'],['volunteers'])]); } }
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Resolvers; use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Communication\Contracts\RecipientResolverContract; use App\Domain\SchoolCore\Communication\DTO\{RecipientData,RecipientQueryData}; use App\Domain\SchoolCore\Communication\Support\RecipientSet; final class VolunteerTeamRecipientResolver implements RecipientResolverContract { public function supports(RecipientQueryData $query): bool { return $query->audienceType === 'volunteer_team'; } public function resolve(SchoolContext $context, RecipientQueryData $query): RecipientSet { return new RecipientSet([new RecipientData('guardian',$context->schoolId*2000+1,'Extension Recipient','volunteer_team@example.test','+15555550200',$query->language,[],null,null,$context->schoolId,[$query->channel->value],[],['extension'=>'islamic_sunday_school'],['volunteer_team'])]); } }
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace App\Domain\IslamicSundaySchool\Communication\Templates; final class IslamicSundaySchoolTemplateCatalog { public function templates(): array { return ['program_class_reminder'=>['category'=>'event'], 'volunteer_reminder'=>['category'=>'event'], 'attendance_follow_up'=>['category'=>'attendance']]; } }
|
||||
@@ -0,0 +1,3 @@
|
||||
# Contracts
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Curriculum
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Families
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Finance\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Finance\Contracts\PaymentAllocationPolicyContract;
|
||||
use App\Domain\SchoolCore\Finance\Support\Money;
|
||||
|
||||
final class IslamicSundaySchoolPaymentAllocationPolicy implements PaymentAllocationPolicyContract
|
||||
{
|
||||
public function allocate(SchoolContext $context, array $invoiceView, Money $paymentAmount): array
|
||||
{
|
||||
return [['invoice_id'=>$invoiceView['id'] ?? null,'amount_minor'=>$paymentAmount->minorAmount(),'currency'=>$paymentAmount->currency(),'strategy'=>'extension_policy_family_program_order']];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Finance\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Finance\Contracts\TuitionPolicyContract;
|
||||
|
||||
final class IslamicSundaySchoolTuitionPolicy implements TuitionPolicyContract
|
||||
{
|
||||
public function calculateExpectedCharges(SchoolContext $context, array $studentFinanceProfile): array
|
||||
{
|
||||
return ['profile'=>$context->domainProfile,'charges'=>[],'labels_are_extension_owned'=>true];
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Finance\Providers;
|
||||
|
||||
use App\Domain\IslamicSundaySchool\Finance\Policies\IslamicSundaySchoolPaymentAllocationPolicy;
|
||||
use App\Domain\IslamicSundaySchool\Finance\Policies\IslamicSundaySchoolTuitionPolicy;
|
||||
use App\Domain\SchoolCore\Finance\Contracts\PaymentAllocationPolicyContract;
|
||||
use App\Domain\SchoolCore\Finance\Contracts\TuitionPolicyContract;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
final class IslamicSundaySchoolFinanceServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
if (! (bool) config('school_context.extensions.islamic_sunday_school.enabled', false)) { return; }
|
||||
$this->app->bind(TuitionPolicyContract::class, IslamicSundaySchoolTuitionPolicy::class);
|
||||
$this->app->bind(PaymentAllocationPolicyContract::class, IslamicSundaySchoolPaymentAllocationPolicy::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Islamic Sunday School Finance Extension
|
||||
|
||||
Owns extension labels, tuition policies, allocation policies, scholarships, and local reports. It depends on SchoolCore Finance contracts. SchoolCore Finance must not depend back on this namespace.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Finance\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Finance\Repositories\FinanceReadRepository;
|
||||
|
||||
final class IslamicSundaySchoolFinanceReportBuilder
|
||||
{
|
||||
public function __construct(private readonly FinanceReadRepository $finance) {}
|
||||
public function outstandingBalances(SchoolContext $context): array
|
||||
{
|
||||
return ['domain_profile'=>$context->domainProfile,'rows'=>$this->finance->outstandingInvoices($context)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Ministry
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Contracts\PaymentAllocationPolicyContract;
|
||||
|
||||
final class IslamicSundaySchoolPaymentAllocationPolicy implements PaymentAllocationPolicyContract
|
||||
{
|
||||
public function allocate(SchoolContext $context, int $invoiceId, float $amount): array
|
||||
{
|
||||
return [['invoice_id' => $invoiceId, 'amount' => $amount, 'allocation_rule' => 'extension_default']];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Policies
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\IslamicSundaySchool\Providers;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Contracts\AcademicCalendarProviderContract;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class IslamicSundaySchoolCalendarProvider implements AcademicCalendarProviderContract
|
||||
{
|
||||
public function currentAcademicYear(SchoolContext $context): ?int
|
||||
{
|
||||
return is_numeric($context->schoolYear) ? (int) $context->schoolYear : null;
|
||||
}
|
||||
|
||||
public function currentTerm(SchoolContext $context): ?int
|
||||
{
|
||||
return is_numeric($context->term) ? (int) $context->term : null;
|
||||
}
|
||||
|
||||
public function sessionsForDate(SchoolContext $context, DateTimeInterface $date): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Providers
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ExportTemplates;
|
||||
final class FamilySummaryExportTemplate {}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ExportTemplates;
|
||||
final class QuranProgressExportTemplate {}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Domain\IslamicSundaySchool\Reporting\Reports\{QuranProgressReport,ArabicLevelReport,IslamicStudiesProgressReport,HalaqaAttendanceReport,SundayProgramAttendanceReport,MasjidFamilySummaryReport,VolunteerTeamReport,IslamicSundaySchoolDashboardReport};
|
||||
|
||||
final class IslamicSundaySchoolReportingServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->tag([
|
||||
QuranProgressReport::class,
|
||||
ArabicLevelReport::class,
|
||||
IslamicStudiesProgressReport::class,
|
||||
HalaqaAttendanceReport::class,
|
||||
SundayProgramAttendanceReport::class,
|
||||
MasjidFamilySummaryReport::class,
|
||||
VolunteerTeamReport::class,
|
||||
IslamicSundaySchoolDashboardReport::class,
|
||||
], 'schoolcore.reports');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ReadModels;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class ArabicProgressReadModel { public function rows(SchoolContext $context, array $filters = []): array { return []; } }
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ReadModels;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class HalaqaReportReadModel { public function rows(SchoolContext $context, array $filters = []): array { return []; } }
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ReadModels;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class IslamicStudiesProgressReadModel { public function rows(SchoolContext $context, array $filters = []): array { return []; } }
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ReadModels;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class MasjidFamilyReportReadModel { public function rows(SchoolContext $context, array $filters = []): array { return []; } }
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\ReadModels;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class QuranProgressReadModel { public function rows(SchoolContext $context, array $filters = []): array { return []; } }
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class ArabicLevelReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.arabic_level'; }
|
||||
public function name(): string { return 'Arabic Level'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Reporting\Enums\ReportCategory;
|
||||
use App\Domain\SchoolCore\Reporting\Reports\BaseReportDefinition;
|
||||
|
||||
abstract class BaseIslamicSundaySchoolReport extends BaseReportDefinition
|
||||
{
|
||||
public function category(): ReportCategory { return ReportCategory::EXTENSION; }
|
||||
public function availableForDomainProfile(?string $domainProfile): bool { return $domainProfile === 'islamic_sunday_school'; }
|
||||
public function requiredPermissions(): array { return ['report.view', 'report.islamic.view']; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class HalaqaAttendanceReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.halaqa_attendance'; }
|
||||
public function name(): string { return 'Halaqa Attendance'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class IslamicStudiesProgressReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.studies_progress'; }
|
||||
public function name(): string { return 'Islamic Studies Progress'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class IslamicSundaySchoolDashboardReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.dashboard'; }
|
||||
public function name(): string { return 'Islamic Sunday School Dashboard'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class MasjidFamilySummaryReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.masjid_family_summary'; }
|
||||
public function name(): string { return 'Masjid Family Summary'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class QuranProgressReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.quran_progress'; }
|
||||
public function name(): string { return "Qur'an Progress"; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class SundayProgramAttendanceReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.sunday_program_attendance'; }
|
||||
public function name(): string { return 'Sunday Program Attendance'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Reporting\Reports;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Reporting\DTO\ReportResultData;
|
||||
|
||||
final class VolunteerTeamReport extends BaseIslamicSundaySchoolReport
|
||||
{
|
||||
public function key(): string { return 'islamic.volunteer_team'; }
|
||||
public function name(): string { return 'Volunteer Team'; }
|
||||
public function columns(): array { return [['key' => 'note', 'label' => 'Note', 'sensitive' => false]]; }
|
||||
public function sensitiveColumns(): array { return ['sensitive_notes']; }
|
||||
public function run(SchoolContext $context, array $filters = []): ReportResultData
|
||||
{
|
||||
return new ReportResultData($this->key(), $this->columns(), [[
|
||||
'school_id' => $context->schoolId,
|
||||
'report_key' => $this->key(),
|
||||
'note' => 'Extension report scaffold. Wire this to Islamic Sunday School read models.',
|
||||
]], null, null, null, 1, gmdate('c'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Reports
|
||||
|
||||
Extension module. Bind policies/providers here without leaking implementation details into SchoolCore.
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\DTO;
|
||||
final readonly class IslamicSundaySchoolStudentProfileData { public function __construct(public int $studentId, public ?int $familyProfileId=null, public ?int $quranLevelId=null, public ?int $arabicLevelId=null, public ?int $islamicStudiesTrackId=null, public ?int $halaqaGroupId=null, public ?string $memorizationProgressSummary=null, public ?string $recitationPlacement=null, public bool $volunteerFamilyParticipation=false, public ?string $sensitiveAdminNotes=null, public array $metadata=[]) {} }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Policies;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Students\DTO\EnrollmentData;
|
||||
final class IslamicSundaySchoolEnrollmentPolicy { public function canEnroll(SchoolContext $context, EnrollmentData $data): bool { return $context->isIslamicSundaySchool() && $data->studentId>0; } }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Policies;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Students\Contracts\PromotionPolicyContract; use App\Domain\SchoolCore\Students\DTO\EnrollmentView; use App\Domain\SchoolCore\Students\DTO\PromotionEvaluationResult; use App\Domain\SchoolCore\Students\DTO\StudentView;
|
||||
final class IslamicSundaySchoolPromotionPolicy implements PromotionPolicyContract { public function evaluate(SchoolContext $context, StudentView $student, EnrollmentView $enrollment): PromotionEvaluationResult { return new PromotionEvaluationResult($context->isIslamicSundaySchool() && $student->status==='active',$student->status==='active'?'promoted':'manual_review'); } }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Policies;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Students\Contracts\StudentIdentifierGeneratorContract; use App\Domain\SchoolCore\Students\DTO\StudentIdentifier; use App\Domain\SchoolCore\Students\DTO\StudentView;
|
||||
final class IslamicSundaySchoolStudentIdentifierGenerator implements StudentIdentifierGeneratorContract { public function generateForPersistedStudent(SchoolContext $context, StudentView $student): StudentIdentifier { return new StudentIdentifier(sprintf('SS-%s-%06d',date('Y'),$student->id)); } }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Providers;
|
||||
use App\Domain\IslamicSundaySchool\Students\Policies\IslamicSundaySchoolPromotionPolicy; use App\Domain\IslamicSundaySchool\Students\Policies\IslamicSundaySchoolStudentIdentifierGenerator; use App\Domain\SchoolCore\Students\Contracts\PromotionPolicyContract; use App\Domain\SchoolCore\Students\Contracts\StudentIdentifierGeneratorContract; use Illuminate\Support\ServiceProvider;
|
||||
final class IslamicSundaySchoolStudentServiceProvider extends ServiceProvider { public function register(): void { if((bool)config('school_context.extensions.islamic_sunday_school.students',false)){ $this->app->bind(StudentIdentifierGeneratorContract::class,IslamicSundaySchoolStudentIdentifierGenerator::class); $this->app->bind(PromotionPolicyContract::class,IslamicSundaySchoolPromotionPolicy::class); } } }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Reports;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class IslamicSundaySchoolStudentReportBuilder { public function buildSummary(SchoolContext $context,array $filters=[]): array { return ['domain_profile'=>$context->domainProfile,'filters'=>$filters,'labels'=>['quran_level','arabic_level','islamic_studies_track']]; } }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Services;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
final class IslamicSundaySchoolFamilyProfileService { public function summarizeForStudent(SchoolContext $context,int $studentId): array { return ['school_id'=>$context->schoolId,'student_id'=>$studentId,'extension'=>'islamic_sunday_school']; } }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace App\Domain\IslamicSundaySchool\Students\Services;
|
||||
use App\Domain\IslamicSundaySchool\Students\DTO\IslamicSundaySchoolStudentProfileData; use App\Domain\SchoolCore\Context\SchoolContext; use Illuminate\Support\Facades\DB;
|
||||
final class IslamicSundaySchoolStudentProfileService { public function save(SchoolContext $context, IslamicSundaySchoolStudentProfileData $data): array { DB::table('islamic_sunday_school_student_profiles')->updateOrInsert(['school_id'=>$context->schoolId,'student_id'=>$data->studentId],['family_profile_id'=>$data->familyProfileId,'quran_level_id'=>$data->quranLevelId,'arabic_level_id'=>$data->arabicLevelId,'islamic_studies_track_id'=>$data->islamicStudiesTrackId,'halaqa_group_id'=>$data->halaqaGroupId,'memorization_progress_summary'=>$data->memorizationProgressSummary,'recitation_placement'=>$data->recitationPlacement,'volunteer_family_participation'=>$data->volunteerFamilyParticipation,'sensitive_admin_notes'=>$data->sensitiveAdminNotes,'metadata'=>json_encode($data->metadata),'updated_at'=>now()]); return ['student_id'=>$data->studentId,'profile'=>'islamic_sunday_school']; } }
|
||||
@@ -0,0 +1,24 @@
|
||||
# Domain Boundary Rules
|
||||
|
||||
Dependency direction:
|
||||
|
||||
```text
|
||||
Http / Controllers / Requests / Resources
|
||||
↓
|
||||
Application Services / Use Cases
|
||||
↓
|
||||
SchoolCore Contracts
|
||||
↓
|
||||
SchoolCore Implementations
|
||||
↑
|
||||
Domain Extensions may provide policies/providers
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `SchoolCore` defines neutral platform capabilities.
|
||||
- Extension modules may depend on `SchoolCore` contracts.
|
||||
- `SchoolCore` must not import extension namespaces.
|
||||
- Domain services must not accept `Illuminate\Http\Request`.
|
||||
- Domain services must not call `auth()`.
|
||||
- Controllers should depend on contracts, not concrete domain services.
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Academics\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Contracts\GradeScalePolicyContract;
|
||||
|
||||
final class DefaultGradeScalePolicy implements GradeScalePolicyContract
|
||||
{
|
||||
public function gradeForScore(SchoolContext $context, float $score): string
|
||||
{
|
||||
return match (true) { $score >= 90 => 'A', $score >= 80 => 'B', $score >= 70 => 'C', $score >= 60 => 'D', default => 'F' };
|
||||
}
|
||||
public function passingScore(SchoolContext $context): float { return 60.0; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Academics\Policies;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Contracts\PromotionPolicyContract;
|
||||
|
||||
final class DefaultPromotionPolicy implements PromotionPolicyContract
|
||||
{
|
||||
public function canPromote(SchoolContext $context, int $studentId): bool { return false; }
|
||||
public function nextPlacement(SchoolContext $context, int $studentId): array { return []; }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Academics
|
||||
|
||||
Neutral SchoolCore module boundary. Keep domain-specific vocabulary in extension modules.
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceAuditData;
|
||||
|
||||
interface AttendanceAuditLoggerContract
|
||||
{
|
||||
public function log(SchoolContext $context, AttendanceAuditData $data): void;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceIdempotencyData;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\ProcessScanData;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\ScanResult;
|
||||
|
||||
interface AttendanceIdempotencyServiceContract
|
||||
{
|
||||
public function findExisting(SchoolContext $context, AttendanceIdempotencyData $data): ?ScanResult;
|
||||
|
||||
public function storeResult(SchoolContext $context, AttendanceIdempotencyData $data, ProcessScanData $scan, ScanResult $result): void;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSubjectView;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
|
||||
interface AttendancePolicyContract
|
||||
{
|
||||
public function canRecordAttendance(SchoolContext $context, AttendanceSubjectView $subject, AttendanceSessionView $session): bool;
|
||||
|
||||
public function duplicateScanWindowSeconds(SchoolContext $context): int;
|
||||
|
||||
public function allowCheckout(SchoolContext $context): bool;
|
||||
|
||||
public function allowManualOverride(SchoolContext $context): bool;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSummaryQuery;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSummaryResult;
|
||||
|
||||
interface AttendanceServiceContract
|
||||
{
|
||||
public function getSessionAttendance(SchoolContext $context, int $sessionId): AttendanceSessionView;
|
||||
|
||||
public function summarizeAttendance(SchoolContext $context, AttendanceSummaryQuery $query): AttendanceSummaryResult;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSubjectView;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use DateTimeInterface;
|
||||
|
||||
interface AttendanceSessionResolverContract
|
||||
{
|
||||
public function resolveForScan(
|
||||
SchoolContext $context,
|
||||
AttendanceSubjectView $subject,
|
||||
DateTimeInterface $timestamp
|
||||
): AttendanceSessionView;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceStatusResult;
|
||||
use DateTimeInterface;
|
||||
|
||||
interface AttendanceStatusPolicyContract
|
||||
{
|
||||
public function statusForScan(
|
||||
SchoolContext $context,
|
||||
AttendanceSessionView $session,
|
||||
DateTimeInterface $timestamp
|
||||
): AttendanceStatusResult;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\BadgeLookupData;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\BadgeLookupResult;
|
||||
|
||||
interface BadgeLookupServiceContract
|
||||
{
|
||||
public function lookup(SchoolContext $context, BadgeLookupData $data): BadgeLookupResult;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\ProcessScanData;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\ScanResult;
|
||||
|
||||
interface ScannerServiceContract
|
||||
{
|
||||
public function processScan(SchoolContext $context, ProcessScanData $data): ScanResult;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\RecordStaffAttendanceData;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceRecordResult;
|
||||
|
||||
interface StaffAttendanceServiceContract
|
||||
{
|
||||
public function recordStaffAttendance(SchoolContext $context, RecordStaffAttendanceData $data): AttendanceRecordResult;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Contracts;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\RecordStudentAttendanceData;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceRecordResult;
|
||||
|
||||
interface StudentAttendanceServiceContract
|
||||
{
|
||||
public function recordStudentAttendance(SchoolContext $context, RecordStudentAttendanceData $data): AttendanceRecordResult;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceActorData
|
||||
{
|
||||
public function __construct(
|
||||
public int $actorUserId,
|
||||
public array $roleIds = [],
|
||||
public string $actorType = 'user',
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceAuditData
|
||||
{
|
||||
public function __construct(
|
||||
public string $action,
|
||||
public string $subjectType,
|
||||
public int $subjectId,
|
||||
public ?int $attendanceSessionId = null,
|
||||
public ?int $attendanceRecordId = null,
|
||||
public ?array $before = null,
|
||||
public ?array $after = null,
|
||||
public ?string $reason = null,
|
||||
public string $source = 'system',
|
||||
public ?string $stationId = null,
|
||||
public ?string $requestId = null,
|
||||
public ?string $idempotencyKey = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceIdempotencyData
|
||||
{
|
||||
public function __construct(
|
||||
public string $operation,
|
||||
public string $key,
|
||||
public string $payloadHash,
|
||||
public ?string $fingerprint = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceRecordResult
|
||||
{
|
||||
public function __construct(
|
||||
public int $recordId,
|
||||
public string $subjectType,
|
||||
public int $subjectId,
|
||||
public string $status,
|
||||
public bool $created,
|
||||
public array $payload = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class AttendanceSessionData
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $sessionType,
|
||||
public DateTimeImmutable $startsAt,
|
||||
public DateTimeImmutable $endsAt,
|
||||
public string $timezone,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class AttendanceSessionView
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public int $schoolId,
|
||||
public string $name,
|
||||
public string $sessionType,
|
||||
public DateTimeImmutable $startsAt,
|
||||
public DateTimeImmutable $endsAt,
|
||||
public string $timezone,
|
||||
public string $status = 'open',
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceStatusData
|
||||
{
|
||||
public function __construct(
|
||||
public string $status,
|
||||
public string $source,
|
||||
public ?string $reason = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceStatusResult
|
||||
{
|
||||
public function __construct(
|
||||
public string $status,
|
||||
public bool $late = false,
|
||||
public ?string $reason = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceSubjectView
|
||||
{
|
||||
public function __construct(
|
||||
public string $subjectType,
|
||||
public int $subjectId,
|
||||
public int $schoolId,
|
||||
public string $displayName,
|
||||
public bool $active = true,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceSummaryQuery
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $fromDate = null,
|
||||
public ?string $toDate = null,
|
||||
public ?int $sessionId = null,
|
||||
public ?string $subjectType = null,
|
||||
public array $filters = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class AttendanceSummaryResult
|
||||
{
|
||||
public function __construct(
|
||||
public array $counts,
|
||||
public array $rows = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class BadgeLookupData
|
||||
{
|
||||
public function __construct(
|
||||
public string $badgeValue,
|
||||
public ?string $badgeType = null,
|
||||
public ?string $subjectTypeHint = null,
|
||||
public ?string $stationId = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class BadgeLookupResult
|
||||
{
|
||||
public function __construct(
|
||||
public AttendanceSubjectView $subject,
|
||||
public ?int $badgeId = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class ProcessScanData
|
||||
{
|
||||
public function __construct(
|
||||
public string $badgeValue,
|
||||
public ?string $stationId,
|
||||
public string $scanAction,
|
||||
public string $scanSource,
|
||||
public DateTimeImmutable $scannedAt,
|
||||
public ?string $deviceId = null,
|
||||
public ?string $clientRequestId = null,
|
||||
public ?string $idempotencyKey = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class RecordStaffAttendanceData
|
||||
{
|
||||
public function __construct(
|
||||
public int $staffId,
|
||||
public ?int $sessionId,
|
||||
public string $attendanceDate,
|
||||
public string $status,
|
||||
public string $source,
|
||||
public DateTimeImmutable $recordedAt,
|
||||
public ?string $reason = null,
|
||||
public ?string $notes = null,
|
||||
public ?string $overrideReason = null,
|
||||
public ?string $idempotencyKey = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class RecordStudentAttendanceData
|
||||
{
|
||||
public function __construct(
|
||||
public int $studentId,
|
||||
public ?int $sessionId,
|
||||
public string $attendanceDate,
|
||||
public string $status,
|
||||
public string $source,
|
||||
public DateTimeImmutable $recordedAt,
|
||||
public ?string $reason = null,
|
||||
public ?string $notes = null,
|
||||
public ?string $overrideReason = null,
|
||||
public ?string $idempotencyKey = null,
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\DTO;
|
||||
|
||||
final readonly class ScanResult
|
||||
{
|
||||
public function __construct(
|
||||
public bool $accepted,
|
||||
public string $status,
|
||||
public string $message,
|
||||
public ?AttendanceSubjectView $subject = null,
|
||||
public ?AttendanceSessionView $session = null,
|
||||
public ?int $attendanceRecordId = null,
|
||||
public bool $duplicate = false,
|
||||
public array $payload = [],
|
||||
) {}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'accepted' => $this->accepted,
|
||||
'status' => $this->status,
|
||||
'message' => $this->message,
|
||||
'subject' => $this->subject ? [
|
||||
'type' => $this->subject->subjectType,
|
||||
'id' => $this->subject->subjectId,
|
||||
'name' => $this->subject->displayName,
|
||||
] : null,
|
||||
'session' => $this->session ? [
|
||||
'id' => $this->session->id,
|
||||
'name' => $this->session->name,
|
||||
'type' => $this->session->sessionType,
|
||||
] : null,
|
||||
'attendance_record_id' => $this->attendanceRecordId,
|
||||
'duplicate' => $this->duplicate,
|
||||
'payload' => $this->payload,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Data;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
final readonly class AttendanceMarkData
|
||||
{
|
||||
public function __construct(public int $studentId, public string $status, public DateTimeInterface $markedAt) {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\SchoolCore\Attendance\Data;
|
||||
|
||||
final readonly class AttendanceResult
|
||||
{
|
||||
/** @param array<string, mixed> $details */
|
||||
public function __construct(public bool $accepted, public string $status, public array $details = []) {}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user