snapshot schedule source data
This commit is contained in:
parent
14fe2fcb46
commit
f500bd6095
14 changed files with 84 additions and 87 deletions
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Element\Element;
|
||||
use App\Exceptions\BadRequestException;
|
||||
use App\Exceptions\NotFoundException;
|
||||
use App\Schedule\Schedule;
|
||||
|
|
@ -96,13 +95,10 @@ class ScheduleController extends Controller
|
|||
*/
|
||||
private function summaryPayload(Schedule $schedule): array
|
||||
{
|
||||
$set = $schedule->getSet();
|
||||
|
||||
return [
|
||||
'id' => $schedule->getId(),
|
||||
'set' => [
|
||||
'id' => $set->getId(),
|
||||
'name' => $set->getName(),
|
||||
'name' => $schedule->getSetName(),
|
||||
],
|
||||
'elementKind' => $schedule->getElementKind(),
|
||||
'startDate' => $schedule->getStartDate()->format('Y-m-d'),
|
||||
|
|
@ -150,8 +146,7 @@ class ScheduleController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array{element: array{
|
||||
* id: int,
|
||||
* @return array{id: int, element: array{
|
||||
* name: string,
|
||||
* kind: string,
|
||||
* path: list<string>
|
||||
|
|
@ -160,34 +155,16 @@ class ScheduleController extends Controller
|
|||
private function assignmentPayload(
|
||||
ScheduleAssignment $assignment,
|
||||
): array {
|
||||
$element = $assignment->getElement();
|
||||
|
||||
return [
|
||||
'id' => $assignment->getId(),
|
||||
'element' => [
|
||||
'id' => $element->getId(),
|
||||
'name' => $element->getName(),
|
||||
'kind' => $element->getKind(),
|
||||
'path' => $this->elementPath($element),
|
||||
'name' => $assignment->getName(),
|
||||
'kind' => $assignment->getKind(),
|
||||
'path' => $assignment->getPath(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function elementPath(Element $element): array
|
||||
{
|
||||
$path = [];
|
||||
$currentElement = $element;
|
||||
|
||||
while ($currentElement !== null) {
|
||||
array_unshift($path, $currentElement->getName());
|
||||
$currentElement = $currentElement->getParentElement();
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function user(Request $request): User
|
||||
{
|
||||
/** @var User $user */
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
namespace App\Schedule;
|
||||
|
||||
use App\Element\Element;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class CreateScheduleAssignmentDto
|
||||
{
|
||||
/**
|
||||
* @param list<string> $path
|
||||
*/
|
||||
public function __construct(
|
||||
public Element $element,
|
||||
public string $name,
|
||||
public string $kind,
|
||||
public array $path,
|
||||
public DateTimeImmutable $scheduledDate,
|
||||
public int $position,
|
||||
) {}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Schedule;
|
||||
|
||||
use App\Set\Set;
|
||||
use App\User\User;
|
||||
use DateTimeImmutable;
|
||||
|
||||
|
|
@ -13,7 +12,7 @@ final readonly class CreateScheduleDto
|
|||
*/
|
||||
public function __construct(
|
||||
public User $user,
|
||||
public Set $set,
|
||||
public string $setName,
|
||||
public string $elementKind,
|
||||
public DateTimeImmutable $startDate,
|
||||
public DateTimeImmutable $targetDate,
|
||||
|
|
|
|||
|
|
@ -2,27 +2,19 @@
|
|||
|
||||
namespace App\Schedule;
|
||||
|
||||
use App\Element\ElementRepository;
|
||||
use App\Set\SetRepository;
|
||||
use App\User\User;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
class EloquentScheduleRepository implements ScheduleRepository
|
||||
{
|
||||
public function __construct(
|
||||
private SetRepository $setRepository,
|
||||
private ElementRepository $elementRepository,
|
||||
) {}
|
||||
|
||||
public function create(CreateScheduleDto $dto): Schedule
|
||||
{
|
||||
return DB::transaction(function () use ($dto): Schedule {
|
||||
$model = ScheduleModel::create([
|
||||
'user_id' => $dto->user->getId(),
|
||||
'set_id' => $dto->set->getId(),
|
||||
'set_name' => $dto->setName,
|
||||
'element_kind' => $dto->elementKind,
|
||||
'start_date' => $dto->startDate->format('Y-m-d'),
|
||||
'target_date' => $dto->targetDate->format('Y-m-d'),
|
||||
|
|
@ -31,7 +23,9 @@ class EloquentScheduleRepository implements ScheduleRepository
|
|||
foreach ($dto->assignments as $assignmentDto) {
|
||||
ScheduleAssignmentModel::create([
|
||||
'schedule_id' => $model->id,
|
||||
'element_id' => $assignmentDto->element->getId(),
|
||||
'element_name' => $assignmentDto->name,
|
||||
'element_kind' => $assignmentDto->kind,
|
||||
'element_path' => $assignmentDto->path,
|
||||
'scheduled_date' => $assignmentDto->scheduledDate
|
||||
->format('Y-m-d'),
|
||||
'position' => $assignmentDto->position,
|
||||
|
|
@ -69,11 +63,6 @@ class EloquentScheduleRepository implements ScheduleRepository
|
|||
|
||||
private function toDomain(ScheduleModel $model, User $user): Schedule
|
||||
{
|
||||
$set = $this->setRepository->find($model->set_id);
|
||||
if ($set === null) {
|
||||
throw new RuntimeException('schedule set not found');
|
||||
}
|
||||
|
||||
$assignmentModels = ScheduleAssignmentModel::query()
|
||||
->where('schedule_id', $model->id)
|
||||
->orderBy('position')
|
||||
|
|
@ -82,16 +71,11 @@ class EloquentScheduleRepository implements ScheduleRepository
|
|||
$assignments = [];
|
||||
|
||||
foreach ($assignmentModels as $assignmentModel) {
|
||||
$element = $this->elementRepository->find(
|
||||
$assignmentModel->element_id,
|
||||
);
|
||||
if ($element === null) {
|
||||
throw new RuntimeException('schedule element not found');
|
||||
}
|
||||
|
||||
$assignments[] = new ScheduleAssignment(
|
||||
id: $assignmentModel->id,
|
||||
element: $element,
|
||||
name: $assignmentModel->element_name,
|
||||
kind: $assignmentModel->element_kind,
|
||||
path: $assignmentModel->element_path,
|
||||
scheduledDate: $this->date($assignmentModel->scheduled_date),
|
||||
position: $assignmentModel->position,
|
||||
);
|
||||
|
|
@ -100,7 +84,7 @@ class EloquentScheduleRepository implements ScheduleRepository
|
|||
return new Schedule(
|
||||
id: $model->id,
|
||||
user: $user,
|
||||
set: $set,
|
||||
setName: $model->set_name,
|
||||
elementKind: $model->element_kind,
|
||||
startDate: $this->date($model->start_date),
|
||||
targetDate: $this->date($model->target_date),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Schedule;
|
||||
|
||||
use App\Set\Set;
|
||||
use App\User\User;
|
||||
use DateTimeImmutable;
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ final readonly class Schedule
|
|||
public function __construct(
|
||||
private int $id,
|
||||
private User $user,
|
||||
private Set $set,
|
||||
private string $setName,
|
||||
private string $elementKind,
|
||||
private DateTimeImmutable $startDate,
|
||||
private DateTimeImmutable $targetDate,
|
||||
|
|
@ -31,9 +30,9 @@ final readonly class Schedule
|
|||
return $this->user;
|
||||
}
|
||||
|
||||
public function getSet(): Set
|
||||
public function getSetName(): string
|
||||
{
|
||||
return $this->set;
|
||||
return $this->setName;
|
||||
}
|
||||
|
||||
public function getElementKind(): string
|
||||
|
|
|
|||
|
|
@ -2,14 +2,18 @@
|
|||
|
||||
namespace App\Schedule;
|
||||
|
||||
use App\Element\Element;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class ScheduleAssignment
|
||||
{
|
||||
/**
|
||||
* @param list<string> $path
|
||||
*/
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private Element $element,
|
||||
private string $name,
|
||||
private string $kind,
|
||||
private array $path,
|
||||
private DateTimeImmutable $scheduledDate,
|
||||
private int $position,
|
||||
) {}
|
||||
|
|
@ -19,9 +23,22 @@ final readonly class ScheduleAssignment
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
public function getElement(): Element
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->element;
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getKind(): string
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getPath(): array
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getScheduledDate(): DateTimeImmutable
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use Illuminate\Database\Eloquent\Model;
|
|||
/**
|
||||
* @property int $id
|
||||
* @property int $schedule_id
|
||||
* @property int $element_id
|
||||
* @property string $element_name
|
||||
* @property string $element_kind
|
||||
* @property list<string> $element_path
|
||||
* @property string $scheduled_date
|
||||
* @property int $position
|
||||
*
|
||||
|
|
@ -21,7 +23,9 @@ use Illuminate\Database\Eloquent\Model;
|
|||
*/
|
||||
#[Fillable([
|
||||
'schedule_id',
|
||||
'element_id',
|
||||
'element_name',
|
||||
'element_kind',
|
||||
'element_path',
|
||||
'scheduled_date',
|
||||
'position',
|
||||
])]
|
||||
|
|
@ -38,7 +42,7 @@ class ScheduleAssignmentModel extends Model
|
|||
{
|
||||
return [
|
||||
'schedule_id' => 'integer',
|
||||
'element_id' => 'integer',
|
||||
'element_path' => 'array',
|
||||
'position' => 'integer',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $set_id
|
||||
* @property string $set_name
|
||||
* @property string $element_kind
|
||||
* @property string $start_date
|
||||
* @property string $target_date
|
||||
|
|
@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||
*/
|
||||
#[Fillable([
|
||||
'user_id',
|
||||
'set_id',
|
||||
'set_name',
|
||||
'element_kind',
|
||||
'start_date',
|
||||
'target_date',
|
||||
|
|
@ -40,7 +40,6 @@ class ScheduleModel extends Model
|
|||
{
|
||||
return [
|
||||
'user_id' => 'integer',
|
||||
'set_id' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class CreateSchedule
|
|||
|
||||
return $this->scheduleRepository->create(new CreateScheduleDto(
|
||||
user: $request->user,
|
||||
set: $set,
|
||||
setName: $set->getName(),
|
||||
elementKind: $request->elementKind,
|
||||
startDate: $startDate,
|
||||
targetDate: $targetDate,
|
||||
|
|
@ -153,7 +153,9 @@ class CreateSchedule
|
|||
dayCount: $dayCount,
|
||||
);
|
||||
$assignments[] = new CreateScheduleAssignmentDto(
|
||||
element: $element,
|
||||
name: $element->getName(),
|
||||
kind: $element->getKind(),
|
||||
path: $this->elementPath($element),
|
||||
scheduledDate: $startDate->modify("+{$dayIndex} days"),
|
||||
position: $index + 1,
|
||||
);
|
||||
|
|
@ -162,6 +164,22 @@ class CreateSchedule
|
|||
return $assignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function elementPath(Element $element): array
|
||||
{
|
||||
$path = [];
|
||||
$currentElement = $element;
|
||||
|
||||
while ($currentElement !== null) {
|
||||
array_unshift($path, $currentElement->getName());
|
||||
$currentElement = $currentElement->getParentElement();
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function dayIndex(
|
||||
int $elementIndex,
|
||||
int $elementCount,
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ return new class extends Migration
|
|||
$table->foreignId('user_id')
|
||||
->constrained('users')
|
||||
->restrictOnDelete();
|
||||
$table->foreignId('set_id')
|
||||
->constrained('sets')
|
||||
->restrictOnDelete();
|
||||
$table->string('set_name');
|
||||
$table->string('element_kind');
|
||||
$table->date('start_date');
|
||||
$table->date('target_date');
|
||||
|
|
|
|||
|
|
@ -15,12 +15,11 @@ return new class extends Migration
|
|||
$table->foreignId('schedule_id')
|
||||
->constrained('schedules')
|
||||
->cascadeOnDelete();
|
||||
$table->foreignId('element_id')
|
||||
->constrained('elements')
|
||||
->restrictOnDelete();
|
||||
$table->string('element_name');
|
||||
$table->string('element_kind');
|
||||
$table->json('element_path');
|
||||
$table->date('scheduled_date');
|
||||
$table->unsignedInteger('position');
|
||||
$table->unique(['schedule_id', 'element_id']);
|
||||
$table->unique(['schedule_id', 'position']);
|
||||
$table->index([
|
||||
'schedule_id',
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const bibleLayout = {
|
|||
const scheduleDetail = {
|
||||
schedule: {
|
||||
id: 73,
|
||||
set: { id: 41, name: 'Bible' },
|
||||
set: { name: 'Bible' },
|
||||
elementKind: 'chapter',
|
||||
startDate: '2026-08-10',
|
||||
targetDate: '2026-08-12',
|
||||
|
|
@ -55,8 +55,8 @@ const scheduleDetail = {
|
|||
date: '2026-08-10',
|
||||
assignments: [
|
||||
{
|
||||
id: 1,
|
||||
element: {
|
||||
id: 4,
|
||||
name: 'Chapter 1',
|
||||
kind: 'chapter',
|
||||
path: ['Genesis', 'Creation', 'Chapter 1'],
|
||||
|
|
@ -69,8 +69,8 @@ const scheduleDetail = {
|
|||
date: '2026-08-12',
|
||||
assignments: [
|
||||
{
|
||||
id: 2,
|
||||
element: {
|
||||
id: 5,
|
||||
name: 'Chapter 1',
|
||||
kind: 'chapter',
|
||||
path: ['Exodus', 'Chapter 1'],
|
||||
|
|
@ -213,7 +213,7 @@ describe('set scheduling', () => {
|
|||
schedules: [
|
||||
{
|
||||
id: 73,
|
||||
set: { id: 41, name: 'Bible' },
|
||||
set: { name: 'Bible' },
|
||||
elementKind: 'chapter',
|
||||
startDate: '2026-08-10',
|
||||
targetDate: '2026-08-12',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
|
|||
export const scheduleSummarySchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
set: z.object({
|
||||
id: z.number().int().positive(),
|
||||
name: z.string().min(1),
|
||||
}),
|
||||
elementKind: z.string().min(1),
|
||||
|
|
@ -19,8 +18,8 @@ export const scheduleSummarySchema = z.object({
|
|||
})
|
||||
|
||||
const scheduleAssignmentSchema = z.object({
|
||||
element: z.object({
|
||||
id: z.number().int().positive(),
|
||||
element: z.object({
|
||||
name: z.string().min(1),
|
||||
kind: z.string().min(1),
|
||||
path: z.array(z.string().min(1)).min(1),
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ function pluralKind(kind: string, count: number): string {
|
|||
<p v-if="day.assignments.length === 0" class="rest-day">Rest day</p>
|
||||
|
||||
<ul v-else class="assignment-list">
|
||||
<li v-for="assignment in day.assignments" :key="assignment.element.id">
|
||||
<li v-for="assignment in day.assignments" :key="assignment.id">
|
||||
<span class="assignment-path">{{ assignment.element.path.join(' / ') }}</span>
|
||||
<span class="assignment-kind">{{ humanizeKind(assignment.element.kind) }}</span>
|
||||
</li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue