From 72483b7ba87e8e8d6812d7f50168eb570b9b7737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:21:46 +0100 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=94=A5=20Removed=20unsupported=20func?= =?UTF-8?q?tion=20args=20in=20endpint=20definitions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Resources/Layout.php | 2 +- src/Resources/Product.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Resources/Layout.php b/src/Resources/Layout.php index 6f0967c..cfdc1ef 100644 --- a/src/Resources/Layout.php +++ b/src/Resources/Layout.php @@ -10,7 +10,7 @@ use Morningtrain\Economic\Traits\Resources\GetSingleable; #[GetCollection('layouts')] -#[GetSingle('/layouts/:layoutNumber', ':layoutNumber')] +#[GetSingle('layouts/:layoutNumber')] class Layout extends Resource { use GetCollectionable; diff --git a/src/Resources/Product.php b/src/Resources/Product.php index d70a601..02ee577 100644 --- a/src/Resources/Product.php +++ b/src/Resources/Product.php @@ -17,7 +17,7 @@ use Morningtrain\Economic\Traits\Resources\GetSingleable; #[GetCollection('products')] // https://restdocs.e-conomic.com/#get-products -#[GetSingle('products/:productNumber', ':productNumber')] +#[GetSingle('products/:productNumber')] #[Create('products')] #[Update('products/:productNumber', [':productNumber' => 'productNumber'])] #[Delete('products/:productNumber', [':productNumber' => 'productNumber'])] From b769a3dc575633f42da2f7bbbe2b04f06aa6612e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:22:20 +0100 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20Added=20API=20formatting=20prop?= =?UTF-8?q?erty=20attributes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApiFormatting/ResourceToArray.php | 36 +++++++++++++++++++ .../ApiFormatting/ResourceToPrimaryKey.php | 23 ++++++++++++ src/Interfaces/ApiFormatter.php | 8 +++++ 3 files changed, 67 insertions(+) create mode 100644 src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php create mode 100644 src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php create mode 100644 src/Interfaces/ApiFormatter.php diff --git a/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php new file mode 100644 index 0000000..9f5fcd5 --- /dev/null +++ b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php @@ -0,0 +1,36 @@ +properties = $properties; + } + + public function format($value): array + { + if(!is_a($value, Resource::class)) { + throw new \InvalidArgumentException('ResourceToArray can only be used on properties of type Resource'); + } + + $resourceAsArray = $value->toArray(); + + $returnArray = []; + + foreach($this->properties as $key) { + if(isset($resourceAsArray[$key])) { + $returnArray[$key] = $resourceAsArray[$key]; + } + } + + return $returnArray; + } +} diff --git a/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php new file mode 100644 index 0000000..5dfc05f --- /dev/null +++ b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php @@ -0,0 +1,23 @@ +getPrimaryKey(); + } +} diff --git a/src/Interfaces/ApiFormatter.php b/src/Interfaces/ApiFormatter.php new file mode 100644 index 0000000..401bedc --- /dev/null +++ b/src/Interfaces/ApiFormatter.php @@ -0,0 +1,8 @@ + Date: Sun, 25 Feb 2024 13:23:20 +0100 Subject: [PATCH 3/9] =?UTF-8?q?=20=E2=99=BB=EF=B8=8F=20Changed=20how=20arg?= =?UTF-8?q?s=20is=20resolved=20for=20Resources=20to=20match=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Abstracts/Resource.php | 128 ++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/src/Abstracts/Resource.php b/src/Abstracts/Resource.php index 7289ab1..75cd20b 100644 --- a/src/Abstracts/Resource.php +++ b/src/Abstracts/Resource.php @@ -10,12 +10,15 @@ use Morningtrain\Economic\Attributes\Resources\Properties\ResourceType; use Morningtrain\Economic\Classes\EconomicCollection; use Morningtrain\Economic\Classes\EconomicCollectionIterator; +use Morningtrain\Economic\Interfaces\ApiFormatter; use Morningtrain\Economic\Services\EconomicApiService; use Morningtrain\Economic\Services\EconomicLoggerService; +use ReflectionAttribute; use ReflectionClass; use ReflectionMethod; +use JsonSerializable; -abstract class Resource +abstract class Resource implements JsonSerializable { public string $self; @@ -129,47 +132,63 @@ public function setPrimaryKey(int|string|float $value): static return $this; } - public function toArray(): array + protected static function resolveArgs(array $args, bool $forApiRequest = false) { - $reflection = new ReflectionClass(static::class); + foreach($args as $key => &$value) { + if($value === null) { + if($forApiRequest) { + unset($args[$key]); // We don't want to send null values to the API + } - $properties = []; + continue; // We don't want to format null values + } - foreach ($reflection->getProperties() as $property) { - // If not public then skip - if (! $property->isPublic()) { - continue; + // Try to convert to resource + try { + $reflection = new ReflectionClass(static::class); + + $propertyReflection = $reflection->getProperty($key); + + $reflectionTypeName = $propertyReflection->getType()->getName(); + + // If is a class + if( + is_a($reflectionTypeName, Resource::class, true) && + !is_a($value, Resource::class) + ) { + $value = new ($propertyReflection->getType()->getName())($value); + } + } catch (Exception $e) { + // Do nothing, since we can't format the value } - // Set self if not set - if ( - $property->getName() == 'self' && - empty($this->{$property->getName()}) && - ! empty($this->getPrimaryKey()) - ) { + // If is for API request then we have some special format cases + if($forApiRequest) { + // Format the value if it has a special case defined try { - $self = EconomicApiService::createURL($this->getEndpoint(GetSingle::class, $this->getPrimaryKey())); + $reflection = new ReflectionClass(static::class); + + $propertyReflection = $reflection->getProperty($key); - $this->self = $self; + $attribute = $propertyReflection->getAttributes(ApiFormatter::class, ReflectionAttribute::IS_INSTANCEOF); + + if (! empty($attribute[0]) && ! empty($value)) { + $apiFormatter = $attribute[0]->newInstance(); + + $value = $apiFormatter->format($value); + } } catch (Exception $e) { - // Do nothing, since we can't get the self + // Do nothing, since we can't format the value } } - // If property is not set then skip - if (! isset($this->{$property->getName()})) { - continue; - } - - $value = $this->{$property->getName()}; - // If is EconomicCollection then only get the self property - if ($property->getType() == EconomicCollection::class) { + if (is_a($value, EconomicCollection::class)) { $value = $value->getSelf(); } - // If is Resource then get the array representation - if (is_a($value, Resource::class)) { + // If is Collection + if (is_a($value, Collection::class)) { $value = $value->toArray(); } @@ -178,53 +197,42 @@ public function toArray(): array $value = $value->format(DateTime::ATOM); } - $properties[$property->getName()] = $value; - } - - return $properties; - } - - public function __toString(): string - { - return json_encode($this->toArray()); - } - - protected static function filterEmpty(array $values): array - { - foreach ($values as $key => $value) { - if (is_array($value)) { - $values[$key] = static::filterEmpty($value); + // If is array (NOTE: need to be before converting to Resource to ensure we follow the Resource formatting) + if(is_array($value)) { + $value = static::resolveArgs($value, $forApiRequest); } - if ($values[$key] === null) { - unset($values[$key]); + // If is Resource then get the array representation + if (is_a($value, Resource::class)) { + $value = $value->toArray($forApiRequest); } } - return $values; + return $args; } - protected static function getMethodArgs(string $function, array $arguments): array + public function toArray(bool $forApiRequest = false): array { - $reflection = new ReflectionMethod(...explode('::', $function)); - - $assoc = []; + $vars = get_object_vars($this); - foreach ($reflection->getParameters() as $i => $parameter) { - $assoc[$parameter->getName()] = $arguments[$i] ?? null; + if(empty($vars['self']) && ! empty($this->getPrimaryKey())) { + try { + $vars['self'] = EconomicApiService::createURL($this->getEndpoint(GetSingle::class, $this->getPrimaryKey())); + } catch (Exception $e) { + // Do nothing, since we can't get the self + } } - return $assoc; + return static::resolveArgs($vars, $forApiRequest); } - protected static function resolveArguments(array $arguments): array + public function __toString(): string { - $creationParameters = []; - - foreach ($arguments as $name => $value) { - $creationParameters[$name] = static::resolvePropertyValue($name, $value); - } + return json_encode($this->toArray()); + } - return $creationParameters; + public function jsonSerialize(): array + { + return $this->toArray(); } } From 99d777a440018b4d29dfeedd942718a59ac26a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:24:15 +0100 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=94=A5=20Removed=20unnessesary=20code?= =?UTF-8?q?=20from=20DraftInvoice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Resources/Invoice/DraftInvoice.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Resources/Invoice/DraftInvoice.php b/src/Resources/Invoice/DraftInvoice.php index 6fd77eb..85a1d84 100644 --- a/src/Resources/Invoice/DraftInvoice.php +++ b/src/Resources/Invoice/DraftInvoice.php @@ -48,22 +48,6 @@ public static function create( // ?Project|int $project = null, // TODO: Implement ?Reference $references = null ): ?static { - if (is_a($currency, Currency::class)) { - $currency = $currency->isoNumber; - } - - if (is_int($customer)) { - $customer = new Customer(['customerNumber' => $customer]); - } - - if (is_int($layout)) { - $layout = new Layout(['layoutNumber' => $layout]); - } - - if (is_int($paymentTerms)) { - $paymentTerms = new PaymentTerm(['paymentTermsNumber' => $paymentTerms]); - } - return static::createRequest(compact( 'currency', 'customer', @@ -94,10 +78,6 @@ public static function new( // ?Project|int $project = null, // TODO: Implement ?Reference $references = null ): ?static { - if (is_a($currency, Currency::class)) { - $currency = $currency->isoNumber; - } - return new static(array_filter(compact( 'currency', 'customer', From 89d35119a30c2dd5d2a5a1df5e01395e25e314a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:25:03 +0100 Subject: [PATCH 5/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Changed=20Creatable=20?= =?UTF-8?q?and=20Updateble=20to=20use=20the=20new=20API=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Traits/Resources/Creatable.php | 12 +----------- src/Traits/Resources/Updatable.php | 4 ++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Traits/Resources/Creatable.php b/src/Traits/Resources/Creatable.php index 7b42087..be307fb 100644 --- a/src/Traits/Resources/Creatable.php +++ b/src/Traits/Resources/Creatable.php @@ -17,17 +17,7 @@ public static function createRequest($args, array $endpointReferences = []): ?st { // TODO: add validation method to check if required properties are set and primary key is not set - throw exception if not - foreach ($args as &$arg) { - if (is_a($arg, DateTime::class)) { - $arg = $arg->format(DateTime::ATOM); - } - } - - $args = json_decode(json_encode($args), true); // We need to convert objects to an array to avoid issues with the API - - if (method_exists(static::class, 'filterEmpty')) { - $args = static::filterEmpty($args); - } + $args = static::resolveArgs($args, true); // We need to convert objects to an array to avoid issues with the API $response = EconomicApiService::post(static::getEndpoint(Create::class, ...$endpointReferences), $args); diff --git a/src/Traits/Resources/Updatable.php b/src/Traits/Resources/Updatable.php index 93959f1..136ae9d 100644 --- a/src/Traits/Resources/Updatable.php +++ b/src/Traits/Resources/Updatable.php @@ -15,14 +15,14 @@ public function save(): static { // TODO: add validation method to check if required properties are set and primary key is set - throw exception if not - $response = EconomicApiService::put(static::getEndpoint(Update::class, $this), $this->toArray()); + $response = EconomicApiService::put(static::getEndpoint(Update::class, $this), $this->toArray(true)); if ($response->getStatusCode() !== 200) { EconomicLoggerService::error('Economic API Service returned a non 200 status code when updating a resource', [ 'status_code' => $response->getStatusCode(), 'response_body' => $response->getBody(), 'resource' => static::class, - 'args' => $this->toArray(), + 'args' => $this->toArray(true), ]); throw new Exception('Economic API Service returned a non 200 status code when updating a resource'); From acbd19e100b384d2c1048cb328d93fdc1e5b40d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:25:56 +0100 Subject: [PATCH 6/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Changed=20some=20resou?= =?UTF-8?q?rces=20to=20use=20the=20new=20API=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Resources/Currency.php | 6 +++--- src/Resources/Customer.php | 33 +++++++++++++++++++++++++----- src/Resources/Customer/Contact.php | 3 ++- src/Resources/Invoice/Invoice.php | 7 ++++++- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/Resources/Currency.php b/src/Resources/Currency.php index 130ddac..ccf0145 100644 --- a/src/Resources/Currency.php +++ b/src/Resources/Currency.php @@ -15,10 +15,10 @@ class Currency extends Resource { use GetCollectionable, GetSingleable; - public string $code; + public ?string $code = null; #[PrimaryKey] - public string $isoNumber; + public ?string $isoNumber = null; - public string $name; + public ?string $name = null; } diff --git a/src/Resources/Customer.php b/src/Resources/Customer.php index 79d55a3..5147b32 100644 --- a/src/Resources/Customer.php +++ b/src/Resources/Customer.php @@ -34,7 +34,7 @@ class Customer extends Resource public float $balance; - public bool $barred; + public ?bool $barred; public ?string $city; @@ -67,7 +67,7 @@ class Customer extends Resource public ?string $ean; - public bool $eInvoicingDisabledByDefault; + public ?bool $eInvoicingDisabledByDefault; public ?string $email; @@ -131,9 +131,32 @@ public static function create( Layout|int|null $layout = null, Employee|int|null $salesPerson = null, ): static { - $creationParameters = static::resolveArguments(static::getMethodArgs(__METHOD__, func_get_args())); - - return static::createRequest($creationParameters); + return static::createRequest(compact( + 'name', + 'customerGroup', + 'currency', + 'vatZone', + 'paymentTerms', + 'email', + 'address', + 'zip', + 'city', + 'country', + 'corporateIdentificationNumber', + 'pNumber', + 'vatNumber', + 'ean', + 'publicEntryNumber', + 'website', + 'mobilePhone', + 'telephoneAndFaxNumber', + 'barred', + 'eInvoicingDisabledByDefault', + 'creditLimit', + 'customerNumber', + 'layout', + 'salesPerson' + )); } public static function new( diff --git a/src/Resources/Customer/Contact.php b/src/Resources/Customer/Contact.php index 65b2443..6d92e2b 100644 --- a/src/Resources/Customer/Contact.php +++ b/src/Resources/Customer/Contact.php @@ -7,6 +7,7 @@ use Morningtrain\Economic\Attributes\Resources\Delete; use Morningtrain\Economic\Attributes\Resources\GetCollection; use Morningtrain\Economic\Attributes\Resources\GetSingle; +use Morningtrain\Economic\Attributes\Resources\Properties\ApiFormatting\ResourceToArray; use Morningtrain\Economic\Attributes\Resources\Properties\PrimaryKey; use Morningtrain\Economic\Attributes\Resources\Update; use Morningtrain\Economic\Classes\EconomicRelatedResource; @@ -28,6 +29,7 @@ class Contact extends Resource { use Creatable, Deletable, EndpointResolvable, GetCollectionable, GetSingleable, Updatable; + #[ResourceToArray('customerNumber', 'self')] public Customer $customer; #[PrimaryKey] @@ -73,7 +75,6 @@ public static function create( } return static::createRequest(compact( - 'customer', 'name', 'email', 'phone', diff --git a/src/Resources/Invoice/Invoice.php b/src/Resources/Invoice/Invoice.php index e17ddb2..5b3392c 100644 --- a/src/Resources/Invoice/Invoice.php +++ b/src/Resources/Invoice/Invoice.php @@ -4,9 +4,12 @@ use DateTime; use Morningtrain\Economic\Abstracts\Resource; +use Morningtrain\Economic\Attributes\Resources\Properties\ApiFormatting\ResourceToArray; +use Morningtrain\Economic\Attributes\Resources\Properties\ApiFormatting\ResourceToPrimaryKey; use Morningtrain\Economic\DTOs\Invoice\Note; use Morningtrain\Economic\DTOs\Invoice\Recipient; use Morningtrain\Economic\DTOs\Invoice\Reference; +use Morningtrain\Economic\Resources\Currency; use Morningtrain\Economic\Resources\Customer; use Morningtrain\Economic\Resources\Layout; use Morningtrain\Economic\Resources\PaymentTerm; @@ -22,8 +25,10 @@ class Invoice extends Resource use GetSingleable; use HasLines; - public ?string $currency = null; + #[ResourceToPrimaryKey()] + public ?Currency $currency = null; + #[ResourceToArray('customerNumber', 'self')] public ?Customer $customer = null; public ?DateTime $date = null; From f597dbc97d03f368880f3193e3f794dad343223b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:35:04 +0100 Subject: [PATCH 7/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20customer=20to=20?= =?UTF-8?q?api=20request=20for=20create=20contact=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Resources/Customer/Contact.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Resources/Customer/Contact.php b/src/Resources/Customer/Contact.php index 6d92e2b..4933e35 100644 --- a/src/Resources/Customer/Contact.php +++ b/src/Resources/Customer/Contact.php @@ -75,6 +75,7 @@ public static function create( } return static::createRequest(compact( + 'customer', 'name', 'email', 'phone', From 58e0de4d1b56b76a006548a9c24b09daf73187bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schadegg=20Br=C3=B8nniche?= Date: Sun, 25 Feb 2024 13:36:43 +0100 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=85=20Updated=20tests=20to=20contain?= =?UTF-8?q?=20self=20in=20api=20requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Customers/Contacts/create-request.json | 5 +- .../Customers/Contacts/update-request.json | 8 +-- .../create-request-with-falsy-values.json | 17 +++++ tests/Fixtures/Customers/create-request.json | 16 +++++ .../{create.json => create-response.json} | 0 .../Invoices/draft/create-request.json | 12 ++-- tests/Fixtures/Products/create-request.json | 17 +++++ tests/Fixtures/Products/create-response.json | 45 ++++++++++++ tests/Unit/CustomerTest.php | 72 +++++-------------- tests/Unit/InvoiceTest.php | 57 ++++++++++++++- tests/Unit/ProductTest.php | 47 +----------- 11 files changed, 184 insertions(+), 112 deletions(-) create mode 100644 tests/Fixtures/Customers/create-request-with-falsy-values.json create mode 100644 tests/Fixtures/Customers/create-request.json rename tests/Fixtures/Customers/{create.json => create-response.json} (100%) create mode 100644 tests/Fixtures/Products/create-request.json create mode 100644 tests/Fixtures/Products/create-response.json diff --git a/tests/Fixtures/Customers/Contacts/create-request.json b/tests/Fixtures/Customers/Contacts/create-request.json index 2b94bcb..a81b1a2 100644 --- a/tests/Fixtures/Customers/Contacts/create-request.json +++ b/tests/Fixtures/Customers/Contacts/create-request.json @@ -1,6 +1,7 @@ { - "customer": { - "customerNumber": 1 + "customer":{ + "customerNumber": "1", + "self": "https://restapi.e-conomic.com/customers/1" }, "name": "John Doe", "email": "ms@morningtrain.dk", diff --git a/tests/Fixtures/Customers/Contacts/update-request.json b/tests/Fixtures/Customers/Contacts/update-request.json index 08ebb31..67c4622 100644 --- a/tests/Fixtures/Customers/Contacts/update-request.json +++ b/tests/Fixtures/Customers/Contacts/update-request.json @@ -1,11 +1,11 @@ { + "self": "https:\/\/restapi.e-conomic.com\/customers\/1\/contacts\/140", "customer": { - "customerNumber": 1, - "self": "https:\/\/restapi.e-conomic.com\/customers\/1" + "self": "https:\/\/restapi.e-conomic.com\/customers\/1", + "customerNumber": 1 }, "customerContactNumber": 140, "emailNotifications": [], "name": "Martin", - "sortKey": 5, - "self": "https:\/\/restapi.e-conomic.com\/customers\/1\/contacts\/140" + "sortKey": 5 } diff --git a/tests/Fixtures/Customers/create-request-with-falsy-values.json b/tests/Fixtures/Customers/create-request-with-falsy-values.json new file mode 100644 index 0000000..eae53d5 --- /dev/null +++ b/tests/Fixtures/Customers/create-request-with-falsy-values.json @@ -0,0 +1,17 @@ +{ + "name": "John Doe", + "customerGroup": { + "customerGroupNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/customer-groups\/1" + }, + "currency": "DKK", + "vatZone": { + "vatZoneNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/vat-zones\/1" + }, + "paymentTerms": { + "paymentTermsNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/payment-terms\/1" + }, + "customerNumber": 0 +} diff --git a/tests/Fixtures/Customers/create-request.json b/tests/Fixtures/Customers/create-request.json new file mode 100644 index 0000000..8d3b078 --- /dev/null +++ b/tests/Fixtures/Customers/create-request.json @@ -0,0 +1,16 @@ +{ + "name": "John Doe", + "customerGroup": { + "customerGroupNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/customer-groups\/1" + }, + "currency": "DKK", + "vatZone": { + "vatZoneNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/vat-zones\/1" + }, + "paymentTerms": { + "paymentTermsNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/payment-terms\/1" + } +} diff --git a/tests/Fixtures/Customers/create.json b/tests/Fixtures/Customers/create-response.json similarity index 100% rename from tests/Fixtures/Customers/create.json rename to tests/Fixtures/Customers/create-response.json diff --git a/tests/Fixtures/Invoices/draft/create-request.json b/tests/Fixtures/Invoices/draft/create-request.json index a5da1f9..5185078 100644 --- a/tests/Fixtures/Invoices/draft/create-request.json +++ b/tests/Fixtures/Invoices/draft/create-request.json @@ -1,19 +1,23 @@ { "currency": "DKK", "customer": { - "customerNumber": 1 + "customerNumber": 1, + "self": "https://restapi.e-conomic.com/customers/1" }, "date": "2024-02-13T12:20:18+00:00", "layout": { - "layoutNumber": 14 + "layoutNumber": 14, + "self": "https://restapi.e-conomic.com/layouts/14" }, "paymentTerms": { - "paymentTermsNumber": 1 + "paymentTermsNumber": 1, + "self": "https://restapi.e-conomic.com/payment-terms/1" }, "recipient": { "name": "John Doe", "vatZone": { - "vatZoneNumber": 1 + "vatZoneNumber": 1, + "self": "https://restapi.e-conomic.com/vat-zones/1" } }, "lines": [ diff --git a/tests/Fixtures/Products/create-request.json b/tests/Fixtures/Products/create-request.json new file mode 100644 index 0000000..cb50679 --- /dev/null +++ b/tests/Fixtures/Products/create-request.json @@ -0,0 +1,17 @@ +{ + "name": "Product 1", + "productGroup": { + "productGroupNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/product-groups\/1" + }, + "productNumber": "p-1", + "barCode": "1234567890", + "costPrice": 100, + "description": "test", + "recommendedPrice": 150, + "salesPrice": 199.95, + "unit": { + "unitNumber": 1, + "self": "https:\/\/restapi.e-conomic.com\/units\/1" + } +} diff --git a/tests/Fixtures/Products/create-response.json b/tests/Fixtures/Products/create-response.json new file mode 100644 index 0000000..a48d15e --- /dev/null +++ b/tests/Fixtures/Products/create-response.json @@ -0,0 +1,45 @@ +{ + "productNumber": "p-1", + "description": "test", + "name": "Product 1", + "costPrice": 100.00, + "recommendedPrice": 150.00, + "salesPrice": 199.95, + "barCode": "1234567890", + "barred": false, + "lastUpdated": "2024-02-25T10:46:00Z", + "productGroup": { + "productGroupNumber": 1, + "name": "Product Group 1", + "salesAccounts": "https://restapi.e-conomic.com/product-groups/1/sales-accounts", + "products": "https://restapi.e-conomic.com/product-groups/1/products", + "self": "https://restapi.e-conomic.com/product-groups/1" + }, + "unit": { + "unitNumber": 1, + "name": "Piece", + "products": "https://restapi.e-conomic.com/units/1/products", + "self": "https://restapi.e-conomic.com/units/1" + }, + "invoices": { + "drafts": "https://restapi.e-conomic.com/products/p-1/invoices/drafts", + "booked": "https://restapi.e-conomic.com/products/p-1/invoices/booked", + "self": "https://restapi.e-conomic.com/products/p-1/invoices" + }, + "pricing": { + "currencySpecificSalesPrices": "https://restapi.e-conomic.com/products/p-1/pricing/currency-specific-sales-prices" + }, + "metaData": { + "delete": { + "description": "Delete this product.", + "href": "https://restapi.e-conomic.com/products/p-1", + "httpMethod": "delete" + }, + "replace": { + "description": "Replace this product.", + "href": "https://restapi.e-conomic.com/products/p-1", + "httpMethod": "put" + } + }, + "self": "https://restapi.e-conomic.com/products/p-1" +} diff --git a/tests/Unit/CustomerTest.php b/tests/Unit/CustomerTest.php index 24ce2cf..813c50b 100644 --- a/tests/Unit/CustomerTest.php +++ b/tests/Unit/CustomerTest.php @@ -48,32 +48,19 @@ }); it('creates customer', function () { - $this->driver->expects()->post() - ->withArgs(function (string $url, array $body) { - return $url === 'https://restapi.e-conomic.com/customers' - && $body === [ - 'name' => 'John Doe', - 'customerGroup' => [ - 'customerGroupNumber' => 1, - ], - 'currency' => 'DKK', - 'vatZone' => [ - 'vatZoneNumber' => 1, - ], - 'paymentTerms' => [ - 'paymentTermsNumber' => 1, - ], - ]; - }) + $this->driver->expects()->post( + 'https://restapi.e-conomic.com/customers', + fixture('Customers/create-request') + ) ->once() - ->andReturn(new EconomicResponse(201, fixture('Customers/create'))); + ->andReturn(new EconomicResponse(201, fixture('Customers/create-response'))); $customer = Customer::create( name: 'John Doe', customerGroup: 1, currency: 'DKK', vatZone: 1, - paymentTerms: 1, + paymentTerms: 1 ); expect($customer) @@ -116,25 +103,12 @@ }); it('filters null values', function () { - $this->driver->expects()->post() - ->withArgs(function (string $url, array $body) { - return $url === 'https://restapi.e-conomic.com/customers' - && $body === [ - 'name' => 'John Doe', - 'customerGroup' => [ - 'customerGroupNumber' => 1, - ], - 'currency' => 'DKK', - 'vatZone' => [ - 'vatZoneNumber' => 1, - ], - 'paymentTerms' => [ - 'paymentTermsNumber' => 1, - ], - ]; - }) + $this->driver->expects()->post( + 'https://restapi.e-conomic.com/customers', + fixture('Customers/create-request') + ) ->once() - ->andReturn(new EconomicResponse(201, fixture('Customers/create'))); + ->andReturn(new EconomicResponse(201, fixture('Customers/create-response'))); Customer::create( name: 'John Doe', @@ -147,26 +121,12 @@ }); it('does not filter falsy values', function () { - $this->driver->expects()->post() - ->withArgs(function (string $url, array $body) { - return $url === 'https://restapi.e-conomic.com/customers' - && $body === [ - 'name' => 'John Doe', - 'customerGroup' => [ - 'customerGroupNumber' => 1, - ], - 'currency' => 'DKK', - 'vatZone' => [ - 'vatZoneNumber' => 1, - ], - 'paymentTerms' => [ - 'paymentTermsNumber' => 1, - ], - 'customerNumber' => 0, // Should be present in request - ]; - }) + $this->driver->expects()->post( + 'https://restapi.e-conomic.com/customers', + fixture('Customers/create-request-with-falsy-values') + ) ->once() - ->andReturn(new EconomicResponse(201, fixture('Customers/create'))); + ->andReturn(new EconomicResponse(201, fixture('Customers/create-request'))); Customer::create( name: 'John Doe', diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index f7444db..97b453c 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -7,6 +7,7 @@ use Morningtrain\Economic\DTOs\Invoice\ProductLine; use Morningtrain\Economic\DTOs\Invoice\Recipient; use Morningtrain\Economic\DTOs\Invoice\Reference; +use Morningtrain\Economic\Resources\Currency; use Morningtrain\Economic\Resources\Customer; use Morningtrain\Economic\Resources\Invoice\BookedInvoice; use Morningtrain\Economic\Resources\Invoice\DraftInvoice; @@ -28,7 +29,7 @@ ->draftInvoiceNumber->toBe(422) ->externalId->toBe('123456') ->date->toBeInstanceOf(DateTime::class) - ->currency->toBe('DKK') + ->currency->toBeInstanceOf(Currency::class) ->exchangeRate->toBeFloat() ->netAmount->toBeFloat() ->grossAmount->toBeFloat() @@ -145,6 +146,60 @@ ->draftInvoiceNumber->toBe(424); }); +it('creates a draft invoice with full customer and currency object', function () { + $this->driver->expects()->post()->with( + 'https://restapi.e-conomic.com/invoices/drafts', + fixture('Invoices/draft/create-request') + )->andReturn(new EconomicResponse(201, fixture('Invoices/draft/create-response'))); + + $invoice = DraftInvoice::new( + new Currency([ + 'code' => 'DKK', + 'isoNumber' => 'DKK', + 'name' => 'Danish Krone' + ]), + Customer::new( + name: 'John Doe', + customerNumber: 1, + currency: 'DKK', + email: 'ms@morningtrain.dk', + address: 'Test Street 1', + vatZone: 1, + customerGroup: 1, + paymentTerms: 1, + ), + new DateTime('2024-02-13T12:20:18+00:00'), + 14, + 1, + Recipient::new( + 'John Doe', + new VatZone(1), + ), + notes: Note::new( + heading: 'Heading', + textLine1: 'Text line 1', + textLine2: 'Text line 2' + ) + ); + + $invoice->addLine( + ProductLine::new( + description: 'T-shirt - Size L', + product: new Product([ + 'productNumber' => 1, + ]), + quantity: 1, + unitNetPrice: 500 + ) + ); + + $invoice->save(); + + expect($invoice) + ->toBeInstanceOf(DraftInvoice::class) + ->draftInvoiceNumber->toBe(424); +}); + it('books draft invoice', function () { $this->driver->expects()->post( 'https://restapi.e-conomic.com/invoices/booked', diff --git a/tests/Unit/ProductTest.php b/tests/Unit/ProductTest.php index fc52f96..5c280fe 100644 --- a/tests/Unit/ProductTest.php +++ b/tests/Unit/ProductTest.php @@ -142,51 +142,8 @@ it('creates a product', function () { $this->driver->expects()->post() - ->with('https://restapi.e-conomic.com/products', [ - 'name' => 'Product 1', - 'productGroup' => [ - 'productGroupNumber' => 1, - ], - 'productNumber' => 'p-1', - 'barCode' => '1234567890', - 'costPrice' => 100, - 'description' => 'test', - 'recommendedPrice' => 150, - 'salesPrice' => 199.95, - 'unit' => [ - 'unitNumber' => 1, - ], - ]) - ->andReturn(new EconomicResponse(201, [ - 'productNumber' => 'p-1', - 'description' => 'test', - 'name' => 'Product 1', - 'costPrice' => 100.0, - 'recommendedPrice' => 150.0, - 'salesPrice' => 199.95, - 'barCode' => '1234567890', - 'barred' => false, - 'lastUpdated' => '2022-01-13T12:43:00Z', - 'productGroup' => [ - 'productGroupNumber' => 1, - 'name' => 'Product Group 1', - 'self' => 'https://restapi.e-conomic.com/product-groups/1', - ], - 'unit' => [ - 'unitNumber' => 1, - 'name' => 'Piece', - 'self' => 'https://restapi.e-conomic.com/units/1', - ], - 'invoices' => [ - 'drafts' => 'https://restapi.e-conomic.com/products/p-1/invoices/drafts', - 'booked' => 'https://restapi.e-conomic.com/products/p-1/invoices/booked', - 'self' => 'https://restapi.e-conomic.com/products/p-1/invoices', - ], - 'pricing' => [ - 'currencySpecificSalesPrices' => 'https://restapi.e-conomic.com/products/p-1/pricing/currency-specific-sales-prices', - ], - 'self' => 'https://restapi.e-conomic.com/products/p-1', - ])); + ->with('https://restapi.e-conomic.com/products', fixture('Products/create-request')) + ->andReturn(new EconomicResponse(201, fixture('Products/create-response'))); $product = Product::create('Product 1', 1, 'p-1', barCode: '1234567890', costPrice: 100.0, recommendedPrice: 150.0, salesPrice: 199.95, description: 'test', unit: 1); From 175471fd7b4caff6b3cd00bbba5a6fd0d01f029f Mon Sep 17 00:00:00 2001 From: mschadegg Date: Sun, 25 Feb 2024 12:37:13 +0000 Subject: [PATCH 9/9] Fix styling --- src/Abstracts/Resource.php | 19 +++++++++---------- .../ApiFormatting/ResourceToArray.php | 6 +++--- .../ApiFormatting/ResourceToPrimaryKey.php | 2 +- src/Resources/Customer.php | 2 +- src/Traits/Resources/Creatable.php | 1 - tests/Unit/CustomerTest.php | 2 +- tests/Unit/InvoiceTest.php | 2 +- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Abstracts/Resource.php b/src/Abstracts/Resource.php index 75cd20b..3de493b 100644 --- a/src/Abstracts/Resource.php +++ b/src/Abstracts/Resource.php @@ -5,6 +5,7 @@ use DateTime; use Exception; use Illuminate\Support\Collection; +use JsonSerializable; use Morningtrain\Economic\Attributes\Resources\GetSingle; use Morningtrain\Economic\Attributes\Resources\Properties\PrimaryKey; use Morningtrain\Economic\Attributes\Resources\Properties\ResourceType; @@ -15,8 +16,6 @@ use Morningtrain\Economic\Services\EconomicLoggerService; use ReflectionAttribute; use ReflectionClass; -use ReflectionMethod; -use JsonSerializable; abstract class Resource implements JsonSerializable { @@ -134,9 +133,9 @@ public function setPrimaryKey(int|string|float $value): static protected static function resolveArgs(array $args, bool $forApiRequest = false) { - foreach($args as $key => &$value) { - if($value === null) { - if($forApiRequest) { + foreach ($args as $key => &$value) { + if ($value === null) { + if ($forApiRequest) { unset($args[$key]); // We don't want to send null values to the API } @@ -152,9 +151,9 @@ protected static function resolveArgs(array $args, bool $forApiRequest = false) $reflectionTypeName = $propertyReflection->getType()->getName(); // If is a class - if( + if ( is_a($reflectionTypeName, Resource::class, true) && - !is_a($value, Resource::class) + ! is_a($value, Resource::class) ) { $value = new ($propertyReflection->getType()->getName())($value); } @@ -163,7 +162,7 @@ protected static function resolveArgs(array $args, bool $forApiRequest = false) } // If is for API request then we have some special format cases - if($forApiRequest) { + if ($forApiRequest) { // Format the value if it has a special case defined try { $reflection = new ReflectionClass(static::class); @@ -198,7 +197,7 @@ protected static function resolveArgs(array $args, bool $forApiRequest = false) } // If is array (NOTE: need to be before converting to Resource to ensure we follow the Resource formatting) - if(is_array($value)) { + if (is_array($value)) { $value = static::resolveArgs($value, $forApiRequest); } @@ -215,7 +214,7 @@ public function toArray(bool $forApiRequest = false): array { $vars = get_object_vars($this); - if(empty($vars['self']) && ! empty($this->getPrimaryKey())) { + if (empty($vars['self']) && ! empty($this->getPrimaryKey())) { try { $vars['self'] = EconomicApiService::createURL($this->getEndpoint(GetSingle::class, $this->getPrimaryKey())); } catch (Exception $e) { diff --git a/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php index 9f5fcd5..4e71fff 100644 --- a/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php +++ b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToArray.php @@ -17,7 +17,7 @@ public function __construct(...$properties) public function format($value): array { - if(!is_a($value, Resource::class)) { + if (! is_a($value, Resource::class)) { throw new \InvalidArgumentException('ResourceToArray can only be used on properties of type Resource'); } @@ -25,8 +25,8 @@ public function format($value): array $returnArray = []; - foreach($this->properties as $key) { - if(isset($resourceAsArray[$key])) { + foreach ($this->properties as $key) { + if (isset($resourceAsArray[$key])) { $returnArray[$key] = $resourceAsArray[$key]; } } diff --git a/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php index 5dfc05f..21beda0 100644 --- a/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php +++ b/src/Attributes/Resources/Properties/ApiFormatting/ResourceToPrimaryKey.php @@ -14,7 +14,7 @@ public function __construct() public function format($value): mixed { - if(!is_a($value, Resource::class)) { + if (! is_a($value, Resource::class)) { throw new \InvalidArgumentException('ResourceToArray can only be used on properties of type Resource'); } diff --git a/src/Resources/Customer.php b/src/Resources/Customer.php index 5147b32..6f6a7f5 100644 --- a/src/Resources/Customer.php +++ b/src/Resources/Customer.php @@ -131,7 +131,7 @@ public static function create( Layout|int|null $layout = null, Employee|int|null $salesPerson = null, ): static { - return static::createRequest(compact( + return static::createRequest(compact( 'name', 'customerGroup', 'currency', diff --git a/src/Traits/Resources/Creatable.php b/src/Traits/Resources/Creatable.php index be307fb..a28fd93 100644 --- a/src/Traits/Resources/Creatable.php +++ b/src/Traits/Resources/Creatable.php @@ -2,7 +2,6 @@ namespace Morningtrain\Economic\Traits\Resources; -use DateTime; use Exception; use Morningtrain\Economic\Attributes\Resources\Create; use Morningtrain\Economic\Services\EconomicApiService; diff --git a/tests/Unit/CustomerTest.php b/tests/Unit/CustomerTest.php index 813c50b..7f5b6ad 100644 --- a/tests/Unit/CustomerTest.php +++ b/tests/Unit/CustomerTest.php @@ -124,7 +124,7 @@ $this->driver->expects()->post( 'https://restapi.e-conomic.com/customers', fixture('Customers/create-request-with-falsy-values') - ) + ) ->once() ->andReturn(new EconomicResponse(201, fixture('Customers/create-request'))); diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index 97b453c..dbeb00e 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -156,7 +156,7 @@ new Currency([ 'code' => 'DKK', 'isoNumber' => 'DKK', - 'name' => 'Danish Krone' + 'name' => 'Danish Krone', ]), Customer::new( name: 'John Doe',