From e8fb01f70719ce7b12e64b9908af34734f074ec6 Mon Sep 17 00:00:00 2001 From: Simon Kuehn Date: Thu, 14 May 2026 04:30:12 +0000 Subject: [PATCH] feat: add Order, Pipeline, Auth domain entities (Customer with matching-key, Order, Invoice, AIPipelineJob, User, ApiKey) --- src/Domain/Auth/ApiKey.php | 81 +++++++++++++++++ src/Domain/Auth/User.php | 75 +++++++++++++++ src/Domain/Order/Customer.php | 78 ++++++++++++++++ src/Domain/Order/Invoice.php | 70 ++++++++++++++ src/Domain/Order/Order.php | 105 +++++++++++++++++++++ src/Domain/Pipeline/AIPipelineJob.php | 111 +++++++++++++++++++++++ tests/Unit/Domain/Order/CustomerTest.php | 39 ++++++++ 7 files changed, 559 insertions(+) create mode 100644 src/Domain/Auth/ApiKey.php create mode 100644 src/Domain/Auth/User.php create mode 100644 src/Domain/Order/Customer.php create mode 100644 src/Domain/Order/Invoice.php create mode 100644 src/Domain/Order/Order.php create mode 100644 src/Domain/Pipeline/AIPipelineJob.php create mode 100644 tests/Unit/Domain/Order/CustomerTest.php diff --git a/src/Domain/Auth/ApiKey.php b/src/Domain/Auth/ApiKey.php new file mode 100644 index 0000000..0a6cd12 --- /dev/null +++ b/src/Domain/Auth/ApiKey.php @@ -0,0 +1,81 @@ + */ + #[ORM\Column(type: 'json')] + private array $permissions = []; + + #[ORM\Column(type: 'boolean')] + private bool $isActive = true; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $lastUsedAt = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $expiresAt = null; + + public function __construct(User $user, string $label, string $keyHash) + { + $this->id = Uuid::v7(); + $this->user = $user; + $this->label = $label; + $this->keyHash = $keyHash; + } + + public function getId(): Uuid { return $this->id; } + public function getUser(): User { return $this->user; } + public function getLabel(): string { return $this->label; } + public function getKeyHash(): string { return $this->keyHash; } + public function isActive(): bool { return $this->isActive; } + public function setIsActive(bool $active): void { $this->isActive = $active; } + public function getLastUsedAt(): ?\DateTimeImmutable { return $this->lastUsedAt; } + public function getExpiresAt(): ?\DateTimeImmutable { return $this->expiresAt; } + public function setExpiresAt(?\DateTimeImmutable $expiresAt): void { $this->expiresAt = $expiresAt; } + + /** @return array */ + public function getPermissions(): array { return $this->permissions; } + + public function hasPermission(string $permission): bool + { + return $this->permissions[$permission] ?? false; + } + + public function grantPermission(string $permission): void + { + $this->permissions[$permission] = true; + } + + public function markUsed(): void + { + $this->lastUsedAt = new \DateTimeImmutable(); + } + + public function isExpired(): bool + { + return null !== $this->expiresAt && $this->expiresAt < new \DateTimeImmutable(); + } +} diff --git a/src/Domain/Auth/User.php b/src/Domain/Auth/User.php new file mode 100644 index 0000000..a4cc8b9 --- /dev/null +++ b/src/Domain/Auth/User.php @@ -0,0 +1,75 @@ + */ + #[ORM\Column(type: 'json')] + private array $permissions = []; + + #[ORM\Column(type: 'boolean')] + private bool $isActive = true; + + public function __construct(string $email, string $passwordHash) + { + $this->id = Uuid::v7(); + $this->email = $email; + $this->passwordHash = $passwordHash; + } + + public function getId(): Uuid { return $this->id; } + public function getEmail(): string { return $this->email; } + public function getPassword(): string { return $this->passwordHash; } + public function getUserIdentifier(): string { return $this->email; } + + /** @return list */ + public function getRoles(): array { return ['ROLE_USER']; } + + public function eraseCredentials(): void {} + + public function getTotpSecret(): ?string { return $this->totpSecret; } + public function setTotpSecret(?string $secret): void { $this->totpSecret = $secret; } + public function isActive(): bool { return $this->isActive; } + public function setIsActive(bool $active): void { $this->isActive = $active; } + + /** @return array */ + public function getPermissions(): array { return $this->permissions; } + + public function hasPermission(string $permission): bool + { + return $this->permissions[$permission] ?? false; + } + + public function grantPermission(string $permission): void + { + $this->permissions[$permission] = true; + } + + public function revokePermission(string $permission): void + { + unset($this->permissions[$permission]); + } +} diff --git a/src/Domain/Order/Customer.php b/src/Domain/Order/Customer.php new file mode 100644 index 0000000..a263b30 --- /dev/null +++ b/src/Domain/Order/Customer.php @@ -0,0 +1,78 @@ + */ + #[ORM\Column(type: 'json')] + private array $address; + + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $frappeCustomerId = null; + + /** @var array */ + #[ORM\Column(type: 'json')] + private array $platformIds = []; + + /** + * @param array $address + */ + public function __construct(string $name, string $email, array $address) + { + $this->id = Uuid::v7(); + $this->name = $name; + $this->email = $email; + $this->address = $address; + } + + public function getId(): Uuid { return $this->id; } + public function getName(): string { return $this->name; } + public function getEmail(): string { return $this->email; } + + /** @return array */ + public function getAddress(): array { return $this->address; } + + public function getFrappeCustomerId(): ?string { return $this->frappeCustomerId; } + public function setFrappeCustomerId(?string $id): void { $this->frappeCustomerId = $id; } + + /** @return array */ + public function getPlatformIds(): array { return $this->platformIds; } + + public function getPlatformId(string $platform): ?string + { + return $this->platformIds[$platform] ?? null; + } + + public function addPlatformId(string $platform, string $id): void + { + $this->platformIds[$platform] = $id; + } + + public function getMatchingKey(): string + { + return \mb_strtolower(\implode('|', [ + $this->name, + $this->address['street'] ?? '', + $this->address['city'] ?? '', + $this->address['zip'] ?? '', + ])); + } +} diff --git a/src/Domain/Order/Invoice.php b/src/Domain/Order/Invoice.php new file mode 100644 index 0000000..9f5f344 --- /dev/null +++ b/src/Domain/Order/Invoice.php @@ -0,0 +1,70 @@ +id = Uuid::v7(); + $this->order = $order; + $this->frappeInvoiceId = $frappeInvoiceId; + $this->storagePath = $storagePath; + $this->filename = $filename; + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): Uuid { return $this->id; } + public function getOrder(): Order { return $this->order; } + public function getFrappeInvoiceId(): string { return $this->frappeInvoiceId; } + public function getStoragePath(): StoragePath { return $this->storagePath; } + public function getFilename(): string { return $this->filename; } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } + public function getEmailedAt(): ?\DateTimeImmutable { return $this->emailedAt; } + + public function markAsEmailed(): void + { + $this->emailedAt = new \DateTimeImmutable(); + } + + public function getFullPath(): string + { + return $this->storagePath->resolveFilePath($this->filename); + } +} diff --git a/src/Domain/Order/Order.php b/src/Domain/Order/Order.php new file mode 100644 index 0000000..955a3bd --- /dev/null +++ b/src/Domain/Order/Order.php @@ -0,0 +1,105 @@ +id = Uuid::v7(); + $this->article = $article; + $this->customer = $customer; + $this->platform = $platform; + $this->platformOrderId = $platformOrderId; + $this->status = OrderStatus::Pending; + $this->salePrice = $salePrice; + $this->saleDate = $saleDate; + } + + public function setTracking(string $trackingNumber, string $carrier): void + { + $this->trackingNumber = $trackingNumber; + $this->carrier = $carrier; + $this->shippedAt = new \DateTimeImmutable(); + $this->status = OrderStatus::Shipped; + } + + public function markTrackingPushedToEbay(): void + { + $this->trackingPushedToEbayAt = new \DateTimeImmutable(); + } + + public function getId(): Uuid { return $this->id; } + public function getArticle(): Article { return $this->article; } + public function getCustomer(): Customer { return $this->customer; } + public function getPlatform(): Platform { return $this->platform; } + public function getPlatformOrderId(): string { return $this->platformOrderId; } + public function getStatus(): OrderStatus { return $this->status; } + public function setStatus(OrderStatus $status): void { $this->status = $status; } + public function getSalePrice(): string { return $this->salePrice; } + public function getSaleDate(): \DateTimeImmutable { return $this->saleDate; } + public function getTrackingNumber(): ?string { return $this->trackingNumber; } + public function getCarrier(): ?string { return $this->carrier; } + public function getShippedAt(): ?\DateTimeImmutable { return $this->shippedAt; } + public function getTrackingPushedToEbayAt(): ?\DateTimeImmutable { return $this->trackingPushedToEbayAt; } + public function getInvoice(): ?Invoice { return $this->invoice; } + public function setInvoice(Invoice $invoice): void { $this->invoice = $invoice; } +} diff --git a/src/Domain/Pipeline/AIPipelineJob.php b/src/Domain/Pipeline/AIPipelineJob.php new file mode 100644 index 0000000..2e4a07c --- /dev/null +++ b/src/Domain/Pipeline/AIPipelineJob.php @@ -0,0 +1,111 @@ + */ + #[ORM\Column(type: 'json')] + private array $inputData; + + /** @var array */ + #[ORM\Column(type: 'json')] + private array $outputData = []; + + /** @var list */ + #[ORM\Column(type: 'simple_array', nullable: true)] + private array $missingFields = []; + + #[ORM\Column(type: 'text', nullable: true)] + private ?string $errorMessage = null; + + #[ORM\Column(type: 'datetime_immutable')] + private \DateTimeImmutable $createdAt; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $completedAt = null; + + /** + * @param array $inputData + */ + public function __construct(AIPipelineJobType $type, array $inputData) + { + $this->id = Uuid::v7(); + $this->type = $type; + $this->inputData = $inputData; + $this->status = AIPipelineJobStatus::Queued; + $this->createdAt = new \DateTimeImmutable(); + } + + public function incrementAttempt(): void { ++$this->attemptCount; } + + public function markCompleted(): void + { + $this->status = AIPipelineJobStatus::Completed; + $this->completedAt = new \DateTimeImmutable(); + } + + public function markFailed(string $errorMessage): void + { + $this->status = AIPipelineJobStatus::Failed; + $this->errorMessage = $errorMessage; + } + + public function markNeedsReview(string $reason): void + { + $this->status = AIPipelineJobStatus::NeedsReview; + $this->errorMessage = $reason; + } + + public function getId(): Uuid { return $this->id; } + public function getType(): AIPipelineJobType { return $this->type; } + public function getArticle(): ?Article { return $this->article; } + public function setArticle(Article $article): void { $this->article = $article; } + public function getStatus(): AIPipelineJobStatus { return $this->status; } + public function setStatus(AIPipelineJobStatus $status): void { $this->status = $status; } + public function getAttemptCount(): int { return $this->attemptCount; } + + /** @return array */ + public function getInputData(): array { return $this->inputData; } + + /** @return array */ + public function getOutputData(): array { return $this->outputData; } + + /** @param array $outputData */ + public function setOutputData(array $outputData): void { $this->outputData = $outputData; } + + /** @return list */ + public function getMissingFields(): array { return $this->missingFields; } + + /** @param list $fields */ + public function setMissingFields(array $fields): void { $this->missingFields = $fields; } + + public function getErrorMessage(): ?string { return $this->errorMessage; } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } + public function getCompletedAt(): ?\DateTimeImmutable { return $this->completedAt; } +} diff --git a/tests/Unit/Domain/Order/CustomerTest.php b/tests/Unit/Domain/Order/CustomerTest.php new file mode 100644 index 0000000..30814e3 --- /dev/null +++ b/tests/Unit/Domain/Order/CustomerTest.php @@ -0,0 +1,39 @@ +assertSame([], $customer->getPlatformIds()); + $this->assertNull($customer->getPlatformId('ebay')); + } + + public function test_add_platform_id(): void + { + $customer = new Customer('Max Mustermann', 'max@example.com', []); + $customer->addPlatformId('ebay', 'ebay-user-123'); + + $this->assertSame('ebay-user-123', $customer->getPlatformId('ebay')); + } + + public function test_matching_key_is_lowercase_normalized(): void + { + $customer = new Customer('Max Mustermann', 'max@example.com', [ + 'street' => 'Musterstraße 1', + 'city' => 'Berlin', + 'zip' => '10115', + ]); + + $expected = 'max mustermann|musterstraße 1|berlin|10115'; + $this->assertSame($expected, $customer->getMatchingKey()); + } +}