From d1308c8f7bd8c7ec3dd2c58e73b1d4db22046e16 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 9 Feb 2023 20:50:36 +1100 Subject: [PATCH 1/4] Add class models for autocomplete --- composer.json | 3 +- src/Exceptions/ApiException.php | 5 +- src/Exceptions/ModelValidationException.php | 16 +++ src/Models/BaseModel.php | 87 +++++++++++++++ src/Models/Client.php | 104 +++++++++++++++++ src/Models/ClientContact.php | 36 ++++++ src/Models/Invoice.php | 118 ++++++++++++++++++++ src/Models/InvoiceItem.php | 76 +++++++++++++ tests/BankIntegrationsTest.php | 2 +- tests/Invoice/CreateInvoiceTest.php | 72 ++++++++++++ 10 files changed, 515 insertions(+), 4 deletions(-) create mode 100644 src/Exceptions/ModelValidationException.php create mode 100644 src/Models/BaseModel.php create mode 100644 src/Models/Client.php create mode 100644 src/Models/ClientContact.php create mode 100644 src/Models/Invoice.php create mode 100644 src/Models/InvoiceItem.php create mode 100644 tests/Invoice/CreateInvoiceTest.php diff --git a/composer.json b/composer.json index f24d119..a8b3b9c 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ }, "require": { "php": "^7.4|^8.0|^8.1", - "guzzlehttp/guzzle": "^7.3" + "guzzlehttp/guzzle": "^7.3", + "nesbot/carbon": "^2.66" }, "require-dev": { "fakerphp/faker": "^1.16", diff --git a/src/Exceptions/ApiException.php b/src/Exceptions/ApiException.php index 0da12db..6ca5f27 100644 --- a/src/Exceptions/ApiException.php +++ b/src/Exceptions/ApiException.php @@ -32,6 +32,7 @@ protected static function parseResponseBody($response) $body = (string) $response->getBody(); $error = ""; $object = @json_decode($body); + $error_array = []; if(property_exists($object, "message")) $error = $object->message; @@ -40,10 +41,10 @@ protected static function parseResponseBody($response) { $properties = array_keys(get_object_vars($object->errors)); - if(is_array($properties)) + if(is_array($properties) && count($properties) >=1) $error_array = $object->errors->{$properties[0]}; - if(is_array($error_array)) + if(is_array($error_array) && count($error_array) >=1) $error = $error_array[0]; } diff --git a/src/Exceptions/ModelValidationException.php b/src/Exceptions/ModelValidationException.php new file mode 100644 index 0000000..081f61b --- /dev/null +++ b/src/Exceptions/ModelValidationException.php @@ -0,0 +1,16 @@ +resetValidationErrors(); + + foreach($this->rules as $key => $rule) + { + + switch($rule) + { + case 'required': + isset($this->{$key}) ?: $this->setValidationErrors(["error" => "{$this->model} - `{$key}` must be set", "code" => 422]); + break; + case 'date': + isset($this->{$key}) ? $this->checkDate($key) : true; + break; + default: + $this->setValidationErrors(["error" => "{$this->model} - Unknown validation rule `{$rule}` for property `{$key}`", "code" => 500]); + + } + + } + + if(count($this->getValidationErrors()) == 0) + return true; + + throw new ModelValidationException($this->getValidationErrors()[0]["error"] ,$this->getValidationErrors()[0]["code"]); + } + + private function resetValidationErrors() + { + $this->validation_errors = []; + + return $this; + } + + private function setValidationErrors($error): self + { + $this->validation_errors[] = $error; + + return $this; + } + + public function getValidationErrors(): array + { + return $this->validation_errors; + } + + public function checkDate(string $date_field): bool + { + + try { + + $parsed_date = \Carbon\Carbon::parse($this->{$date_field}); + $this->{$date_field} = $parsed_date->format('Y-m-d'); + + return true; + } + catch(\Exception $e) { + $this->setValidationErrors([$date_field => "Invalid date format for field {$date_field} - '{$this->{$date_field}}'"]); + return false; + } + } + + + + + +} \ No newline at end of file diff --git a/src/Models/Client.php b/src/Models/Client.php new file mode 100644 index 0000000..2510ec7 --- /dev/null +++ b/src/Models/Client.php @@ -0,0 +1,104 @@ + 'required', + ]; + + public function __construct() + { + + } + + public function setContact(ClientContact $contact): self + { + $this->contacts = array_merge($this->contacts, $contact); + + return $this; + } + + public function getContacts(): array + { + return $this->contacts; + } + + public function save() + { + $this->validate(); + } +} diff --git a/src/Models/ClientContact.php b/src/Models/ClientContact.php new file mode 100644 index 0000000..fdebee5 --- /dev/null +++ b/src/Models/ClientContact.php @@ -0,0 +1,36 @@ + 'required', + 'date' => 'date', + 'due_date' => 'date', + 'partial_due_date' => 'date', + ]; + + private array $validation_errors = []; + + public function __construct() + { + + } + + public function setClient(Client $client): self + { + $this->client_id = $client->id; + + return $this; + } + + public function setClientId(string $client_id): self + { + $this->client_id = $client->id; + + return $this; + } + + public function addLine(InvoiceItem $item) + { + $this->line_items = array_merge($this->line_items, $item); + + return $this; + } + + + //////////////////////////////////////// helpers - for abstraction/////////////////////////////////////////// + + + + +} \ No newline at end of file diff --git a/src/Models/InvoiceItem.php b/src/Models/InvoiceItem.php new file mode 100644 index 0000000..ee18d82 --- /dev/null +++ b/src/Models/InvoiceItem.php @@ -0,0 +1,76 @@ +setUrl($this->url); $bank_integrations = $ninja->bank_integrations->create(['bank_account_name' => $this->faker->firstName]); - + $this->assertTrue(is_array($bank_integrations)); } diff --git a/tests/Invoice/CreateInvoiceTest.php b/tests/Invoice/CreateInvoiceTest.php new file mode 100644 index 0000000..220acb0 --- /dev/null +++ b/tests/Invoice/CreateInvoiceTest.php @@ -0,0 +1,72 @@ +faker = \Faker\Factory::create(); + } + + // $ninja = new InvoiceNinja($this->token); + // $ninja->setUrl($this->url); + + public function testInitInvoice() + { + $invoice = new Invoice(); + $this->assertInstanceOf(Invoice::class, $invoice); + } + + public function testInitItem() + { + $item = new InvoiceItem(); + $this->assertInstanceOf(InvoiceItem::class, $item); + } + + public function testBaseName() + { + $client = new Client; + + $this->assertEquals("InvoiceNinja\Sdk\Models\Client", get_class($client)); + } + + public function testClientValidation() + { + $client = new Client; + $client->name = 'Jim'; + + $client->save(); + + $this->assertInstanceOf(Client::class, $client); + } + + // public function testBuildInvoice() + // { + // $client = new Client; + // $client->name = 'Jim'; + // $client->save(); + + // $invoice = new Invoice; + // $invoice->setClient($client); + // } +} \ No newline at end of file From 0f5c39ef451306ef3963d9b2e667300979d760cd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 9 Feb 2023 21:13:10 +1100 Subject: [PATCH 2/4] Doc blocks for class definitions --- src/Models/Client.php | 3 + src/Models/Invoice.php | 163 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 158 insertions(+), 8 deletions(-) diff --git a/src/Models/Client.php b/src/Models/Client.php index 2510ec7..66758c2 100644 --- a/src/Models/Client.php +++ b/src/Models/Client.php @@ -72,6 +72,9 @@ class Client extends BaseModel public ?string $custom_value4 = null; /** + * The array of client contacts. This is optional, however + * the API will always set a blank ClientContact if none are set + * * @var ClientContact[] */ public array $contacts = []; diff --git a/src/Models/Invoice.php b/src/Models/Invoice.php index b49a36a..3627330 100644 --- a/src/Models/Invoice.php +++ b/src/Models/Invoice.php @@ -20,58 +20,205 @@ class Invoice extends BaseModel public const STATUS_DRAFT = 1; + + /** + * The status of the invoice - readonly + * + * STATUS_DRAFT = 1; + * STATUS_SENT = 2; + * STATUS_PARETIAL = 3; + * STATUS_PAID = 4; + */ public int $status_id = self::STATUS_DRAFT; + /** + * Invoice number - optional + * + * The system will apply this if no invoice number is set. + * The invoice number _must_ be unique else a validation + * error will be returned + */ public ?string $number = null; + /** + * Discount - optional + * + */ public float $discount = 0; + /** + * Is amount discount - optional + * + * If set true, a discount is set in a currency amount + * If set false a discount is set in a percentage + */ public bool $is_amount_discount = true; + /** + * Purchase order - optional + * + */ public string $po_number = ''; + /** + * Invoice footer - optional + * + * If a default company footer is set, then it will + * be applied if nothing is set + */ public string $footer = ''; + + /** + * Invoice terms - optional + * + * If a default company terms is set, then it will + * be applied if nothing is set + */ public string $terms = ''; + /** + * Public notes - optional + * + */ public string $public_notes = ''; + /** + * Private notes - optional + * + */ public string $private_notes = ''; + /** + * The invoice date - optional + * + * format Y-m-d + * + * If left blank the API will set todays date + */ public ?string $date = null; + + /** + * The invoice due date - optional + * + * format Y-m-d + * + * If left blank the API will set the due date + * based on the default payment terms - if set + */ public ?string $due_date = null; + /** + * Partial / Deposit - optional + * + */ public float $partial = 0; + /** + * The invoice partial due date - optional + * + * format Y-m-d + * + * The date which the partial / deposit is due + */ public ?string $partial_due_date = null; + /** + * A boolean flag which defines if the invoice + * is deleted + * + * @property-read bool $is_deleted + */ public bool $is_deleted = false; + /** + * A array of invoice items - optional + * + * @var InvoiceItem[] + */ public array $line_items = []; + /** + * Tax Name 1 name - optional + * + * ie GST + */ public string $tax_name1 = ''; + /** + * Tax Rate 1 - optional + * + * ie 10 + */ public float $tax_rate1 = 0; + /** + * Tax Name 2 name - optional + * + * ie GST + */ public string $tax_name2 = ''; + /** + * Tax Rate 2 - optional + * + * ie 10 + */ public float $tax_rate2 = 0; + + /** + * Tax Name 3 name - optional + * + * ie GST + */ public string $tax_name3 = ''; + /** + * Tax Rate 3 - optional + * + * ie 10 + */ public float $tax_rate3 = 0; - public string $custom_value1 = ''; - - public string $custom_value2 = ''; - - public string $custom_value3 = ''; - - public string $custom_value4 = ''; - + /** + * Custom value - optional + * + */ + public ?string $custom_value1 = null; + + /** + * Custom value - optional + * + */ + public ?string $custom_value2 = null; + + /** + * Custom value - optional + * + */ + public ?string $custom_value3 = null; + + /** + * Custom value - optional + * + */ + public ?string $custom_value4 = null; + + /** + * Exchange rate - optional + * + * default 1 + */ public float $exchange_rate = 1; + /** + * Client ID - required + * + * The hashed ID of the client + * + */ public ?string $client_id = null; protected array $rules = [ From 9fd9b0d5ab63570ff930dd852b7c5081b238f6f4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 9 Feb 2023 21:39:03 +1100 Subject: [PATCH 3/4] Client Settings Model --- src/Models/Client.php | 190 ++++++++++++++++++++++++++++++++-- src/Models/ClientContact.php | 23 ++++ src/Models/ClientSettings.php | 71 +++++++++++++ src/Models/InvoiceItem.php | 7 ++ 4 files changed, 282 insertions(+), 9 deletions(-) create mode 100644 src/Models/ClientSettings.php diff --git a/src/Models/Client.php b/src/Models/Client.php index 66758c2..9c646a3 100644 --- a/src/Models/Client.php +++ b/src/Models/Client.php @@ -12,6 +12,7 @@ namespace InvoiceNinja\Sdk\Models; use InvoiceNinja\Sdk\Models\ClientContact; +use InvoiceNinja\Sdk\Models\ClientSettings; class Client extends BaseModel { @@ -37,19 +38,24 @@ class Client extends BaseModel public ?string $public_notes = null; /** - * Country ID - optional|autoset If no value is set - * then the system will apply the company country ID. - * For the full list of country IDs you can use this reference - * - * https://invoiceninja.github.io/docs/statics/#page-content + * Phone - optional */ - public ?string $country_id = null; + public ?string $phone = null; /** - * Country Code - optional iso_3166_2 or iso_3166_3 country code - * This can be used optionally instead of the country_id field + * VAT/GST/Tax number - optional */ - public ?string $country_code = null; + public ?string $vat_number = null; + + /** + * ID number - optional + */ + public ?string $id_number = null; + + /** + * Number - optional + */ + public ?string $number = null; /** * Custom value 1 - optional @@ -78,6 +84,172 @@ class Client extends BaseModel * @var ClientContact[] */ public array $contacts = []; + + /** + * Industry ID - optional + */ + public ?int $industry_id = null; + + /** + * Size ID - optional + */ + public ?int $size_id = null; + + /** + * Address line 1 - optional + */ + public ?string $address1 = null; + + /** + * Address line 2 - optional + */ + public ?string $address2 = null; + + /** + * City - optional + */ + public ?string $city = null; + + /** + * State - optional + */ + public ?string $state = null; + + /** + * Postal Code - optional + */ + public ?string $postal_code = null; + + /** + * Country ID - optional|autoset If no value is set + * then the system will apply the company country ID. + * For the full list of country IDs you can use this reference + * + * https://invoiceninja.github.io/docs/statics/#page-content + */ + public ?string $country_id = null; + + /** + * Country Code - optional iso_3166_2 or iso_3166_3 country code + * This can be used optionally instead of the country_id field + */ + public ?string $country_code = null; + + /** + * Shipping Address line 1 - optional + */ + public ?string $shipping_address1 = null; + + /** + * Shipping Address line 2 - optional + */ + public ?string $shipping_address2 = null; + + /** + * Shipping City - optional + */ + public ?string $shipping_city = null; + + /** + * Shipping State - optional + */ + public ?string $shipping_state = null; + + /** + * Shipping Postal Code - optional + */ + public ?string $shipping_postal_code = null; + + /** + * Shipping Country ID - optional + * + * https://invoiceninja.github.io/docs/statics/#page-content + */ + public ?string $country_id = null; + + /** + * @property-read float $balance - The client Balance + */ + + public float $balance; + + /** + * @property-read float $credit_balance - The client credit_balance + */ + + public float $credit_balance; + + /** + * @property-read float $paid_to_date - The client paid_to_date + */ + + public float $paid_to_date; + + /** + * @var ClientSettings $settings + * + * The client settings object + */ + + public ClientSettings $client_settings; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + protected array $rules = [ 'name' => 'required', diff --git a/src/Models/ClientContact.php b/src/Models/ClientContact.php index fdebee5..35d46a0 100644 --- a/src/Models/ClientContact.php +++ b/src/Models/ClientContact.php @@ -15,14 +15,37 @@ class ClientContact extends BaseModel { protected string $model = 'ClientContact'; + /** + * The first name of the contact - optional + * + */ public string $first_name = ''; + /** + * The last name of the contact - optional + * + */ public string $last_name = ''; + /** + * Email - optional + * + */ public string $email = ''; + /** + * Phone number - optional + * + */ public string $phone = ''; + /** + * Flag for whether the contact will + * receive emails. - optional + * + * default - true + * + */ public bool $send_email = true; public string $custom_value1 = ''; diff --git a/src/Models/ClientSettings.php b/src/Models/ClientSettings.php new file mode 100644 index 0000000..cdc936e --- /dev/null +++ b/src/Models/ClientSettings.php @@ -0,0 +1,71 @@ + Date: Fri, 31 Mar 2023 08:49:00 +1100 Subject: [PATCH 4/4] update for dependencies --- composer.json | 4 ++-- src/Models/Client.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index a8b3b9c..ae34fb8 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,8 @@ "psr-4": {"InvoiceNinja\\Sdk\\": "src/"} }, "require": { - "php": "^7.4|^8.0|^8.1", - "guzzlehttp/guzzle": "^7.3", + "php": "^8.1|^8.2", + "guzzlehttp/guzzle": "^7.5", "nesbot/carbon": "^2.66" }, "require-dev": { diff --git a/src/Models/Client.php b/src/Models/Client.php index 9c646a3..d53d70a 100644 --- a/src/Models/Client.php +++ b/src/Models/Client.php @@ -165,7 +165,7 @@ class Client extends BaseModel * * https://invoiceninja.github.io/docs/statics/#page-content */ - public ?string $country_id = null; + public ?string $shipping_country_id = null; /** * @property-read float $balance - The client Balance